View
A view folds events into read state. It can rebuild from history at any time.
Import
import { on, view } from '@marcelsud/accord'Usage
const Summary = view(
'Summary',
{ from: Account, initial: { balance: 0, deposits: 0 } },
on(Deposited, (state, { balance }) => ({ balance, deposits: state.deposits + 1 })),
on(Withdrawn, (state, { balance }) => ({ ...state, balance }))
)
const Bank = model('Bank', { aggregates: { account: Account }, views: { summary: Summary } })Each handler receives (state, event, envelope) and returns the next state synchronously, so the same handler works during replay.
Options
from
Aggregate | View. The source. Views can also listen to other views; downstream views consume the forwarded source events.
initial
The starting state for each row. It also supplies the view's state type.
schema optional
SchemaInput. Validate state at runtime.
version optional
number, default 1. Bump it when the calculation changes to rebuild from history. Versions can't decrease within a store.
keyOf optional
(envelope) => string | string[] | undefined. Rows are keyed by the source instance ID by default. Return several keys to fan out, or undefined to skip the event.
const ByCurrency = view(
'ByCurrency',
{ from: Account, initial: { total: 0 }, keyOf: ({ event }) => event.currency },
on(Deposited, (state, { amount }) => ({ total: state.total + amount }))
)Reading
const [summary, error] = await bank.summary.get('a-1')
const [snapshot] = await bank.summary.snapshot('a-1') // { state, cursor }
const [history] = await bank.summary.events('a-1', { after: 0 })Read your own writes
Without options, get() returns what is materialized now, which may be null. Pass a command receipt to flush and catch up first:
const [receipt] = await bank.account.deposit('a-1', 100)
const [summary] = await bank.summary.get('a-1', { atLeast: receipt })Watch
Native watchers are async generators of snapshot tuples. Pass an AbortSignal to stop.
const controller = new AbortController()
for await (const [snapshot, error] of bank.summary.watch('a-1', { signal: controller.signal })) {
if (error !== null) break
console.log(snapshot.state)
}Contract form
view(name, { from, on, state, initial, version?, keyOf? }, evolve) is also supported. evolve is (state, event) => state, usually with match.
const Summary = view(
'Summary',
{ from: Account, on: [Deposited, Withdrawn], state: { balance: integer }, initial: () => ({ balance: 0 }) },
(state, event) =>
match(event, {
Deposited: ({ amount }) => ({ balance: state.balance + amount }),
Withdrawn: ({ amount }) => ({ balance: state.balance - amount }),
})
)