|
|
před 2 týdny | |
|---|---|---|
| .. | ||
| examples | před 3 dny | |
| src | před 3 dny | |
| tests | před 3 dny | |
| Cargo.toml | před 3 dny | |
| README.md | před 3 dny | |
Async ledger resource — the main entry point for callers.
Composes kuatia-core (validation) and kuatia-storage (persistence) into
a saga-driven commit pipeline with automatic retry and compensation.
Build transfers with TransferBuilder, then commit them:
let transfer = TransferBuilder::new()
.deposit(alice, usd, Cent::from(100), bank)
.build();
let receipt = ledger.commit(transfer).await?;
| Builder method | Description |
|---|---|
.pay(from, to, asset, amount) |
Transfer with automatic posting selection and change |
.deposit(to, asset, amount, external) |
Fund an account from an external source |
.withdraw(from, asset, amount, external) |
Send value to an external destination |
.movement(from, to, asset, amount) |
Raw movement for custom operations |
Every commit is the envelope saga — two steps driven by legend with
automatic retry and LIFO compensation:
commit(transfer) — resolves the intent into a concrete envelope (read-only),
then runs commit_envelope.commit_envelope(envelope) — the one commit path. Persists a write-ahead
PendingSaga record (phase Reserving), then:
reserve_postings: Active → PendingInactive, stamped with this saga's ReservationIdFinalizing, then runs the dumb primitives deactivate_postings → insert_postings → store_transfer → append_event, verifying every end-statereverse(id) — builds a reversal envelope and runs the same path.The store reports an affected-row count for each primitive; the saga
interprets it (full = continue, partial = error → compensate, zero = read state
and continue only if this same envelope already applied it). There is no
monolithic commit_transfer and no separate "atomic" path.
recover() — call on startup. It completes any PendingSaga left by a crash,
branching on the persisted phase: a Reserving saga is re-run (re-validating,
aborting cleanly if a posting was taken or an account frozen); a Finalizing
saga is rolled forward through the verified finalize_envelope. Roll-forward,
not rollback.
| Method | Description |
|---|---|
create_account(account) |
Create account and emit AccountCreated event |
freeze(id) |
Set FROZEN flag |
unfreeze(id) |
Clear FROZEN flag |
close(id) |
Set CLOSED flag (requires zero active postings) |
| Method | Description |
|---|---|
balance(account, asset) |
Current balance (sum of non-Inactive postings) |
query_transfers(query) |
Paginated, filtered transfer history |
history(account) |
All transfers for an account |
postings(account) |
All postings (any status) |
get_events_since(seq, limit) |
Query ledger event log |
Combine steps into multi-transfer workflows using the legend! macro:
legend! {
FundAndPay<LedgerCtx, SagaError> {
deposit: DepositMovementStep,
pay: PayMovementStep,
}
}
Runnable programs in examples/ connect to a real SQLite-backed
ledger (via sqlx) and walk through the core operations:
cargo run -p kuatia --example create_accounts # create user/system/external accounts
cargo run -p kuatia --example fund_and_trade # fund two accounts in different assets, then swap
cargo run -p kuatia --example withdraw # fund an account, then withdraw out of the ledger
Each opens an in-memory SQLite database (sqlite::memory:); point the
connection string at a file or a Postgres URL for a persistent ledger.