Skip to main content

Hsm

Client handle returned by makeHsm — post events, await services, synchronize, restore.

The same object is also the runtime actor (HsmObject); external code should treat it as an actor reference: send messages, never mutate internal queues directly.

Extends

  • Base<Context, Protocol>

Type Parameters

Context

Context = Any

Protocol

Protocol extends { } | undefined = undefined

Properties

currentState

readonly currentState: StateClass<Context, Protocol>

Constructor (Function) of the leaf state class currently executing.

Compare with topState, which is always the root composite passed to makeHsm. After a transition, this updates to the new leaf's constructor.

Inherited from

Base.currentState


currentStateName

readonly currentStateName: string

Human-readable name of currentState.

Sourced from defineStateName / registerStateNames when registered; otherwise Class.name (unreliable under minification — register names in browser builds).

Inherited from

Base.currentStateName


topState

readonly topState: StateClass<Context, Protocol>

Constructor of the root state class supplied to makeHsm.

Constant for the lifetime of the instance unless you replace the entire machine.

Inherited from

Base.topState


topStateName

readonly topStateName: string

Display name of topState (same naming rules as currentStateName).

Inherited from

Base.topStateName


ctxTypeName

readonly ctxTypeName: string

Runtime label derived from ctx constructor name, used as the first segment of traceHeader in verbose traces.

Inherited from

Base.ctxTypeName


traceHeader

readonly traceHeader: string

Prefix for nested trace domains, built from internal dispatch stack frames.

Empty at the top level; grows like domain|subdomain| during nested operations. Handlers rarely need to read this directly — it is prepended automatically by the default TraceWriter.

Inherited from

Base.traceHeader


eventName

readonly eventName: string

Name of the event or service currently being dispatched.

Matches the string passed to Base.post, Hsm.call, or State.postNow. Empty string when no handler is running.

Inherited from

Base.eventName


eventPayload

readonly eventPayload: any[]

Arguments passed with the current dispatch, excluding injected resolve / reject for Hsm.call services.

Empty array when idle. Typed as any[] at runtime; correlate with EventPayload / ServiceRequest at compile time on the client.

Inherited from

Base.eventPayload


traceLevel

traceLevel: TraceLevel

Active trace verbosity; changing this swaps dispatch tracing behavior immediately.

See

TraceLevel

Inherited from

Base.traceLevel


traceWriter

traceWriter: TraceWriter

Destination for runtime and handler-initiated trace lines.

Replaceable at any time (e.g. swap in a test double before post/call).

Inherited from

Base.traceWriter


dispatchErrorCallback

dispatchErrorCallback: DispatchErrorCallback<Context, Protocol>

Last-resort error hook when StateEvents.onError / StateEvents.onUnhandled do not recover.

See

DispatchErrorCallback

Inherited from

Base.dispatchErrorCallback


ctx

readonly ctx: Context

Mutable domain data object shared across all states of this machine instance.

Passed as the second argument to makeHsm; survives transitions unless replaced by Hsm.restore. Update fields freely for internal transitions (no transition()).

Remarks

Context is not the active state name — state is which class prototype is active; context is arbitrary application data (counters, buffers, IDs, flags).

Methods

post()

post<EventName>(eventName, ...eventPayload): void

Enqueue a normal-priority event for later dispatch on the active state.

Returns immediately; the handler runs asynchronously when the mailbox reaches this job. Dispatch walks the prototype chain from the current leaf upward until a method named eventName is found.

Type Parameters

EventName

EventName extends string | number | symbol

Literal key of Protocol being posted

Parameters

eventName

PostedEvent<Protocol, EventName>

Event or service name. Must be keyof Protocol and must not collide with reserved State method names (transition, post, ctx, …)

eventPayload

...EventPayload<Protocol, EventName>

Arguments tuple inferred from Protocol[eventName] handler parameters. For events, pass every parameter except resolve / reject. For fire-and-forget events, the handler return type must be void or Promise<void>

Returns

void

Remarks

Client usage: door.post('open') then await door.sync() to wait for completion.

Handler usage: this.post('tick') schedules work after the current handler returns and after any State.transition it requested. Normal-priority posts run after all State.postNow hi-priority jobs drained for the current turn.

Ordering: FIFO among normal-priority jobs. Multiple posts before one sync() are processed in submission order.

Typing: With Protocol extends undefined, accepts any string and any[] (legacy mode).

Examples

