state
@rotorsoft/act-root / act/src / state
Function: state()
state<
TName,TState>(entry):StateBuilder<TState,TName>
Defined in: libs/act/src/state-builder.ts:456
Creates a new state definition with event sourcing capabilities.
States are the core building blocks of Act. Each state represents a consistency boundary (aggregate) that processes actions, emits events, and maintains its own state through event patches (reducers). States use event sourcing to maintain a complete audit trail and enable time-travel capabilities.
The state builder provides a fluent API for defining:
- Initial state via
.init() - Event types via
.emits()— all events default to passthrough (({ data }) => data) - Custom event reducers via
.patch()(optional — only for events that need custom logic) - Actions (commands) via
.on()→.emit()— pass an event name string for passthrough - Business rules (invariants) via
.given() - Snapshotting strategy via
.snap()
Type Parameters
TName
TName extends string
TState
TState extends Schema
Zod schema type defining the shape of the state
Parameters
entry
StateEntry<TName, TState>
Single-key record mapping state name to Zod schema (e.g., { Counter: z.object({ count: z.number() }) })
Returns
StateBuilder<TState, TName>
A StateBuilder instance for fluent API configuration
Examples
import { state } from "@rotorsoft/act";
import { z } from "zod";
const Counter = state({ Counter: z.object({ count: z.number() }) })
.init(() => ({ count: 0 }))
.emits({
Incremented: z.object({ amount: z.number() })
})
.patch({ // optional — only for events needing custom reducers
Incremented: ({ data }, state) => ({ count: state.count + data.amount })
})
.on({ increment: z.object({ by: z.number() }) })
.emit((action) => ["Incremented", { amount: action.by }])
.build();
const DigitBoard = state({ DigitBoard: z.object({ digit: z.string() }) })
.init(() => ({ digit: "" }))
.emits({ DigitCounted: z.object({ digit: z.string() }) })
// no .patch() — passthrough is the default (event data merges into state)
.on({ CountDigit: z.object({ digit: z.string() }) })
.emit("DigitCounted") // string passthrough — action payload becomes event data
.build();
const BankAccount = state({ BankAccount: z.object({
balance: z.number(),
currency: z.string(),
status: z.enum(["open", "closed"])
}) })
.init(() => ({ balance: 0, currency: "USD", status: "open" }))
.emits({
Deposited: z.object({ amount: z.number() }),
Withdrawn: z.object({ amount: z.number() }),
Closed: z.object({})
})
.patch({ // only override events needing custom logic
Deposited: ({ data }, state) => ({ balance: state.balance + data.amount }),
Withdrawn: ({ data }, state) => ({ balance: state.balance - data.amount }),
Closed: () => ({ status: "closed", balance: 0 })
})
.on({ deposit: z.object({ amount: z.number() }) })
.given([
(_, snap) => snap.state.status === "open" || "Account must be open"
])
.emit("Deposited") // passthrough — action payload { amount } becomes event data
.on({ withdraw: z.object({ amount: z.number() }) })
.given([
(_, snap) => snap.state.status === "open" || "Account must be open",
(_, snap, action) =>
snap.state.balance >= action.amount || "Insufficient funds"
])
.emit("Withdrawn")
.on({ close: z.object({}) })
.given([
(_, snap) => snap.state.status === "open" || "Already closed",
(_, snap) => snap.state.balance === 0 || "Balance must be zero"
])
.emit("Closed")
.build();
const User = state({ User: z.object({
name: z.string(),
email: z.string(),
loginCount: z.number()
}) })
.init((data) => ({ ...data, loginCount: 0 }))
.emits({
UserCreated: z.object({ name: z.string(), email: z.string() }),
UserLoggedIn: z.object({})
})
.patch({ // only override events needing custom logic
UserLoggedIn: (_, state) => ({ loginCount: state.loginCount + 1 })
})
// UserCreated uses passthrough — event data merges into state
.on({ createUser: z.object({ name: z.string(), email: z.string() }) })
.emit("UserCreated") // passthrough
.on({ login: z.object({}) })
.emit("UserLoggedIn")
.snap((snap) => snap.patches >= 10) // Snapshot every 10 events
.build();
See
- StateBuilder for available builder methods
- ActionBuilder for action configuration methods
- Getting Started Guide
- Calculator Example