Skip to content

@mantaq/core

function state<const T extends string>(id: T): <Payload = unknown>() => StateRef<T, Payload, false>;

Two calls: id, then payload type.

const basicInfo = state("basicInfo")();
const payment = state("payment")<{ cardNumber: string }>();
PropertyTypeWhat
nameTState id
isFinalIsFinalTerminal state?
.final()StateRef<T, Payload, true>Mark final
.create(payload){ state, payload }Make state entry. Payload required
.regions(options)thisDeclared but not wired (no-op; kept internal)

function event<const T extends string>(
id: T,
): <Payload extends object | void = void>() => EventRef<T, Payload>;

Two calls: id, then payload type. Payload must be object | void.

const submitBasicInfo = event("submitBasicInfo")<{ email: string; name: string }>();
const back = event("back")();
PropertyTypeWhat
typeTEvent type (discriminant)
.is(event)booleanIdentity check. Type guard
.create(){ type: T }Void-payload events only
.create(payload){ type: T; payload: Payload }Payload events. Arg required

class Context<T> {
constructor(get: () => T, set: (value: T) => void);
get(): T;
set(value: T): void;
}

Read/write handle to actor context. Passed to handlers and effects.


class Actor<
const States extends readonly AnyStateRef[],
const Inputs extends readonly AnyEventRef[],
const Internal extends readonly AnyEventRef[] = readonly [],
const Outputs extends readonly AnyEventRef[] = readonly [],
ActorContext = Record<string, unknown>,
>
OptionTypeWhat
inputsEventRef[]External events accepted
outputsEventRef[]Events to parent
internalEventRef[]Events from effects/transitions
statesStateRef[]All states
initialStateRef or { state, payload? }Start state
contextActorContextInitial context
clockClockDefault: RealClock
setup(m: ActorBuilder) => voidRegister transitions and effects
regionsRecord<string, AnyActor>Static children
internalBudgetnumberMax internal events (default: 10000)
MethodSignatureWhat
send(event)(event: CreatedOf<Inputs[number]>) => voidSend event
inject(event)(event: InternalEvent) => voidPush internal event and drain (tests/embedders)
dispose()() => voidStop: abort effect, clear queue + subscribers
recover(target)(target: { state; context }) => voidRestart dead machine from caller-supplied state
snapshot()() => SnapshotState tree
on("change", fn)(fn) => () => voidState or context changed ((snap, prev) => void)
on("done", fn)(fn) => () => voidFinal or error state
on("error", fn)(fn) => () => voidDeath signal, seeded to late subscribers ((info) => void)
on("output", fn)(fn) => () => voidEmitted output event ((event) => void)
settled()() => Promise<void>Queue drained
pendingEffectCount()() => numberIn-flight effects not yet settled (introspection)
PropertyTypeWhat
stateStates[number] | ErrorStateCurrent state (__error on error)
contextActorContextCurrent context
clockClockClock instance
regionsRecord<string, AnyActor>Children
optionsInternalActorOptions<...>Built options. Semi-internal

Type-safe registrar passed to setup. Targets validate against declared states, inputs, internal, and outputs. Undeclared state, event, or emit fails to compile.

setup: (m) => {
m.on(basicInfo, submitBasicInfo, () => ({ state: payment }));
m.onAny(back, () => ({ state: basicInfo }));
m.effect(payment, { name: "chargePayment", fn: ({ signal, emit }) => {} });
};
MethodSignatureWhat
on(state, event, fn)(stateRef, eventRef, fn) => thisTransition for one pair
onAny(event, fn)(eventRef, fn) => thisHandler in every state
effect(state, { name, fn })(stateRef, { name, fn }) => thisRun fn on state entry

Handler return: { state?; payload?; emit? } or {} to stay in place.

Effects run on every state entry. Each takes a required camelCase name; executed effects are recorded in history as { stateName, effectName }, and tests can assert on them by name (harness.assertEffectRan(stateName, effectName)). Initial state runs effects at construction with synthetic event { type: "__init" }. Terminal states run theirs on entry.


class VirtualClock implements Clock {
advance(ms: number): void;
hasPending(): boolean;
pendingTimers(): Array<{ id: number; deadline: number; ms: number; eventName?: string }>;
setDrain(fn: () => void): void;
now(): number;
setTimeout(ms, cb, options?): number;
clearTimeout(id): void;
setInterval(ms, cb, options?): number;
clearInterval(id): void;
}

Deterministic. advance(ms) fires all due timers instantly. Aborted signal passed to setTimeout/setInterval returns -1.


class RealClock implements Clock {
now(): number;
setTimeout(ms, cb, options?): number;
clearTimeout(id): void;
setInterval(ms, cb, options?): number;
clearInterval(id): void;
}

Real timers via globalThis.setTimeout. Default for actors.


interface Snapshot<C = unknown> {
path: string[];
context: C;
regions: Record<string, Snapshot<unknown>>;
done?: boolean;
error?: ErrorInfo;
}

done set on final state including __error, so a died machine reports done: true and error. done alone means successful completion. error means it died. Either way sends are ignored.

interface Clock {
setTimeout(
ms: number,
cb: () => void,
options?: { signal?: AbortSignal; eventName?: string },
): number;
clearTimeout(id: number): void;
setInterval(ms: number, cb: () => void, options?: { signal?: AbortSignal }): number;
clearInterval(id: number): void;
now(): number;
setDrain?(fn: () => void): void;
}
type EffectInput<ActorContext, Payload = unknown> = {
signal: AbortSignal;
state: { name: string; payload: Payload };
event: InternalEvent;
context: Context<ActorContext>;
emit: (event: InternalEvent) => void;
clock: Clock;
};
type TransitionResult<AllowedState = AnyStateRef, AllowedEmit = string> = {
state?: AllowedState | { state: AllowedState; payload?: unknown };
payload?: unknown;
emit?: Array<{ type: AllowedEmit }>;
};

Internal type. Not exported. Handler returns infer from it.

type InternalEvent = { type: string; payload?: unknown };
interface AnyActor<C = Record<string, unknown>> {
state: AnyStateRef;
clock: Clock;
regions: Record<string, AnyActor>;
context?: C;
options?: {
transitions?: Record<string, Record<string, unknown>>;
effects?: Record<string, unknown[]>;
states?: ReadonlyArray<{ name: string; isFinal: boolean }>;
};
send(event: AnyEventRef | InternalEvent): void;
snapshot(): Snapshot<C>;
on(event: "change", fn: (snapshot: Snapshot<C>, prev: Snapshot<C>) => void): () => void;
on(event: "done", fn: () => void): () => void;
on(event: "error", fn: (info: ErrorInfo) => void): () => void;
settled(): Promise<void>;
}
TypeWhat
NonFinalStateRef<States>Non-terminal state refs
CreatedOf<E>Event instance type for E: { type } or { type; payload }
CreatedOfEvent<Id, P>Same, explicit id and payload
EffectFn<C, P>(input: EffectInput<C, P>) => void | Promise<void>
ErrorInfo{ error; state; context; event; reason }. Last failure
ErrorStateStateRef<"__error", unknown, true>. Terminal error state
SetupFn<...>(m: ActorBuilder<...>) => void. The setup option type