door.post('open');
await door.sync(); // handler + transition complete
approve(): void {
this.ctx.approved = true;
this.post('notify'); // runs after this handler finishes
}

Inherited from

Base.post


deferredPost()

deferredPost<EventName>(millis, eventName, ...eventPayload): void

Schedule a normal-priority post after a wall-clock delay.

Uses setTimeout internally; when the timer fires, the event is enqueued like an ordinary post. Timers are not cancelled if the machine transitions or the scheduling handler throws.

Type Parameters

EventName

EventName extends string | number | symbol

Literal key of Protocol being scheduled

Parameters

millis

number

Delay in milliseconds before enqueueing (≥ 0). Subject to event-loop timer granularity

eventName

PostedEvent<Protocol, EventName>

Event name (same constraints as post)

eventPayload

...EventPayload<Protocol, EventName>

Handler arguments tuple (same as post)

Returns

void

Remarks

Available on State and Hsm. Typical pattern: handler schedules reminder, client waits with await sleep(millis); await hsm.sync().

Does not block the calling handler — returns as soon as the timer is registered.

Example

scheduleReminder(text: string): void {
this.deferredPost(50, 'deliver', text);
}
deliver(text: string): void {
this.ctx.message = text;
}

Inherited from

Base.deferredPost


sync()

sync(): Promise<void>

Wait until all previously enqueued mailbox work completes through a sync marker.

Returns a Promise resolved when the marker job reaches the front of the queue and runs — meaning every job enqueued before this sync() call has finished (handlers, transitions, hi-priority drains, and previously scheduled timers that have already fired).

Returns

Promise<void>

Promise that resolves when the queue drains up to the marker (does not reject on handler errors unless dispatchErrorCallback rethrows to caller)

Remarks

  • After post: one sync() waits for that handler and its transition
  • Batch posts: single sync() waits for all jobs enqueued before it
  • After chained handler posts: call sync() again to drain follow-up work
  • After postNow chains: may require two sync() calls (handler + hi-priority drain)
  • After call: usually unnecessary — await call(...) already waits for resolve/reject
  • Initialization: makeHsm(..., initialize: true) enqueues init work; await sync() before asserting initial state

Example

door.post('open');
await door.sync();
expect(door.currentStateName).toBe('Open');

restore()

restore(state, ctx): void

Atomically replace the active leaf state and context without running onExit / onEntry.

Used for persistence rehydration, snapshot restore, time-travel debugging, and test fixtures. Does not enqueue mailbox jobs — the next post/call runs from the restored configuration.

Parameters

state

StateClass<Context, Protocol>

Leaf or composite state class to activate (prototype switched immediately)

ctx

Context

New context object (replaces ctx reference entirely)

Returns

void

Remarks

Caller is responsible for consistency: restored ctx should match what state expects. Does not walk @InitialState — if you restore a composite class, you get that exact class, not its default child. Queued jobs from before restore are not cancelled.

Example

checkpoint.restore(SavedState, savedCtx);
await checkpoint.sync(); // drain any pre-restore jobs first if needed

call()

call<EventName>(eventName, ...eventPayload): Promise<ServiceResponse<Protocol, EventName>>

Invoke a service handler and await its typed result over the mailbox.

Enqueues a dispatch job like post, but the runtime prepends resolve and reject callbacks to the handler invocation. The returned Promise settles when the handler calls resolve(value) or reject(error)not when the handler function returns.

Type Parameters

EventName

EventName extends string | number | symbol

Literal service name on Protocol

Parameters

eventName

ServiceName<Protocol, EventName>

Service key whose handler signature starts with (resolve: ResolveCallback<T>, reject: RejectCallback, ...payload)

eventPayload

...ServiceRequest<Protocol, EventName>

Request arguments after resolve/reject (client never passes callbacks)

Returns

Promise<ServiceResponse<Protocol, EventName>>

Promise resolving to T inferred from the handler's resolve parameter type

Throws

Propagates any Error passed to reject, or EventHandlerError / UnhandledEventError if dispatch fails before the service runs

Remarks

  • Same serialized mailbox as post — no concurrent handler re-entrancy
  • Return type ServiceResponse is inferred from Protocol[eventName]
  • Use ResolveCallback / RejectCallback in handler signatures for clarity
  • async handlers should await work then call resolve(result)

Example

// Protocol: getBalance(resolve: ResolveCallback<number>, reject: RejectCallback): void
const balance = await wallet.call('getBalance');