diff --git a/CHANGELOG.md b/CHANGELOG.md index 6096153..8374f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,26 @@ # Changelog +All notable changes to `fragment-dev` will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/). + +Releases prior to `2.0.0` were published before this changelog was added and +are not documented here. + ## [2.1.0] ### Added -- `add_ledger_entries` commits a batch of Ledger Entries atomically. It accepts +- `add_ledger_entries` posts a batch of Ledger Entries atomically. It accepts raw `AddLedgerEntryInput` hashes, typed payloads, or both in one batch, and preserves their order. -- Typed batch payloads. `FragmentClient::TypedEntries.load` derives one payload - class per `(Ledger Entry type, typeVersion)` from the per-entry-type - `addLedgerEntry` operations the Fragment CLI generates for your Schema, so a - batch can be built with real parameter names instead of untyped hashes: +- Strongly-typed batch payloads. `FragmentClient::TypedEntries.load` derives one + payload class per `(Ledger Entry type, typeVersion)` from the per-entry-type + `addLedgerEntry` operations the Fragment CLI generates for your Schema. Because a + batch mutation takes one list of one input type, GraphQL cannot type each entry's + `parameters` field individually; these payloads do. Payload names always carry the + entry type version, defaulting to `V1`: ```ruby fragment.add_ledger_entries(entries: [ diff --git a/README.md b/README.md index 809e320..28c4009 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [Fragment](https://fragment.dev) is the Ledger API for engineers that move money. Stop wrangling payment tables, debugging balance errors and hacking together data pipelines. Start shipping the features that make a difference. +See [CHANGELOG.md](CHANGELOG.md) for release notes and upgrade guidance. + ## Installation To install the Fragment SDK for Ruby, you'll need to install the gem. Run the following command in your terminal: @@ -34,7 +36,7 @@ fragment = FragmentClient.new( ### Post a Ledger Entry -To post a Ledger Entry defined in your schema: +To [post](https://fragment.dev/guides/post-ledger-entries#post-to-the-api) a Ledger Entry defined in your Schema: ```ruby fragment.add_ledger_entry({ @@ -51,95 +53,36 @@ fragment.add_ledger_entry({ ### Post a batch of Ledger Entries -`add_ledger_entries` commits a batch atomically — either every entry commits or -none do. Idempotency keys are per entry, so a retried batch reports `isIkReplay` -for each one. - -```ruby -result = fragment.add_ledger_entries(entries: [ - { - ik: "some-ik", - entry: { - ledger: { ik: "your-ledger-ik" }, - type: "user_funds_account", - posted: "1968-01-01T16:45:00Z", - parameters: { user_id: "user-1", funding_amount: "200" } - } - } -]) - -case result.data.add_ledger_entries.__typename -when "AddLedgerEntriesResult" - result.data.add_ledger_entries.results.each { |r| puts [r.entry.ik, r.is_ik_replay].inspect } -when "AddLedgerEntriesError" - # One element per failing entry, each carrying the ik that identifies it. - result.data.add_ledger_entries.errors.each { |e| warn "#{e.ik}: #{e.message}" } -end -``` - -Writing those nested hashes by hand is easy to get wrong, and nothing checks the -parameter names against your Schema. The next section is about not doing that. - -### Typed batch payloads - -The Fragment CLI generates a per-entry-type `addLedgerEntry` operation for each -Ledger Entry in your Schema. Those operations know two things GraphQL cannot -express for a batch: the entry type, and the type of every parameter. The SDK -derives a payload class per Ledger Entry from them. - -Pass the `.graphql` file as an extra queries file and the payloads are registered -for you: +To [post](https://fragment.dev/guides/post-ledger-entries#batch-ledger-entries) a +batch of Ledger Entries atomically: ```ruby -fragment = FragmentClient.new( - 'your-client-id', - 'your-client-secret', - extra_queries_filenames: ['app/graphql/entries.graphql'] -) - fragment.add_ledger_entries(entries: [ FragmentClient::Entries::UserFundsAccountV1.new( - ik: "some-ik", + ik: "some-ik-1", ledger_ik: "your-ledger-ik", posted: "1968-01-01T16:45:00Z", user_id: "user-1", - funding_amount: "200" + funding_amount: "20000" + ), + FragmentClient::Entries::UserFundsAccountV1.new( + ik: "some-ik-2", + ledger_ik: "your-ledger-ik", + posted: "1968-01-01T16:45:00Z", + user_id: "user-2", + funding_amount: "20000" ) ]) ``` -A payload is named for its Ledger Entry type and the version it posts, so adding -`user_funds_account` v2 later leaves every existing `...V1` call site alone. A -misspelled or missing parameter raises immediately rather than reaching the API, -and typed payloads can be mixed with the raw hashes above in a single batch. - -Nothing you did not set is sent. An omitted field is absent from the request; an -explicit `nil` is sent as `null`, because those mean different things to the API. - -Readers behave the way you would expect either way — an unset field reads as `nil`, -so `entry.posted&.iso8601` and `if entry.description` do the obvious thing. When -you need to tell "never set" from "set to `nil`", ask: - -```ruby -entry = FragmentClient::Entries::UserFundsAccountV1.new( - ik: "some-ik", ledger_ik: "your-ledger-ik", user_id: "user-1", funding_amount: "200" -) -entry.posted #=> nil -entry.set?(:posted) #=> false - -entry.to_entry_input # the exact hash that goes on the wire, if you want to inspect it -``` - -Payloads compare by value, so they are straightforward to assert on in your own -tests. +Construct the entries in the batch using the typed payloads generated for your +Schema, named `V` under `FragmentClient::Entries`. -You can register payloads without constructing a client — no credentials, no +Payloads can also be registered without constructing a client — no credentials, no network: ```ruby FragmentClient::TypedEntries.load('app/graphql/entries.graphql') -FragmentClient::TypedEntries.fetch('user_funds_account', 1) -#=> FragmentClient::Entries::UserFundsAccountV1 ``` ### Sorbet @@ -159,7 +102,7 @@ does not declare. ### Sync Transactions -To sync transaction using a custom link: +To sync transaction using a [Custom Link](https://fragment.dev/guides/sync-payments#custom-link): ```ruby fragment.sync_custom_accounts({ @@ -197,7 +140,7 @@ fragment.sync_custom_txs({ ### Reconcile a Transaction -To reconcile a transaction: +To [reconcile](https://fragment.dev/guides/reconcile-payments#reconcile-a-tx) a transaction: ```ruby fragment.reconcile_tx({ @@ -250,7 +193,7 @@ ledger_entry_details = ledger_entry_response.data.ledger_entry ### Get a Ledger Account with Balance -To get the balance details of a specific ledger account: +To read a Ledger Account's [balance](https://fragment.dev/guides/read-balances#latest): ```ruby ledger_account_balance_response = fragment.get_ledger_account_balance({ diff --git a/test/add_ledger_entries_test.rb b/test/add_ledger_entries_test.rb index 4622b7e..449a8f2 100644 --- a/test/add_ledger_entries_test.rb +++ b/test/add_ledger_entries_test.rb @@ -79,8 +79,7 @@ def test_a_payload_converts_to_a_hash_under_either_name def test_raw_entries_work_without_loading_any_typed_payloads # The batch method is not conditional on the typed-payload machinery: a client - # constructed with no extra queries still posts a batch of plain hashes, which - # is the first example in the README. + # constructed with no extra queries still posts a batch of plain hashes. entry = { ik: 'raw-1', entry: { ledger: { ik: 'prod' }, type: 'user_funds_account', parameters: { user_id: 'user-1' } } }