Skip to content

@mantaq/sugar

import { checkout } from "../checkout.ts";
function matches(actor: { snapshot(): Snapshot }, pattern: string): boolean;

Dot-separated pattern match against snapshot tree.

matches(checkout, "payment"); // flat
matches(checkout, "basicInfo.fraud.scanning"); // hierarchical
matches(checkout, "basicInfo.fraud.cleared"); // parallel region

Prefix match. Checks top-level state name first, descends through region names in order, ends at state boundary. matches(actor, "connected") is true only if top-level state is connected, not anywhere inside. That is isIn.


function states(
...entries: Array<string | { name: string; final?: boolean }>
): Record<string, StateRef>;

Batch state creation. Entries are strings, or objects with final: true for a terminal state.

const s = states("basicInfo", "payment", { name: "success", final: true }, "error");
s.basicInfo.name; // "basicInfo"
s.success.isFinal; // true

function events<T extends string>(...names: T[]): { [K in T]: EventRef<K> };

Batch event creation.

const e = events("submitBasicInfo", "submitPayment", "back");
e.submitBasicInfo.create(); // { type: "submitBasicInfo" }

function tag(...stateRefs: StateRef<string, unknown>[]): Tag;

Group states. tag.has(snapshot) returns true if any active.

const busy = tag(s.payment, s.submitting);
busy.has(checkout.snapshot()); // true if paying or submitting

Keyed registry of one actor type.

const map = new ActorMap(createOrderWorker);
map.spawn("ord_1");
map.send("ord_1", ship.create());
map.kill("ord_1");

new ActorMap(factory, options?). Factory builds actor, receiving key: (id: string) => Actor. spawn(key) creates fresh instance keyed by id. options: { autoReap? }: true removes child once it reaches final state or dies into __error. Off by default.

MethodWhat
spawn(key)Fresh instance via factory
ensure(key)No-op if exists
send(key, event)Send to child
kill(key)Abort and remove
has(key)Exists?
keys()List keys
snapshot(key)Child snapshot. undefined if absent
sizeCount

function broadcast<const T extends SendableEvent>(map: SendableMap<T>, event: T): void;

Send to all children.


function isIn(snapshot: Snapshot, stateRefName: string): boolean;

Recursive search of snapshot tree for state name.


function activeLeaves(snapshot: Snapshot): string[];

All leaf paths as dot-separated strings.


function actorSpec<
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>,
const Initial extends AnyStateRef | { state: AnyStateRef; payload?: unknown } = AnyStateRef,
>(config: {
states: States;
inputs: Inputs;
internal?: Internal;
outputs?: Outputs;
context?: ActorContext;
initial: Initial;
clock?: Clock;
regions?: Record<string, AnyActor>;
internalBudget?: number;
}): {
states: States;
inputs: Inputs;
internal?: Internal;
outputs?: Outputs;
context?: ActorContext;
initial: Initial;
clock?: Clock;
regions?: Record<string, AnyActor>;
internalBudget?: number;
};

Builds static actor spec. Narrows state/event arrays to literals (no as const), keeps context mutable. Feed to definePart<typeof x> and withParts.

const checkout = actorSpec({
inputs: [submitBasicInfo, submitShipping],
internal: [paymentOk, paymentFail],
states: [basicInfo, shippingAddress, payment, submitting],
initial: basicInfo,
context: {} as CheckoutContext,
});

typeof checkout is the anchor for definePart. ActorSpec is the type of such a value; BuilderOf<ActorSpec> is its builder.


function definePart<S extends ActorSpec = never>(fn: (m: BuilderOf<S>) => void): Fragment<S>;

Wrap a slice of setup (m.on, m.onAny, m.effect) for its own file. Generic required: omit spec anchor and S defaults to never, so registering anything is a compile error.

const basicInfoPart = definePart<typeof checkout>((m) => {
m.on(basicInfo, submitBasicInfo, (event, opts) => {
const cur = opts.context.get();
cur.basicInfo = event.payload;
opts.context.set(cur);
return { state: shippingAddress };
});
});

Anchor with typeof <spec value>. Import spec type-only in part file, refs as value imports.


function use<S extends ActorSpec>(m: BuilderOf<S>, part: Fragment<S>): void;

Register a part inside hand-written setup.

setup: (m) => {
use(m, basicInfoPart);
use(m, submittingPart);
};

Not a React hook. Takes Fragments from definePart. Never annotate Part type by hand.


function withParts<
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>,
>(
base: Omit<ActorOptions<States, Inputs, Internal, Outputs, ActorContext>, "setup">,
parts:
| Part<States, Inputs, Internal, Outputs, ActorContext>
| readonly Part<States, Inputs, Internal, Outputs, ActorContext>[],
): Actor<States, Inputs, Internal, Outputs, ActorContext>;

Build actor from spec plus parts. Setup generated. Parts run against builder in order. base takes every option new Actor takes.

Parts array shares actor generics: Part<States, ...>. A part built against one spec cannot wire into another. For same (state, event), last registered handler wins, across parts and inline code. Effects append: every effect on a state runs on entry.

const actor = withParts(checkout, [basicInfoPart, submittingPart, backPart]);

type ActorSpec; // shape of the static actor spec (states/inputs/.../initial)
type BuilderOf<S>; // ActorBuilder for that spec
type Fragment<S>; // (m: BuilderOf<S>) => void
type Part<S, I, Int, O, C>; // (m: ActorBuilder<S, I, Int, O, C>) => void — withParts array element type

ActorSpec is a constraint, not a value annotation: annotating with ActorSpec (or ActorOptions) erases literal types. Build with actorSpec, pass typeof <that value>. Never annotate the object itself.


function onOutput(actor: AnyActor, handler: (event: InternalEvent) => void): void;

Route actor emitted outputs to a handler. regions auto-wire child outputs to parent; ActorMap children do not. One call connects declared outputs to a receiver.

onOutput(childActor, (event) => {
// handle emitted output
});

withPromise(promise, signal, emit, events)

Section titled “withPromise(promise, signal, emit, events)”
function withPromise<T>(
promise: Promise<T>,
signal: AbortSignal,
emit: EmitFn,
events: {
success: (data: T) => InternalEvent;
error: (err: unknown) => InternalEvent;
},
): void;

Promise to actor events. Checks signal.aborted.


function withTimeout<ActorContext>(
ms: number,
input: EffectInput<ActorContext>,
event: () => InternalEvent,
): void;

Timeout using actor’s clock. Checks abort signal.