Skip to main content
Version: Current

ActionBuilder

@rotorsoft/act-root


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

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

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

Defined in: libs/act/src/types/builders.ts:760

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 declare preconditions, and emits zero 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

TSnap​

TSnap extends boolean = false

true once .snap(...) has been called. Gates the .autocloses({ keep }) rolling-window option — a windowed close is meaningless without snapshots, so keep only typechecks after .snap in the chain.

Properties​

archives​

archives: (archive) => ActionBuilder<TState, TEvents, TActions, TName, TSnap>

Defined in: libs/act/src/types/builders.ts:938

Declares the archiver the online close cycle runs before truncating a stream this state's .autocloses(...) predicate accepted. Hosts use it to write events to durable storage (S3, an analytics warehouse, cold tier) before the tombstone lands, so the truncate doesn't lose history that the operator still needs.

Threads into CloseTarget.archive via the same plumbing app.close({ stream, archive }) already uses — the cycle holds the stream's guard while the archiver runs, and a thrown archiver leaves the stream guarded but un-truncated. No partial truncate state, no data loss. It is not retried automatically; app.close([{ stream, archive }]) resumes the guarded stream.

One archiver per state. A second .archives(...) call replaces the first (same shape as .snap / .discloses / .autocloses). Absent → the cycle truncates without an archive step.

Parameters​

archive​

AutocloseArchiver<TEvents>

(stream, head) => Promise<void>. Runs while the stream is locked against new writes; the truncate runs immediately after a successful resolve.

Returns​

ActionBuilder<TState, TEvents, TActions, TName, TSnap>

The ActionBuilder for chaining.

Example​

Archive to S3 before truncate.

state({ Ticket: ticketSchema })
.emits({ TicketOpened, TicketResolved })
// ...
.autocloses({ is: "TicketResolved" })
.archives(async (stream) => {
const events = await loadEvents(stream);
await s3.upload(`tickets/${stream}.jsonl`, events);
})

autocloses​

autocloses: (policy) => ActionBuilder<TState, TEvents, TActions, TName, TSnap>

Defined in: libs/act/src/types/builders.ts:890

Declares the online close predicate for this state. The orchestrator's autoclose cycle iterates the state's streams once per tick and calls the predicate per candidate; truthy results are scheduled for atomic truncate-and-seed via Store.truncate on the next batch.

One predicate per state. A second .autocloses(...) call replaces the first (same shape as .snap / .discloses — state-level, not per-event). Absent → the state opts out of online close entirely; the cycle skips it and pays zero per-tick cost for it.

Pass a declarative AutoclosePolicy object literal covering the three operational pressure points (after, is, reaches). Top-level fields combine with AND; an optional or: {...} block opens an alternative OR path. Validated via Zod at build time; misconfiguration throws before act().build() completes.

Under the hood this compiles to a synthesized reaction that runs on a per-aggregate synthetic stream: it defers to head.created + the policy's min after while the cooldown holds and closes the stream once the policy matches. There is no background sweep.

There is no function-predicate form: an opaque predicate has no derivable due-time or terminal event to react to. For conditions the declarative form can't express, call app.close(...) from your own logic.

Parameters​

policy​

[TSnap] extends [true] ? AutoclosePolicy : Omit<AutoclosePolicy, "keep"> & object

The declarative AutoclosePolicy bag.

Returns​

ActionBuilder<TState, TEvents, TActions, TName, TSnap>

The ActionBuilder for chaining.

Example​

Declarative — cooldown after terminal (a Ticket closes

90 days after resolution).

.autocloses({ is: "TicketResolved", after: { days: 90 } })

build​

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

Defined in: libs/act/src/types/builders.ts:960

Finalizes and builds the state definition.

Call this method after defining all actions, preconditions, 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>

discloses​

discloses: (disclose) => ActionBuilder<TState, TEvents, TActions, TName, TSnap>

Defined in: libs/act/src/types/builders.ts:848

Declares the disclosure predicate for sensitive(...)-marked event fields. Gates external reads: returning true allows the actor to see plaintext on the event; returning false substitutes "[REDACTED]". When absent, the framework default-denies on every external read — fail-safe.

One predicate per state. A second .discloses(...) call replaces the first (same shape as snapshots being state-level, not per-event).

The predicate receives the full event including merged PII so it can branch on the payload itself (e.g. event.data.ownerId === actor.id). Reducers, projections, and reactions are unaffected — they follow separate visibility rules.

Parameters​

disclose​

(event, actor) => boolean

Predicate (event, actor) => boolean. true = plaintext, false = "[REDACTED]" substitution.

Returns​

ActionBuilder<TState, TEvents, TActions, TName, TSnap>

The ActionBuilder for chaining.

Example​

Owner-or-admin disclosure

state({ User: userSchema })
.init(() => ({ ... }))
.emits({ UserRegistered: z.object({ email: sensitive(z.string()) }) })
.discloses((event, actor) =>
actor.id === event.stream || actor.roles?.includes("admin"))

on​

on: <TKey, TNewActions>(entry, options?) => ActionRules<TState, TEvents, TActions, TName, TSnap, TKey, TNewActions>

Defined in: libs/act/src/types/builders.ts:798

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 emit zero or more events (none commits nothing).

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.

Pass an optional second argument to declare a per-action retry policy — the orchestrator retries this action on ConcurrencyError up to maxRetries extra times, applying backoff between attempts when set. Omit the argument to keep the current single-attempt behavior (ConcurrencyError surfaces on first conflict).

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 }

options?​

ActionOptions

Optional per-action retry policy (ActionOptions).

Returns​

ActionRules<TState, TEvents, TActions, TName, TSnap, TKey, TNewActions>

An object with .loads(), .requires() and .emit() for further configuration

Example​

Simple action without preconditions

.on({ increment: z.object({ by: z.number() }) })
.emit((action) => ["Incremented", { amount: action.by }])

snap​

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

Defined in: libs/act/src/types/builders.ts:817

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, true>

The ActionBuilder for chaining

Example​

Snapshot every 10 events

.snap((snapshot) => snapshot.patches >= 10)