Skip to main content

ActionBuilder

@rotorsoft/act-root


@rotorsoft/act-root / act/src / ActionBuilder

Type Alias: ActionBuilder<TState, TEvents, TActions, TName>

ActionBuilder<TState, TEvents, TActions, TName> = object

Defined in: libs/act/src/state-builder.ts:137

Builder interface for defining actions (commands) on a state.

Actions represent user/system intents to modify state. Each action is validated against a schema, can have business rule invariants, and must emit one or more events.

See

state for complete usage examples

Type Parameters

TState

TState extends Schema

State schema type

TEvents

TEvents extends Schemas

Event schemas type

TActions

TActions extends Schemas

Action schemas type

TName

TName extends string = string

State name literal type

Properties

build()

build: () => State<TState, TEvents, TActions, TName>

Defined in: libs/act/src/state-builder.ts:336

Finalizes and builds the state definition.

Call this method after defining all actions, invariants, and patches to create the complete State object that can be registered with Act.

Returns

State<TState, TEvents, TActions, TName>

The complete strongly-typed State definition

Example

const Counter = state({ Counter: schema })
.init(() => ({ count: 0 }))
.emits({ Incremented: z.object({ amount: z.number() }) })
.patch({ Incremented: ({ data }, state) => ({ count: state.count + data.amount }) })
.on({ increment: z.object({ by: z.number() }) })
.emit((action) => ["Incremented", { amount: action.by }])
.build(); // Returns State<TState, TEvents, TActions, TName>

on()

on: <TKey, TNewActions>(entry) => object

Defined in: libs/act/src/state-builder.ts:182

Defines an action (command) that can be executed on this state.

Actions represent intents to change state - they should be named in imperative form (e.g., "CreateUser", "IncrementCounter", "PlaceOrder"). Actions are validated against their schema and must emit at least one event.

Pass a { ActionName: schema } record — use shorthand { ActionName } when the variable name matches the action name. The key becomes the action name, the value the Zod schema.

Type Parameters

TKey

TKey extends string

Action name (string literal type)

TNewActions

TNewActions extends Schema

Action payload schema type

Parameters

entry

ActionEntry<TKey, TNewActions>

Single-key record { ActionName: schema }

Returns

An object with .given() and .emit() for further configuration

emit()

emit: (handler) => ActionBuilder<TState, TEvents, TActions & { [P in TKey]: TNewActions }, TName>

Defines the action handler that emits events.

The handler receives the action payload and current state snapshot, and must return one or more events to emit. Return a single event as ["EventName", data] or multiple events as an array of event tuples.

Pass a string event name for passthrough: the action payload becomes the event data directly.

Parameters
handler

Function that returns events to emit, or event name string for passthrough

ActionHandler<TState, TEvents, { [P in TKey]: TNewActions }, TKey> | keyof TEvents & string

Returns

ActionBuilder<TState, TEvents, TActions & { [P in TKey]: TNewActions }, TName>

The ActionBuilder for chaining more actions

Examples
.emit("Incremented")
.emit((action) => ["Incremented", { amount: action.by }])
.emit((action) => [
["Incremented", { amount: action.by }],
["LogUpdated", { message: `Incremented by ${action.by}` }]
])
given()

given: (rules) => object

Adds business rule invariants that must hold before the action can execute.

Invariants are checked after loading the current state but before emitting events. Each invariant should return true or an error message string. All invariants must pass for the action to succeed.

Parameters
rules

Invariant<TState>[]

Array of invariant functions

Returns

An object with .emit() to finalize the action

emit()

emit: (handler) => ActionBuilder<TState, TEvents, TActions & { [P in TKey]: TNewActions }, TName>

Defines the action handler that emits events.

The handler receives the action payload and current state snapshot, and must return one or more events to emit. Events are applied to state via the patch handlers defined earlier.

Pass a string event name for passthrough: the action payload becomes the event data directly.

Parameters
handler

Function that returns events to emit, or event name string for passthrough

ActionHandler<TState, TEvents, { [P in TKey]: TNewActions }, TKey> | keyof TEvents & string

Returns

ActionBuilder<TState, TEvents, TActions & { [P in TKey]: TNewActions }, TName>

The ActionBuilder for chaining more actions

Examples
.emit((action, snapshot) => {
const newBalance = snapshot.state.balance + action.amount;
return ["Deposited", { amount: action.amount, newBalance }];
})
.emit("TicketAssigned")
Example
.given([
(_, snap) => snap.state.status === "active" || "Must be active",
(target, snap) => snap.state.ownerId === target.actor.id || "Not authorized"
])

Examples

.on({ increment: z.object({ by: z.number() }) })
.emit((action) => ["Incremented", { amount: action.by }])
.on({ withdraw: z.object({ amount: z.number() }) })
.given([
(_, snap) => snap.state.balance >= 0 || "Account closed",
(_, snap, action) => snap.state.balance >= action.amount || "Insufficient funds"
])
.emit((action) => ["Withdrawn", { amount: action.amount }])
const OpenTicket = z.object({ title: z.string() });
.on({ OpenTicket })
.emit((action) => ["TicketOpened", { title: action.title }])

snap()

snap: (snap) => ActionBuilder<TState, TEvents, TActions, TName>

Defined in: libs/act/src/state-builder.ts:314

Defines a snapshotting strategy to optimize state reconstruction.

Snapshots store the current state at a point in time, allowing faster state loading by avoiding replaying all events from the beginning. The snap function is called after each event is applied and should return true when a snapshot should be taken.

Parameters

snap

(snapshot) => boolean

Predicate function that returns true when a snapshot should be taken

Returns

ActionBuilder<TState, TEvents, TActions, TName>

The ActionBuilder for chaining

Examples

.snap((snapshot) => snapshot.patches >= 10)
.snap((snapshot) => {
const estimatedSize = JSON.stringify(snapshot.state).length;
return estimatedSize > 10000 || snapshot.patches >= 50;
})
.snap((snapshot) => {
const hoursSinceLastSnapshot = snapshot.patches * 0.1; // Estimate
return hoursSinceLastSnapshot >= 24;
})