Notifications + SQLite ledger
Open an account, deposit money, record a ledger entry in SQLite, and ask an external notification system to tell the account holder. The domain only knows two contracts: Notifications.send and LedgerStore.record.
Source: notifications/
Run it
sh
npm run notifications
npm run notifications:effectEach prints the mode, a committed balance of 100, one ledger row, and Notifications sent: 1. The runner starts a dummy notification service on a loopback port. No public service or account needed.
Pass a fresh filename to inspect the ledger afterwards:
sh
npm run notifications -- /tmp/accord-pure.sqliteThe flow
ts
const [, openError] = await bank.account.open('a-1')
if (openError !== null) throw openError
const [receipt, error] = await bank.account.deposit('a-1', 100)
if (error !== null) throw error
console.log(receipt.reply.balance) // 100
const [, deliveryError] = await bank.flush()
if (deliveryError !== null) throw deliveryError- A method updates state and emits
OpenedorDeposited. State and events commit together. flush()delivers events to the ledger projection and the notification policy.- The policy uses the envelope's stable event ID as the notification's idempotency key.
Swap in a fake
ts
const notifications: NotificationPort = {
async send(input) {
console.log('Notification requested:', input.accountId)
return ok({ id: `fake-${input.eventId}` })
},
}
const { bank } = createBank(ledger, notifications)Adapters
- The HTTP adapters send the event ID as an idempotency key, validate acknowledgements, time out after five seconds, and forward cancellation.
- Conflicts become
NotificationConflict; failed responses becomeNotificationsUnavailable; network and decoding failures becomeUnexpected. - The SQLite adapter upserts by event ID, so replays are safe. A failed statement rolls back the whole batch.
WARNING
The default runner persists the ledger only. Aggregate state and checkpoints stay in memory. For durable state, pass a SQLite runtime store and share its connection with the ledger:
ts
using storage = sqlite('bank.sqlite')
using ledger = openLedger(storage.database)
const { bank } = createBank(ledger, notifications, storage)Files
| File | Responsibility |
|---|---|
domain.ts | Account, rejections, external system contracts |
pure.ts | Native projection, policy, and model |
with-effect.ts | Effect projection, policy, and model |
adapters/notifications.ts | Native notification adapter |
adapters/notifications-effect.ts | Effect notification adapter |
adapters/dummy-notifications.ts | Dummy notification service |
adapters/sqlite.ts | SQLite ledger |
run.ts | Binds adapters, wires the model, runs it |