Skip to content

FsmConfig

Defined in: types.ts:803

Configuration object for creating an FSM.

// TCtx inferred as { tickCount: number }, TStates inferred from states object:
createFsm({
  id: "traffic-light",
  initialState: "green",         // validated against state keys
  context: { tickCount: 0 },     // inference site for TCtx
  states: {
    green:  { timeout: "yellow" }, // "yellow" validated against state keys
    yellow: { timeout: "red" },
    red:    { timeout: "green" },
  },
});

Declaring bubbled inputs for a child FSM

// This FSM fires "phaseComplete" at itself but never handles it — it
// expects whatever mounts it via `_child` to catch the bubble.
const phaseController = createFsm({
  id: "phase-controller",
  initialState: "green",
  bubbles: ["phaseComplete"],
  states: {
    green: { advance: "red" },
    red: {
      _onEnter({ ctx }) {
        setTimeout(() => phaseController.handle("phaseComplete"), 0);
      },
    },
  },
});

// Mounting it without handling "phaseComplete" (and without re-declaring
// it in this FSM's own `bubbles`) is a compile error on `_child` below.
const intersection = createFsm({
  id: "intersection",
  initialState: "northSouth",
  states: {
    northSouth: {
      _child: phaseController,
      phaseComplete: "clearance", // this line is what covers the bubble
    },
    clearance: { advance: "northSouth" },
  },
});

TCtx

The context type (Fsm) or client type (BehavioralFsm). For Fsm, this is inferred from the context property. For BehavioralFsm, it’s the client object type provided explicitly or as a generic parameter.

TStates extends Record<string, Record<string, unknown>> = Record<string, Record<string, unknown>>

The literal states object type. Captured directly from the naked states: TStates & ... intersection member below (ideally with const on the factory’s generic to preserve string literal types) — deliberately NOT derived from ValidateStates, so an empty or function-only state can’t disable inference (see ValidateStates’ module comment for the full mechanism). Defaults to a loose record for unconstrained usage.

TStateNames extends string = keyof TStates & string

The state-name union, defaulted from TStates but captured as its OWN parameter (see ValidateStates) so it’s available, fully resolved, while handler bodies are still being type-checked.

TBubbles extends string = never

The union of inputs this FSM declares via bubbles. Defaults to never — most FSMs bubble nothing.

TInputs extends InputMapLike<TInputs> = InputMap

The input payload map (#195). The curried factory forms fix it before inference starts, so it adds nothing to the inference machinery above. Defaults to the wide map, which keeps ValidateStates and InputVocabulary behaving exactly as the untyped path always has.

optional bubbles?: readonly TBubbles[]

Defined in: types.ts:855

Inputs this FSM fires at itself without handling them — expecting whatever mounts it via _child to catch them through machina’s nohandler-bubbling mechanism (see the “Input delegation” section of the hierarchical states guide). Declaring a bubble does two things:

  1. Joins this FSM’s own typed input union, so a self-directed fsm.handle("phaseComplete") (e.g. from inside _onEnter) type-checks without a cast.
  2. Becomes part of this FSM’s mounting contract: any config that mounts it via _child must handle every declared bubble in some state, re-declare it in its OWN bubbles (passing the obligation up another level), or carry a "*" catch-all — enforced at compile time by ChildCoverage, which is intersected onto states below.

A bubble name is NOT a state name — it can’t be used as a string shorthand transition target. Omit bubbles entirely (the default) for an FSM that never expects a container to catch anything from it — such an FSM can be mounted via _child anywhere with no obligations.


optional context?: TCtx

Defined in: types.ts:833

Initial context data. The type is inferred from this value and flows into every handler’s ctx parameter.

For BehavioralFsm, this property is optional and serves only as a type constraint — the client object IS the context.


id: string

Defined in: types.ts:814

Unique identifier for this FSM


initialState: NoInfer<TStateNames>

Defined in: types.ts:824

The state to start in. Must be a key of states.

Wrapped in NoInfer to prevent TypeScript from using this value as an inference site for TStateNames. Without it, initialState: "green" could narrow the state-name union to only have a “green” key. We want inference to come exclusively from the states property.


states: TStates & ValidateStates<TCtx, TStates, TStateNames, TInputs> & ChildCoverage<TStates, TStateNames, TBubbles> & InputVocabulary<TStates, TStateNames, TInputs>

Defined in: types.ts:866

State definitions. Keys become the state name union.

The intersection with the bare TStates is what makes literal-type capture work for empty and function-only states (see ValidateStates’ module comment) — ValidateStates and ChildCoverage layer validation and the bubble-coverage contract on top without becoming the inference source themselves.