Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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: [
Expand Down
97 changes: 20 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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({
Expand All @@ -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 `<EntryType>V<typeVersion>` 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
Expand All @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down
Loading
Loading