/** * Normalized internal model produced by `normalize(config)` and consumed by the * interpreter, the exporters and the devtools. Everything here is immutable * after normalization; nodes reference each other directly (no ids to look * up during a step). See `.tmp/statechart-impl-spec.md`, section 2. */ import type { Action, ContextFactory, DoneStateEvent, EventObject, Guard, HistoryType, MachineConfig, MachineContext, Mapper, MetaObject, SnapshotStatus, StateNodeConfig, StateNodeType } from "../types/index.js"; import type { NonReducibleUnknown } from "../types/common.js"; /** Any event an action of the model may observe (user events plus system events). */ export type ModelEvent = TEvent | EventObject; /** Actions as stored in the model: the raw config value, already validated. */ export type ModelAction = Action, TEvent>; /** Guards as stored in the model: the raw config value, already validated. */ export type ModelGuard = Guard>; /** `output` of a final state or of the root: a mapper or a static value. */ export type OutputResolver = Mapper | DoneStateEvent, NonReducibleUnknown> | NonReducibleUnknown; export interface Transition { readonly source: StateNode; /** * Resolved target nodes; `null` for targetless transitions (actions only, * no exit/entry). History nodes may appear here and are resolved at step * time against `MachineState.historyValue`. */ readonly target: readonly StateNode[] | null; readonly actions: readonly ModelAction[]; readonly guard: ModelGuard | null; readonly reenter: boolean; /** * The `on` descriptor (`"TIMER"`, `"*"`, `"user.*"`), a system event type * (`"xstate.done.state."`, `"xstate.after.."`), * `NULL_EVENT` (`""`) for `always`, or `null` for initial / synthetic * transitions. */ readonly eventType: string | null; /** The `after` key this transition came from; `null` otherwise. */ readonly delay: number | string | null; readonly description: string | undefined; readonly meta: MetaObject | undefined; /** Config path for diagnostics: `"states.a.on.E[1]"`, `"states.a.always[0]"`, `"states.a.after.3000[0]"`, `"states.a.onDone[0]"`. */ readonly configPath: string; } /** The implicit transition into a compound state's `initial` child. */ export interface InitialTransition extends Transition { readonly target: readonly [StateNode]; readonly guard: null; readonly reenter: false; readonly eventType: null; readonly delay: null; } export interface StateNode { /** `config.id`, or `.` joined with `"."`; the root's id is the machine id. */ readonly id: string; /** Key under the parent's `states`; the machine id for the root. */ readonly key: string; /** Keys from the root down to this node (`[]` for the root). */ readonly path: readonly string[]; readonly type: StateNodeType; /** Document order (depth-first, root = 0). Drives exit/entry ordering. */ readonly order: number; readonly parent: StateNode | null; /** Children in document order, history nodes included. */ readonly children: readonly StateNode[]; readonly childrenByKey: ReadonlyMap>; /** Compound nodes only. */ readonly initial: InitialTransition | null; /** * Candidates per event descriptor: `on` entries plus the synthesized * `xstate.done.state.` (from `onDone`) and `xstate.after..` * (from `after`) descriptors. Array order = config order (first enabled wins). */ readonly transitions: ReadonlyMap[]>; readonly always: readonly Transition[]; /** `after` transitions, also present in `transitions` under their event type. */ readonly after: readonly Transition[]; /** * `config.entry` followed by one `raise(afterEvent, { delay, id: afterEventType })` * per `after` key (mirrors XState `getDelayedTransitions`). */ readonly entry: readonly ModelAction[]; /** `config.exit` followed by one `cancel(afterEventType)` per `after` key. */ readonly exit: readonly ModelAction[]; /** History nodes only. */ readonly history: HistoryType | null; /** Resolved `config.target` of a history node; `null` when absent (parent's initial / the parent itself for parallel). */ readonly historyTarget: readonly StateNode[] | null; /** Final nodes and the root only; `undefined` otherwise. */ readonly output: OutputResolver | undefined; readonly tags: readonly string[]; readonly meta: MetaObject | undefined; readonly description: string | undefined; /** The raw (frozen) config of this node. */ readonly config: StateNodeConfig; /** `""` for the root, `"states.a.states.b"` below. */ readonly configPath: string; } /** Names referenced by the config, checked against the implementation table by `Statechart`. */ export interface ModelReferences { readonly actions: ReadonlySet; readonly guards: ReadonlySet; readonly delays: ReadonlySet; } export interface MachineModel { readonly id: string; readonly root: StateNode; /** Every node in document order; `nodes[i].order === i`. */ readonly nodes: readonly StateNode[]; readonly idMap: ReadonlyMap>; /** Always a factory: an object `context` is wrapped in `() => context`. */ readonly context: ContextFactory; readonly references: ModelReferences; readonly config: MachineConfig; } /** * The immutable value the pure interpreter steps over. `configuration` holds * every active node including the root and all ancestors (XState `_nodes`). * A step that changes nothing returns the very same object. */ export interface MachineState { readonly configuration: ReadonlySet>; readonly context: TContext; /** History node id → nodes recorded when the history's parent was last exited. */ readonly historyValue: Readonly[]>>; readonly status: SnapshotStatus; readonly output: unknown; readonly error: unknown; }