machina 4.0.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +198 -415
- package/dist/index.d.mts +628 -0
- package/dist/index.d.ts +628 -0
- package/dist/index.js +656 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +651 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +28 -149
- package/changelog.md +0 -187
- package/lib/machina.js +0 -744
- package/lib/machina.min.js +0 -2
- package/lib/machina.min.js.map +0 -1
- package/lib/machina_OLD.js +0 -612
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/emitter.ts","../src/types.ts","../src/behavioral-fsm.ts","../src/fsm.ts"],"names":["payload"],"mappings":"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","file":"index.js","sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Listener = (...args: any[]) => void;\ntype WildcardListener = (eventName: string, data: unknown) => void;\n\n/**\n * Returned by `on()`. Call `off()` to remove the subscription.\n * Safe to call multiple times — subsequent calls are no-ops because\n * the underlying Set deduplicates membership.\n */\nexport type Subscription = { off(): void };\n\n/**\n * Minimal typed event emitter used internally by Fsm and BehavioralFsm.\n *\n * Two listener categories exist at runtime: named listeners (keyed by event\n * name) and wildcard listeners (keyed as `\"*\"`). The wildcard fires on EVERY\n * emit, regardless of event name, and its callback receives both the event\n * name and the data. Named listeners receive only the data.\n *\n * @typeParam TEventMap - Record mapping event names to their payload types.\n * Constrains `on()` and `emit()` to matching event/payload pairs.\n */\nexport class Emitter<TEventMap extends Record<string, unknown>> {\n private listeners = new Map<string, Set<Listener>>();\n\n /**\n * Subscribe to a named event or the wildcard.\n *\n * Named overload: callback receives only the typed payload.\n * Wildcard overload (`\"*\"`): callback receives `(eventName, data)` — useful\n * for proxying all events to another emitter, as Fsm does with its bfsm.\n *\n * Wildcard listeners fire BEFORE named listeners. If a wildcard listener\n * removes a named listener via `off()`, the named listener still fires for\n * the current emit — Set iteration is not affected by concurrent deletes.\n *\n * Returns a `Subscription` whose `off()` removes this specific listener.\n */\n on<K extends keyof TEventMap & string>(\n event: K,\n cb: (data: TEventMap[K]) => void\n ): Subscription;\n on(event: \"*\", cb: WildcardListener): Subscription;\n on(event: string, cb: Listener): Subscription {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(cb);\n return {\n off: () => {\n set.delete(cb);\n },\n };\n }\n\n /**\n * Emit a named event, notifying wildcard listeners first, then named listeners.\n *\n * Wildcard listeners receive `(eventName, data)`; named listeners receive\n * only `data`. The firing order (wildcards before named) is intentional —\n * it lets relay listeners (like Fsm's bfsm proxy) observe all events\n * before specific subscribers react.\n */\n emit<K extends keyof TEventMap & string>(event: K, data: TEventMap[K]): void {\n const wildcards = this.listeners.get(\"*\");\n if (wildcards) {\n for (const cb of wildcards) {\n (cb as WildcardListener)(event, data);\n }\n }\n const named = this.listeners.get(event);\n if (named) {\n for (const cb of named) {\n cb(data);\n }\n }\n }\n\n /**\n * Remove all listeners (named and wildcard).\n *\n * Existing `Subscription` objects remain valid — their `off()` closures\n * hold a reference to the now-empty Set, so calling them is harmless.\n * Called by `dispose()` to prevent memory retention after an FSM shuts down.\n */\n clear(): void {\n this.listeners.clear();\n }\n}\n","// =============================================================================\n// types.ts — Shared type foundations for machina v6\n//\n// Pure type definitions. No runtime code. Every type here is consumed by\n// behavioral-fsm.ts, fsm.ts, or both. The factory functions (createFsm,\n// createBehavioralFsm) use these as building blocks for their own generics.\n//\n// =============================================================================\n\n// -----------------------------------------------------------------------------\n// Utility types for state/input name extraction\n//\n// These are the inference primitives that power machina's \"zero-ceremony\"\n// type safety. Given a states config object, they extract the state name\n// union and input name union as string literal types.\n// -----------------------------------------------------------------------------\n\n/**\n * Keys on a state object that have special meaning and are NOT input names.\n * Used by InputNamesOf to filter these out when collecting input names.\n */\ntype SpecialStateKeys = \"_onEnter\" | \"_onExit\" | \"_child\" | \"*\";\n\n/**\n * Extracts state names as a string literal union from a states config object.\n *\n * @example\n * ```ts\n * type S = StateNamesOf<{ green: {...}, yellow: {...}, red: {...} }>;\n * // => \"green\" | \"yellow\" | \"red\"\n * ```\n */\nexport type StateNamesOf<TStates> = keyof TStates & string;\n\n/**\n * Extracts input names as a string literal union from a states config object.\n * Collects all handler keys across ALL states, then strips out lifecycle hooks\n * and special keys (_onEnter, _onExit, _child, *).\n *\n * This is what flows into `handle(inputName)` to provide compile-time\n * validation of input names.\n *\n * @example\n * ```ts\n * type I = InputNamesOf<{\n * idle: { start: \"running\", reset: fn };\n * running: { pause: \"paused\", stop: \"idle\" };\n * }>;\n * // => \"start\" | \"reset\" | \"pause\" | \"stop\"\n * ```\n *\n * How it works:\n * 1. `{ [S in keyof TStates]: keyof TStates[S] & string }` — maps each state\n * to the union of its handler key names\n * 2. `[keyof TStates]` — collapses the mapped type into a flat union of ALL\n * handler keys across every state\n * 3. `Exclude<..., SpecialStateKeys>` — strips lifecycle/special keys\n */\nexport type InputNamesOf<TStates> = Exclude<\n { [S in keyof TStates]: keyof TStates[S] & string }[keyof TStates],\n SpecialStateKeys\n>;\n\n// -----------------------------------------------------------------------------\n// Handler argument object\n//\n// Every handler in machina receives this as its first argument. This replaces\n// the old v5 pattern of binding `this` to the FSM and passing the client as\n// a separate parameter. Benefits:\n// - Works with arrow functions (no `this` caveat)\n// - BehavioralFsm and Fsm handler signatures are identical\n// - Destructure only what you need: tick({ ctx }) { ... }\n// - Easy to mock in tests: just construct the object\n//\n// Handlers RETURN a state name to trigger a transition (or void/undefined\n// to stay in the current state). This replaces the imperative transition()\n// call and mirrors gen_fsm's {next_state, StateName, NewStateData} pattern.\n// -----------------------------------------------------------------------------\n\n/**\n * The single combined object passed to every handler.\n *\n * Handlers return a state name to transition, or void to stay put.\n * This replaces imperative `transition()` calls — closer to gen_fsm's\n * return-based model and symmetrical with string shorthand handlers.\n *\n * @typeParam TCtx - The context type. For Fsm this is the config-defined\n * context object. For BehavioralFsm this is the client object itself.\n * @typeParam TStateNames - String literal union of valid state names.\n * Defaults to `string` for loose usage; the factory functions narrow this\n * to the actual state names inferred from the config.\n *\n * @example\n * ```ts\n * // Conditional transition — return the target state:\n * timeout({ ctx }) {\n * if (ctx.tickCount >= 3) return \"yellow\";\n * }\n *\n * // Side effects without transition — return nothing:\n * tick({ ctx }) {\n * ctx.tickCount++;\n * }\n *\n * // In a catch-all — inputName tells you what was received:\n * \"*\"({ inputName }) {\n * console.log(`unhandled input: ${inputName}`);\n * }\n * ```\n */\nexport interface HandlerArgs<TCtx, TStateNames extends string = string> {\n /** The context (Fsm) or client object (BehavioralFsm) */\n ctx: TCtx;\n\n /**\n * The name of the input currently being handled.\n *\n * Typed as `string` rather than the inferred input union because:\n * 1. Inside a named handler you already know the input name\n * 2. In a catch-all (*) handler it could be anything\n * 3. Narrowing to the literal per-handler would require complex\n * mapped types for zero practical benefit\n */\n inputName: string;\n\n /**\n * Defer the current input for replay after a future transition.\n * Erlang's selective receive, in JS form.\n *\n * @example\n * ```ts\n * // Replay on the next transition to any state\n * defer();\n *\n * // Replay only when entering \"yellow\"\n * defer({ until: \"yellow\" });\n * ```\n */\n defer(opts?: { until: TStateNames }): void;\n\n /**\n * Emit a custom event through the FSM's emitter.\n * Built-in events (transitioning, transitioned, etc.) are emitted\n * automatically by the FSM engine — this is for user-defined events.\n */\n emit(eventName: string, data?: unknown): void;\n}\n\n// -----------------------------------------------------------------------------\n// Handler function type\n//\n// A single callable type for all handler forms: state input handlers,\n// lifecycle hooks (_onEnter, _onExit), and the catch-all (*).\n//\n// Handlers return a state name to trigger a transition, or void/undefined\n// to stay in the current state. This is the dynamic counterpart to string\n// shorthand — same concept (determine next state), two expressions.\n// -----------------------------------------------------------------------------\n\n/**\n * A function handler for state inputs, lifecycle hooks (_onEnter, _onExit),\n * and catch-all (*) handlers.\n *\n * **Return value determines transition:**\n * - Return a valid state name → FSM transitions to that state\n * - Return void/undefined → FSM stays in the current state\n *\n * This mirrors gen_fsm's `{next_state, StateName, NewStateData}` return.\n * Guards are just `if` statements. Actions are just code before the return.\n *\n * The `...extra` rest parameter captures additional arguments passed through\n * `handle(inputName, ...extraArgs)`. These are untyped (`unknown[]`) because\n * correlating per-input arg types with handle() call sites would require\n * prohibitively complex mapped types for minimal benefit.\n *\n * @example\n * ```ts\n * // Side effects only, no transition:\n * tick({ ctx }) { ctx.tickCount++; }\n *\n * // Conditional transition (replaces guard + target):\n * timeout({ ctx }) {\n * if (ctx.tickCount >= 3) return \"yellow\";\n * }\n *\n * // Unconditional transition with side effect (replaces action + target):\n * timeout({ ctx }) {\n * console.log(\"transitioning after\", ctx.tickCount, \"ticks\");\n * return \"yellow\";\n * }\n *\n * // Handler with extra args passed via handle(\"success\", responseData):\n * success({ ctx }, data) { ctx.result = data; }\n * ```\n */\nexport type HandlerFn<TCtx, TStateNames extends string = string> = (\n args: HandlerArgs<TCtx, TStateNames>,\n ...extra: unknown[]\n) => TStateNames | void;\n\n// -----------------------------------------------------------------------------\n// Handler definition forms\n//\n// A handler property on a state object is one of two things:\n// 1. A string — auto-transition shorthand: `timeout: \"yellow\"`\n// 2. A function — returns target state or void: `tick({ ctx }) { ... }`\n//\n// Two forms, one concept: \"a handler determines the next state.\"\n// String is the static case, function is the dynamic case.\n//\n// The FSM engine checks typeof at runtime: \"string\" → immediate transition,\n// \"function\" → call it and transition if it returns a state name.\n// -----------------------------------------------------------------------------\n\n/**\n * The union of valid handler definition forms for a state input.\n *\n * - `TStateNames` — string shorthand, auto-transitions to that state\n * - `HandlerFn` — function that returns a state name (transition) or void (stay)\n *\n * @example\n * ```ts\n * states: {\n * green: {\n * timeout: \"yellow\", // string shorthand\n * tick({ ctx }) { ctx.tickCount++; }, // function, no transition\n * emergency({ ctx }) { // function, conditional transition\n * if (ctx.severity > 5) return \"red\";\n * },\n * },\n * }\n * ```\n */\n/**\n * The union of valid handler definition forms for a state input.\n *\n * - `TStateNames` — string shorthand, auto-transitions to that state\n * - `HandlerFn` — function that returns a state name (transition) or void (stay)\n * - `MachinaInstance` — included to satisfy TypeScript's structural widening\n * when `ValidateStates` falls back to its constraint type. The per-key\n * restriction on `_child` is still enforced by `ValidateStates`; this\n * union member just prevents the inference engine from rejecting\n * `_child: childFsm` before the conditional mapping can evaluate it.\n *\n * @example\n * ```ts\n * states: {\n * green: {\n * timeout: \"yellow\", // string shorthand\n * tick({ ctx }) { ctx.tickCount++; }, // function, no transition\n * emergency({ ctx }) { // function, conditional transition\n * if (ctx.severity > 5) return \"red\";\n * },\n * },\n * }\n * ```\n */\nexport type HandlerDef<TCtx, TStateNames extends string = string> =\n | TStateNames\n | HandlerFn<TCtx, TStateNames>\n | MachinaInstance;\n\n// -----------------------------------------------------------------------------\n// State validation\n//\n// ValidateStates is the mapped type that makes config-inferred type safety\n// work. It re-maps the user's states object, constraining each property to\n// the correct type based on its key name. This is where typos in string\n// shorthand targets get caught at compile time.\n//\n// The key trick: `keyof TStates & string` is self-referential — it derives\n// the state name union FROM the same states object being validated. So when\n// a user writes `timeout: \"yellw\"`, TypeScript checks \"yellw\" against the\n// actual state keys and reports the error.\n// -----------------------------------------------------------------------------\n\n/**\n * Validates and constrains the states object at the type level.\n *\n * This is a mapped type that walks every state and every property within\n * each state, assigning the correct expected type based on the property key:\n *\n * | Key | Expected type |\n * |------------------|-----------------------------------------|\n * | `_onEnter` | HandlerFn (lifecycle hook) |\n * | `_onExit` | HandlerFn (lifecycle hook) |\n * | `_child` | MachinaInstance (Fsm or BehavioralFsm) |\n * | `*` | HandlerFn (catch-all) |\n * | anything else | HandlerDef (string or fn) |\n *\n * @typeParam TCtx - Context/client type, flows into handler signatures\n * @typeParam TStates - The literal states object type captured by the\n * factory function's generic parameter\n */\nexport type ValidateStates<TCtx, TStates extends Record<string, Record<string, unknown>>> = {\n [S in keyof TStates]: {\n [K in keyof TStates[S]]: K extends \"_onEnter\" | \"_onExit\"\n ? HandlerFn<TCtx, keyof TStates & string>\n : K extends \"_child\"\n ? MachinaInstance\n : K extends \"*\"\n ? HandlerFn<TCtx, keyof TStates & string>\n : HandlerDef<TCtx, keyof TStates & string>;\n };\n};\n\n// -----------------------------------------------------------------------------\n// FSM configuration\n//\n// The config shape passed to createFsm() and createBehavioralFsm().\n// Both factory functions share this type — the difference is in how TCtx\n// is resolved:\n// - createFsm: TCtx inferred from the `context` property\n// - createBehavioralFsm: TCtx is the client type (context property ignored)\n// -----------------------------------------------------------------------------\n\n/**\n * Configuration object for creating an FSM.\n *\n * @typeParam TCtx - The context type (Fsm) or client type (BehavioralFsm).\n * For Fsm, this is inferred from the `context` property. For BehavioralFsm,\n * it's the client object type provided explicitly or as a generic parameter.\n *\n * @typeParam TStates - The literal states object type. Captured by the factory\n * function's generic parameter (ideally with `const` to preserve string\n * literal types). Defaults to a loose record for unconstrained usage.\n *\n * @example\n * ```ts\n * // TCtx inferred as { tickCount: number }, TStates inferred from states object:\n * createFsm({\n * id: \"traffic-light\",\n * initialState: \"green\", // validated against state keys\n * context: { tickCount: 0 }, // inference site for TCtx\n * states: {\n * green: { timeout: \"yellow\" }, // \"yellow\" validated against state keys\n * yellow: { timeout: \"red\" },\n * red: { timeout: \"green\" },\n * },\n * });\n * ```\n */\nexport interface FsmConfig<\n TCtx,\n TStates extends Record<string, Record<string, unknown>> = Record<\n string,\n Record<string, unknown>\n >,\n> {\n /** Unique identifier for this FSM */\n id: string;\n\n /**\n * The state to start in. Must be a key of `states`.\n *\n * Wrapped in NoInfer to prevent TypeScript from using this value as an\n * inference site for TStates. Without it, `initialState: \"green\"` could\n * narrow TStates to only have a \"green\" key. We want inference to come\n * exclusively from the `states` property.\n */\n initialState: NoInfer<keyof TStates & string>;\n\n /**\n * Initial context data. The type is inferred from this value and flows\n * into every handler's `ctx` parameter.\n *\n * For BehavioralFsm, this property is optional and serves only as a\n * type constraint — the client object IS the context.\n */\n context?: TCtx;\n\n /** State definitions. Keys become the state name union. */\n states: ValidateStates<TCtx, TStates>;\n}\n\n// -----------------------------------------------------------------------------\n// Type discriminant — runtime distinguishing of Fsm vs BehavioralFsm\n//\n// Used internally by the ChildLink adapter to normalize delegation calls\n// without introducing circular imports. Not exported to users.\n// -----------------------------------------------------------------------------\n\n/**\n * Symbol used as a property key to identify machina FSM instances at runtime.\n * Each class stamps itself with a MachinaType value so the ChildLink adapter\n * can dispatch handle()/canHandle()/reset() correctly without circular imports.\n */\nexport const MACHINA_TYPE = Symbol(\"machina.type\");\n\n/**\n * Discriminant values stamped onto FSM instances via `MACHINA_TYPE`.\n * Used by the `ChildLink` adapter to dispatch calls correctly without\n * importing either class directly (which would create circular dependencies).\n */\nexport type MachinaType = \"Fsm\" | \"BehavioralFsm\";\n\n/** Structural type matching any machina FSM instance (Fsm or BehavioralFsm) */\nexport type MachinaInstance = { readonly [MACHINA_TYPE]: MachinaType };\n\n// -----------------------------------------------------------------------------\n// ChildLink — internal adapter interface\n//\n// Normalizes Fsm (no client arg) vs BehavioralFsm (client arg) delegation.\n// The BehavioralFsm engine uses this internally; never exported to users.\n// -----------------------------------------------------------------------------\n\n/**\n * Internal adapter that wraps either an Fsm or BehavioralFsm child,\n * presenting a uniform API for parent-initiated delegation.\n */\nexport interface ChildLink {\n /** Check if the child's current state can handle this input */\n canHandle(client: object, inputName: string): boolean;\n /** Dispatch the input to the child */\n handle(client: object, inputName: string, ...args: unknown[]): void;\n /** Reset the child to its initialState */\n reset(client: object): void;\n /** Subscribe to all child events (wildcard). Returns unsubscribe fn. */\n onAny(callback: (eventName: string, data: unknown) => void): { off(): void };\n /** The child FSM's compositeState for the given client */\n compositeState(client: object): string;\n /** Dispose the child FSM */\n dispose(): void;\n}\n\n/**\n * Options for FSM disposal.\n */\nexport interface DisposeOptions {\n /**\n * When true, child FSMs declared via _child are NOT disposed.\n * Default: false (children ARE disposed along with the parent).\n */\n preserveChildren?: boolean;\n}\n\n// -----------------------------------------------------------------------------\n// Built-in FSM event maps\n//\n// machina FSMs emit lifecycle events that external code can subscribe to.\n// These types define the payload shape for each built-in event. Custom\n// events (emitted via emit() in handlers) are untyped — they flow through\n// the emitter's wildcard path with `unknown` payloads.\n//\n// Event naming follows a grammatical pattern:\n// - Present participle = \"about to happen\": transitioning, handling\n// - Past participle = \"just happened\": transitioned, handled\n// -----------------------------------------------------------------------------\n\n/**\n * Built-in event map for Fsm instances.\n * Payloads do NOT include a client reference (Fsm is its own client).\n *\n * @typeParam TStateNames - The state name union, flows into transition\n * event payloads so fromState/toState are narrowed to actual state names.\n */\nexport interface FsmEventMap<TStateNames extends string = string> {\n /** Fired just before a state transition occurs */\n transitioning: { fromState: TStateNames; toState: TStateNames };\n\n /** Fired just after a state transition completes */\n transitioned: { fromState: TStateNames; toState: TStateNames };\n\n /** Fired when an input is about to be dispatched to a handler */\n handling: { inputName: string };\n\n /** Fired after an input has been successfully handled */\n handled: { inputName: string };\n\n /** Fired when an input has no matching handler in the current state */\n nohandler: { inputName: string; args: unknown[] };\n\n /** Fired when a transition targets a state that doesn't exist */\n invalidstate: { stateName: string };\n\n /** Fired when an input is deferred for later replay */\n deferred: { inputName: string };\n}\n\n/**\n * Built-in event map for BehavioralFsm instances.\n * Every payload is intersected with `{ client: TClient }` so subscribers\n * can identify which client the event pertains to.\n *\n * @typeParam TClient - The client object type\n * @typeParam TStateNames - The state name union\n */\nexport type BehavioralFsmEventMap<TClient, TStateNames extends string = string> = {\n [K in keyof FsmEventMap<TStateNames>]: FsmEventMap<TStateNames>[K] & {\n client: TClient;\n };\n};\n\n// -----------------------------------------------------------------------------\n// Internal per-client metadata\n//\n// BehavioralFsm tracks per-client state in a WeakMap<TClient, ClientMeta>.\n// When the client object is garbage collected, its metadata goes with it.\n// This replaces v5's __machina__ property stamping on client objects.\n// -----------------------------------------------------------------------------\n\n/**\n * A deferred input queue entry. Created when a handler calls\n * deferUntilTransition() — the input is stored here and replayed\n * after a future state transition.\n */\nexport interface DeferredInput {\n /** The input name that was deferred */\n inputName: string;\n\n /** The original arguments passed to handle() for this input */\n args: unknown[];\n\n /**\n * If set, only replay when entering this specific state.\n * When undefined, replays on the next transition to any state.\n */\n untilState?: string;\n}\n\n/**\n * Internal metadata stored per client in the WeakMap.\n * This is the bookkeeping the FSM engine needs — NOT user-facing data.\n *\n * @typeParam TStateNames - The state name union. The `state` field is\n * typed to this so internal code gets compile-time validation.\n */\nexport interface ClientMeta<TStateNames extends string = string> {\n /** The client's current state */\n state: TStateNames;\n\n /** Queue of inputs deferred for later replay */\n deferredQueue: DeferredInput[];\n\n /**\n * The args array for the currently-executing handle() call.\n * Captured before handler dispatch so that deferUntilTransition()\n * can snapshot them for later replay. Cleared after dispatch.\n */\n currentActionArgs?: unknown[];\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n// =============================================================================\n// behavioral-fsm.ts — The core FSM engine\n//\n// BehavioralFsm defines behavior (states + transitions) while tracking state\n// externally per-client via a WeakMap. One FSM definition can drive any number\n// of independent client objects.\n//\n// =============================================================================\n\nimport { Emitter, type Subscription } from \"./emitter\";\nimport {\n MACHINA_TYPE,\n type FsmConfig,\n type StateNamesOf,\n type InputNamesOf,\n type HandlerArgs,\n type HandlerFn,\n type BehavioralFsmEventMap,\n type ClientMeta,\n type DeferredInput,\n type ChildLink,\n type DisposeOptions,\n} from \"./types\";\n\n// Safety valve for _onEnter → transition loops. Instance-level counter works\n// because the engine is synchronous — only one transition chain is active at\n// a time. Throws on overflow, then resets so the FSM isn't permanently broken.\nconst MAX_TRANSITION_DEPTH = 20;\n\n/**\n * Defines FSM behavior (states + transitions) while tracking per-client state\n * in a `WeakMap`. A single `BehavioralFsm` instance can drive any number of\n * independent client objects simultaneously — each gets its own state,\n * deferred queue, and lifecycle.\n *\n * Prefer `createBehavioralFsm()` over constructing this directly — the factory\n * infers all generic parameters from the config object.\n *\n * All public methods silently no-op after `dispose()` is called.\n *\n * @typeParam TClient - The client object type. Must be an object (non-primitive)\n * so it can serve as a WeakMap key.\n * @typeParam TStateNames - String literal union of valid state names.\n * @typeParam TInputNames - String literal union of valid input names.\n */\nexport class BehavioralFsm<\n TClient extends object,\n TStateNames extends string,\n TInputNames extends string,\n> {\n readonly id: string;\n readonly initialState: TStateNames;\n // Type discriminant — lets ChildLink adapter identify this at runtime\n readonly [MACHINA_TYPE] = \"BehavioralFsm\" as const;\n private readonly states: Record<string, Record<string, unknown>>;\n private readonly emitter = new Emitter<BehavioralFsmEventMap<TClient, TStateNames>>();\n private readonly clients = new WeakMap<TClient, ClientMeta<TStateNames>>();\n // Tracks all initialized clients so the Fsm-child nohandler listener can\n // find which clients need bubbling (Fsm events have no client in payload).\n // WeakRef prevents retention; clients clean themselves up naturally.\n private readonly knownClients: Set<WeakRef<TClient>> = new Set();\n // Subscriptions to child FSM wildcard events, keyed by state name.\n // Set up once during construction, torn down in dispose().\n private readonly childSubscriptions: Array<{ off(): void }> = [];\n private disposed = false;\n private transitionDepth = 0;\n\n constructor(config: FsmConfig<TClient, Record<string, Record<string, unknown>>>) {\n this.id = config.id;\n this.initialState = config.initialState as TStateNames;\n this.states = config.states as Record<string, Record<string, unknown>>;\n this.wrapChildLinks();\n this.setupChildSubscriptions();\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /**\n * Dispatch an input to the given client's current state handler.\n *\n * Delegation order: if the current state has a `_child` FSM that can\n * handle the input, it is dispatched there. If the child emits `nohandler`,\n * the input bubbles up to this FSM's local handler. If no handler exists\n * here either, `nohandler` is emitted on this FSM's emitter.\n *\n * No-ops silently when disposed.\n */\n handle(client: TClient, inputName: TInputNames, ...args: unknown[]): void {\n if (this.disposed) {\n return;\n }\n\n const meta = this.getOrCreateClientMeta(client);\n meta.currentActionArgs = args;\n\n // Delegation: if the current state has a _child and the child can\n // handle this input, send it there. Otherwise fall through to\n // handleLocally(). No nohandler emitted for the delegation path.\n const stateObj = this.states[meta.state];\n const childLink = stateObj?._child as ChildLink | undefined;\n if (childLink) {\n if (childLink.canHandle(client, inputName)) {\n try {\n childLink.handle(client, inputName, ...args);\n } finally {\n // Clear args even if the child handler throws — stale args\n // on meta would corrupt subsequent handle() calls for this client.\n meta.currentActionArgs = undefined;\n }\n return;\n }\n }\n\n this.handleLocally(client, inputName, args, meta);\n }\n\n /**\n * Returns true if the client's current state has a handler for `inputName`\n * (or a catch-all `\"*\"` handler). Does NOT initialize the client — no\n * `_onEnter`, no events, no side effects. Unseen clients are treated as\n * if they were already in `initialState`. Returns false when disposed.\n */\n canHandle(client: TClient, inputName: string): boolean {\n if (this.disposed) {\n return false;\n }\n // Does NOT initialize the client. We want a pure state check —\n // no side effects, no _onEnter, no events. Use initialState as the\n // fallback for unseen clients (they'd start there anyway).\n const state = this.clients.get(client)?.state ?? this.initialState;\n const stateObj = this.states[state];\n return !!(stateObj?.[inputName] ?? stateObj?.[\"*\"]);\n }\n\n /**\n * Transition the client back to `initialState`, firing `_onEnter` and\n * lifecycle events as if entering it fresh. No-ops when disposed.\n */\n reset(client: TClient): void {\n if (this.disposed) {\n return;\n }\n this.transition(client, this.initialState);\n }\n\n /**\n * Returns the client's current state, or `undefined` if the client has\n * never been initialized (i.e. `handle()`, `transition()`, or `reset()`\n * have never been called for it). Does NOT trigger initialization.\n */\n currentState(client: TClient): TStateNames | undefined {\n // Intentionally uses WeakMap.get() directly — does NOT trigger initialization.\n // Returns undefined for clients the FSM has never seen.\n return this.clients.get(client)?.state;\n }\n\n /**\n * Directly transition `client` to `toState`, running the full lifecycle:\n * `_onExit` for the current state → `transitioning` event → update state →\n * `_onEnter` for new state → `transitioned` event → child reset → deferred\n * queue replay → bounce (if `_onEnter` returned a state name).\n *\n * Same-state transitions are silently ignored. Transitions to unknown state\n * names emit `invalidstate` instead of throwing. Throws if the transition\n * depth exceeds `MAX_TRANSITION_DEPTH` (likely an `_onEnter` → transition loop).\n *\n * No-ops when disposed.\n */\n transition(client: TClient, toState: TStateNames): void {\n if (this.disposed) {\n return;\n }\n\n const meta = this.getOrCreateClientMeta(client);\n const fromState = meta.state;\n\n if (toState === fromState) {\n return;\n }\n\n if (!(toState in this.states)) {\n this.emitter.emit(\"invalidstate\", { stateName: toState, client });\n return;\n }\n\n this.transitionDepth++;\n if (this.transitionDepth > MAX_TRANSITION_DEPTH) {\n this.transitionDepth = 0;\n throw new Error(\n `Max transition depth (${MAX_TRANSITION_DEPTH}) exceeded in FSM \"${this.id}\". ` +\n \"Likely an infinite _onEnter → transition loop.\"\n );\n }\n\n try {\n const curStateObj = this.states[fromState];\n const newStateObj = this.states[toState];\n\n // _onExit for current state. The optional chain handles two cases:\n // 1. First-time initialization — fromState is `undefined as TStateNames`,\n // so this.states[undefined] is undefined; no exit hook to run.\n // 2. States that simply have no _onExit defined — equally valid, equally ignored.\n if (curStateObj?._onExit && typeof curStateObj._onExit === \"function\") {\n const exitArgs = this.buildHandlerArgs(client, \"\", meta);\n (curStateObj._onExit as HandlerFn<TClient, TStateNames>)(exitArgs);\n // _onExit return value is intentionally ignored — you're already leaving\n }\n\n meta.state = toState;\n\n const payload = { fromState, toState, client };\n this.emitter.emit(\"transitioning\", payload);\n\n // _onEnter for new state — return value is a bounce target\n let bounceTarget: TStateNames | void = undefined;\n if (newStateObj?._onEnter && typeof newStateObj._onEnter === \"function\") {\n const enterArgs = this.buildHandlerArgs(client, \"\", meta);\n bounceTarget = (newStateObj._onEnter as HandlerFn<TClient, TStateNames>)(enterArgs);\n }\n\n this.emitter.emit(\"transitioned\", payload);\n\n // Reset child FSM after entering the new state. This ensures the child\n // always starts from its initialState when the parent enters. Happens\n // after _onEnter and transitioned but before deferred queue processing,\n // so deferred inputs see the post-reset child state.\n const childLink = newStateObj?._child as ChildLink | undefined;\n if (childLink) {\n childLink.reset(client);\n }\n\n // Replay deferred inputs targeting this state\n this.processQueue(client, meta);\n\n // Bounce: _onEnter returned a state name. Only fires if we're still\n // in the state _onEnter belongs to — a deferred replay might have\n // already moved us elsewhere.\n if (typeof bounceTarget === \"string\" && meta.state === toState) {\n this.transition(client, bounceTarget);\n }\n } finally {\n this.transitionDepth--;\n }\n }\n\n /**\n * Returns the client's state as a dot-delimited path including any active\n * child FSM states (e.g. `\"active.connecting.retrying\"`). Returns just the\n * current state name when no child is active. Returns `\"\"` for clients that\n * have never been initialized (unlike `currentState()` which returns `undefined`).\n */\n compositeState(client: TClient): string {\n const meta = this.clients.get(client);\n if (!meta) {\n // Returns \"\" (not undefined like currentState()) because compositeState\n // produces dot-delimited paths (\"stateA.child.grandchild\") for hierarchies.\n // Empty string is the correct \"nothing to report\" sentinel for string concat.\n return \"\";\n }\n\n const stateObj = this.states[meta.state];\n const childLink = stateObj?._child as ChildLink | undefined;\n if (childLink) {\n const childComposite = childLink.compositeState(client);\n if (childComposite) {\n return `${meta.state}.${childComposite}`;\n }\n }\n\n return meta.state;\n }\n\n /**\n * Subscribe to a built-in lifecycle event or the wildcard.\n *\n * Named overload: typed payload includes `{ client: TClient }` so you can\n * identify which client the event pertains to. Wildcard (`\"*\"`) receives\n * `(eventName, data)` for every event. Returns a no-op `Subscription`\n * when disposed.\n */\n on<K extends keyof BehavioralFsmEventMap<TClient, TStateNames> & string>(\n eventName: K,\n callback: (data: BehavioralFsmEventMap<TClient, TStateNames>[K]) => void\n ): Subscription;\n on(eventName: \"*\", callback: (eventName: string, data: unknown) => void): Subscription;\n on(eventName: string, callback: (...args: any[]) => void): Subscription {\n if (this.disposed) {\n return { off() {} };\n }\n return this.emitter.on(eventName as any, callback as any);\n }\n\n /**\n * Emit a custom event through the FSM. Built-in lifecycle events are\n * emitted automatically — this is for user-defined events from handlers.\n * No-ops when disposed.\n */\n emit(eventName: string, data?: unknown): void {\n if (this.disposed) {\n return;\n }\n // Public emit is for user-defined custom events. Built-in events are\n // emitted automatically by the engine.\n (this.emitter as any).emit(eventName, data);\n }\n\n /**\n * Permanently shut down this FSM. Irreversible — all subsequent method\n * calls become silent no-ops. Tears down child subscriptions, clears all\n * listeners, and cascades disposal to child FSMs (unless `preserveChildren`\n * is set). The same child appearing in multiple states is disposed once.\n */\n dispose(options?: DisposeOptions): void {\n this.disposed = true;\n // Tear down all child wildcard subscriptions before clearing our emitter.\n for (const sub of this.childSubscriptions) {\n sub.off();\n }\n // Cascade disposal to child FSMs unless explicitly opted out.\n // Deduplication via Set handles the same child appearing in multiple states.\n if (!options?.preserveChildren) {\n const seen = new Set<ChildLink>();\n for (const stateName of Object.keys(this.states)) {\n const childLink = this.states[stateName]?._child as ChildLink | undefined;\n if (childLink && !seen.has(childLink)) {\n seen.add(childLink);\n childLink.dispose();\n }\n }\n }\n this.emitter.clear();\n // WeakMap entries are GC'd naturally — can't iterate, don't need to\n }\n\n // -------------------------------------------------------------------------\n // Internal\n // -------------------------------------------------------------------------\n\n /**\n * Walks all states at construction time, detects raw FSM instances assigned\n * to _child, and wraps them into ChildLink adapters via createChildLink().\n * Must run BEFORE setupChildSubscriptions() so the subscriptions see\n * ChildLink objects, not raw FSM instances.\n */\n private wrapChildLinks(): void {\n for (const stateName of Object.keys(this.states)) {\n const stateObj = this.states[stateName];\n const rawChild = stateObj?._child;\n if (!rawChild) {\n continue;\n }\n if (typeof rawChild !== \"object\") {\n throw new Error(\n `State \"${stateName}\"._child: expected an Fsm or BehavioralFsm instance, got ${String(rawChild)}`\n );\n }\n if (!(MACHINA_TYPE in rawChild)) {\n throw new Error(\n `State \"${stateName}\"._child: expected an Fsm or BehavioralFsm instance, got a plain object`\n );\n }\n stateObj._child = createChildLink(rawChild);\n }\n }\n\n /**\n * Walks all states at construction time, finds states with _child, and\n * subscribes once to each unique child's wildcard events. Subscriptions are\n * stored for cleanup in dispose(). We deduplicate by child reference to\n * avoid double-subscribing when the same child appears in multiple states.\n */\n private setupChildSubscriptions(): void {\n const seenChildren = new Set<ChildLink>();\n\n for (const stateName of Object.keys(this.states)) {\n const stateObj = this.states[stateName];\n const childLink = stateObj?._child as ChildLink | undefined;\n\n if (!childLink || seenChildren.has(childLink)) {\n continue;\n }\n seenChildren.add(childLink);\n\n // Subscribe to all child events. We use the wildcard so we get\n // every event type in a single listener.\n const sub = childLink.onAny((eventName: string, data: unknown) => {\n // Nohandler from child = child couldn't handle the input.\n // Re-dispatch to parent via handleLocally() (not handle(), to\n // avoid re-entering the delegation path and looping).\n if (eventName === \"nohandler\") {\n const payload = data as {\n inputName: string;\n args: unknown[];\n client?: TClient;\n };\n\n if (payload.client !== undefined) {\n // BehavioralFsm child: payload includes the client.\n // Filter stale events: only bubble if this client is\n // currently in a state that has this child.\n this.bubbleNohandler(\n payload.client,\n childLink,\n payload.inputName,\n payload.args ?? []\n );\n } else {\n // Fsm child: single-client, no client in payload.\n // Walk all known clients and bubble for any that are\n // currently in a state with this child.\n for (const ref of this.knownClients) {\n const client = ref.deref();\n if (client === undefined) {\n // GC'd — clean up the dead ref\n this.knownClients.delete(ref);\n continue;\n }\n this.bubbleNohandler(\n client,\n childLink,\n payload.inputName,\n payload.args ?? []\n );\n }\n }\n return;\n }\n\n // All other child events: only relay if at least one client\n // is currently in a parent state that delegates to this child.\n // Prevents stale events from leaking through the parent emitter\n // when the child fires during construction or after the parent\n // has moved to a different state.\n const payload = data as Record<string, unknown> | undefined;\n if (payload && typeof payload === \"object\" && \"client\" in payload) {\n // BehavioralFsm child: payload includes the client.\n if (this.isChildActiveForClient(payload.client as TClient, childLink)) {\n (this.emitter as any).emit(eventName, data);\n }\n } else {\n // Fsm child (or payload without client): walk known clients,\n // relay if ANY client is currently in a state with this child.\n for (const ref of this.knownClients) {\n const client = ref.deref();\n if (!client) {\n this.knownClients.delete(ref);\n continue;\n }\n if (this.isChildActiveForClient(client, childLink)) {\n (this.emitter as any).emit(eventName, data);\n break; // one match is enough — relay once\n }\n }\n }\n });\n\n this.childSubscriptions.push(sub);\n }\n }\n\n /**\n * Bubbles a child nohandler to the parent for the given client.\n * Only fires if the client is currently in a state that has this childLink.\n * Extracted from the lambda in setupChildSubscriptions to keep it readable.\n */\n private bubbleNohandler(\n client: TClient,\n childLink: ChildLink,\n inputName: string,\n args: unknown[]\n ): void {\n if (!this.isChildActiveForClient(client, childLink)) {\n return;\n }\n // Safe to assert — isChildActiveForClient confirmed meta exists AND\n // the client is in a state whose _child matches childLink.\n const meta = this.clients.get(client)!;\n meta.currentActionArgs = args;\n this.handleLocally(client, inputName as TInputNames, args, meta);\n }\n\n /**\n * Returns true if the given client is currently in a parent state whose\n * _child is the specified childLink. Returns false if the client has no\n * meta (never initialized) or is in a state with a different (or no) child.\n */\n private isChildActiveForClient(client: TClient, childLink: ChildLink): boolean {\n const meta = this.clients.get(client);\n if (!meta) {\n return false;\n }\n const parentStateObj = this.states[meta.state];\n return parentStateObj?._child === childLink;\n }\n\n /**\n * The inner handler dispatch — no delegation, no initialization side effects\n * beyond what getOrCreateClientMeta already did. Called by handle() after\n * the delegation check, and by the nohandler child listener for bubbling.\n */\n private handleLocally(\n client: TClient,\n inputName: TInputNames | string,\n args: unknown[],\n meta: ClientMeta<TStateNames>\n ): void {\n const stateObj = this.states[meta.state];\n const handler = stateObj?.[inputName] ?? stateObj?.[\"*\"];\n\n if (!handler) {\n this.emitter.emit(\"nohandler\", { inputName: inputName as string, args, client });\n meta.currentActionArgs = undefined;\n return;\n }\n\n try {\n this.emitter.emit(\"handling\", { inputName: inputName as string, client });\n\n const handlerArgs = this.buildHandlerArgs(client, inputName as string, meta);\n let targetState: TStateNames | void = undefined;\n\n if (typeof handler === \"string\") {\n targetState = handler as TStateNames;\n } else if (typeof handler === \"function\") {\n targetState = (handler as HandlerFn<TClient, TStateNames>)(handlerArgs, ...args);\n }\n\n this.emitter.emit(\"handled\", { inputName: inputName as string, client });\n\n if (typeof targetState === \"string\") {\n this.transition(client, targetState);\n }\n } finally {\n meta.currentActionArgs = undefined;\n }\n }\n\n private getOrCreateClientMeta(client: TClient): ClientMeta<TStateNames> {\n let meta = this.clients.get(client);\n if (meta) {\n return meta;\n }\n\n // State starts as undefined so transition()'s same-state check passes\n // (undefined !== initialState). The transition immediately sets state\n // to initialState before returning, so any code that triggered\n // initialization (handle/reset/transition) sees the correct state.\n // currentState() for uninitialized clients still returns undefined —\n // it reads the WeakMap directly without triggering init.\n meta = {\n state: undefined as unknown as TStateNames,\n deferredQueue: [],\n };\n this.clients.set(client, meta);\n // Track this client weakly so Fsm-child nohandler listeners can find\n // which clients to bubble to (Fsm events carry no client in payload).\n this.knownClients.add(new WeakRef(client));\n\n // v5-style active initialization: full transition into initialState.\n // Fires _onEnter, emits transitioning/transitioned, processes deferred\n // queue. _onExit is skipped (no state object for undefined).\n this.transition(client, this.initialState);\n\n return meta;\n }\n\n private buildHandlerArgs(\n client: TClient,\n inputName: string,\n meta: ClientMeta<TStateNames>\n ): HandlerArgs<TClient, TStateNames> {\n return {\n ctx: client,\n inputName,\n defer: (opts?: { until: TStateNames }) => {\n // Only meaningful inside a handle() context where currentActionArgs\n // is set. No-ops during lifecycle hooks (matches v5 behavior).\n if (!meta.currentActionArgs) {\n return;\n }\n const deferred: DeferredInput = {\n inputName,\n args: [...meta.currentActionArgs],\n untilState: opts?.until,\n };\n meta.deferredQueue.push(deferred);\n this.emitter.emit(\"deferred\", { inputName, client });\n },\n emit: (evtName: string, evtData?: unknown) => {\n (this.emitter as any).emit(evtName, evtData);\n },\n };\n }\n\n private processQueue(client: TClient, meta: ClientMeta<TStateNames>): void {\n const toReplay: DeferredInput[] = [];\n const remaining: DeferredInput[] = [];\n\n for (const item of meta.deferredQueue) {\n if (item.untilState === undefined || item.untilState === meta.state) {\n toReplay.push(item);\n } else {\n remaining.push(item);\n }\n }\n\n meta.deferredQueue = remaining;\n\n for (const item of toReplay) {\n this.handle(client, item.inputName as TInputNames, ...item.args);\n }\n }\n}\n\n// -----------------------------------------------------------------------------\n// createChildLink — internal adapter factory\n//\n// Wraps either an Fsm or BehavioralFsm child and presents a uniform interface\n// for the parent engine to call. Never exported. The MACHINA_TYPE discriminant\n// tells us which call shape to use.\n// -----------------------------------------------------------------------------\n\n/**\n * Internal factory called by `wrapChildLinks()` during construction.\n * Wraps a raw Fsm or BehavioralFsm instance in a uniform `ChildLink` adapter\n * so the parent engine doesn't need to know which type it's talking to.\n *\n * Users never call this directly — they assign an FSM instance to `_child`\n * in their state config and `wrapChildLinks()` handles the wrapping.\n */\nfunction createChildLink(child: any): ChildLink {\n if (!child || typeof child !== \"object\") {\n throw new Error(\n `createChildLink: expected an Fsm or BehavioralFsm instance, got ${String(child)}`\n );\n }\n const childType: string = child[MACHINA_TYPE];\n\n if (childType === \"BehavioralFsm\") {\n return {\n canHandle(client: object, inputName: string): boolean {\n return child.canHandle(client, inputName);\n },\n handle(client: object, inputName: string, ...args: unknown[]): void {\n child.handle(client, inputName, ...args);\n },\n reset(client: object): void {\n child.transition(client, child.initialState);\n },\n onAny(callback: (eventName: string, data: unknown) => void): { off(): void } {\n return child.on(\"*\", callback);\n },\n compositeState(client: object): string {\n return child.compositeState(client);\n },\n dispose(): void {\n child.dispose();\n },\n };\n }\n\n if (childType === \"Fsm\") {\n return {\n canHandle(_client: object, inputName: string): boolean {\n return child.canHandle(inputName);\n },\n handle(_client: object, inputName: string, ...args: unknown[]): void {\n child.handle(inputName, ...args);\n },\n reset(_client: object): void {\n child.reset();\n },\n onAny(callback: (eventName: string, data: unknown) => void): { off(): void } {\n return child.on(\"*\", callback);\n },\n compositeState(_client: object): string {\n return child.compositeState();\n },\n dispose(): void {\n child.dispose();\n },\n };\n }\n\n throw new Error(\n `createChildLink: expected an Fsm or BehavioralFsm instance, ` +\n `got [MACHINA_TYPE] = ${String(childType ?? \"undefined\")}`\n );\n}\n\n/**\n * Create a behavioral FSM (one definition, many clients) from a config object.\n *\n * Generic parameters are inferred automatically:\n * - `TClient` must be provided explicitly as a type parameter (it can't be\n * inferred from the config since no `context` property exists at the FSM level).\n * - `TStates` is captured with `const` inference to preserve string literal\n * types, enabling compile-time validation of transition targets and `handle()`\n * input names.\n *\n * State names, input names, and all handler signatures derive from `TStates`.\n *\n * @example\n * ```ts\n * interface Connection { url: string; retries: number; }\n *\n * const connFsm = createBehavioralFsm<Connection>({\n * id: \"connectivity\",\n * initialState: \"disconnected\",\n * states: {\n * disconnected: { connect: \"connecting\" },\n * connecting: { connected: \"online\", failed: \"disconnected\" },\n * online: { disconnect: \"disconnected\" },\n * },\n * });\n *\n * const conn = { url: \"wss://example.com\", retries: 0 };\n * connFsm.handle(conn, \"connect\");\n * ```\n */\nexport function createBehavioralFsm<\n TClient extends object,\n const TStates extends Record<string, Record<string, unknown>>,\n>(\n config: FsmConfig<TClient, TStates>\n): BehavioralFsm<TClient, StateNamesOf<TStates>, InputNamesOf<TStates>> {\n return new BehavioralFsm(config as FsmConfig<TClient, Record<string, Record<string, unknown>>>);\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n// =============================================================================\n// fsm.ts — Single-client FSM convenience wrapper\n//\n// Fsm wraps a BehavioralFsm, using the config's `context` object as the\n// internal client. Same engine, same lifecycle, but without the client\n// parameter on every method call.\n//\n// =============================================================================\n\nimport { Emitter, type Subscription } from \"./emitter\";\nimport { BehavioralFsm } from \"./behavioral-fsm\";\nimport {\n MACHINA_TYPE,\n type FsmConfig,\n type FsmEventMap,\n type StateNamesOf,\n type InputNamesOf,\n type DisposeOptions,\n} from \"./types\";\n\n/**\n * Single-client FSM. Wraps a BehavioralFsm and uses the config's `context`\n * object as the implicit client, so callers never pass a client argument.\n *\n * Prefer `createFsm()` over constructing this directly — the factory infers\n * all generic parameters from the config object.\n *\n * All public methods silently no-op after `dispose()` is called.\n *\n * @typeParam TCtx - The context type, inferred from `config.context`.\n * @typeParam TStateNames - String literal union of valid state names.\n * @typeParam TInputNames - String literal union of valid input names.\n */\nexport class Fsm<TCtx extends object, TStateNames extends string, TInputNames extends string> {\n readonly id: string;\n // Type discriminant — lets ChildLink adapter identify this at runtime\n readonly [MACHINA_TYPE] = \"Fsm\" as const;\n private readonly bfsm: BehavioralFsm<TCtx, TStateNames, TInputNames>;\n private readonly context: TCtx;\n // FsmEventMap is an interface, which lacks the implicit index signature\n // that Emitter's Record<string, unknown> constraint needs. The mapped type\n // BehavioralFsmEventMap doesn't have this issue. Intersect with Record to\n // satisfy the constraint while preserving typed event signatures.\n private readonly emitter = new Emitter<FsmEventMap<TStateNames> & Record<string, unknown>>();\n private disposed = false;\n\n constructor(config: FsmConfig<TCtx, Record<string, Record<string, unknown>>>) {\n this.id = config.id;\n this.context = (config.context ?? {}) as TCtx;\n\n this.bfsm = new BehavioralFsm(config);\n\n // Relay events from BehavioralFsm → Fsm emitter, stripping the\n // `client` field from built-in event payloads. Custom events (from\n // handler emit() or public emit()) pass through unchanged.\n this.bfsm.on(\"*\", (eventName: string, data: unknown) => {\n if (data && typeof data === \"object\" && \"client\" in data) {\n const { client: _, ...payload } = data as Record<string, unknown>;\n (this.emitter as any).emit(eventName, payload);\n } else {\n (this.emitter as any).emit(eventName, data);\n }\n });\n\n // Eager init: call bfsm.transition() to enter initialState now.\n // bfsm.transition() calls getOrCreateClientMeta(), which bootstraps\n // the client and calls this.transition() internally (the real init,\n // which fires _onEnter and emits transitioning/transitioned). After\n // that inner call completes, meta.state === initialState, so the\n // original bfsm.transition() call hits the same-state guard and exits.\n this.bfsm.transition(this.context, config.initialState as TStateNames);\n }\n\n // -------------------------------------------------------------------------\n // Public API — same as BehavioralFsm, minus the client parameter\n // -------------------------------------------------------------------------\n\n /**\n * Dispatch an input to the current state's handler.\n * If a `_child` FSM in the current state can handle it, delegation occurs\n * there first; unhandled inputs bubble up to the parent.\n * No-ops silently when disposed.\n */\n handle(inputName: TInputNames, ...args: unknown[]): void {\n if (this.disposed) {\n return;\n }\n this.bfsm.handle(this.context, inputName, ...args);\n }\n\n /**\n * Returns true if the current state has a handler for `inputName`\n * (or a catch-all `\"*\"` handler). Does not trigger initialization\n * or any side effects. Returns false when disposed.\n */\n canHandle(inputName: string): boolean {\n if (this.disposed) {\n return false;\n }\n return this.bfsm.canHandle(this.context, inputName);\n }\n\n /**\n * Transition back to `initialState`, firing `_onEnter` and lifecycle\n * events as if entering it fresh. No-ops silently when disposed.\n */\n reset(): void {\n if (this.disposed) {\n return;\n }\n this.bfsm.reset(this.context);\n }\n\n /**\n * Returns the current state name. Always defined — Fsm eagerly\n * initializes into `initialState` during construction.\n */\n currentState(): TStateNames {\n return this.bfsm.currentState(this.context) as TStateNames;\n }\n\n /**\n * Directly transition to `toState`, firing `_onExit`, `_onEnter`, and\n * lifecycle events. Same-state transitions are silently ignored.\n * No-ops when disposed.\n */\n transition(toState: TStateNames): void {\n if (this.disposed) {\n return;\n }\n this.bfsm.transition(this.context, toState);\n }\n\n /**\n * Returns the current state as a dot-delimited path that includes\n * any active child FSM states (e.g. `\"active.connecting.retrying\"`).\n * Returns just the current state name when no child is active.\n */\n compositeState(): string {\n return this.bfsm.compositeState(this.context);\n }\n\n /**\n * Subscribe to a built-in lifecycle event or the wildcard.\n *\n * Named overload: typed payload, no event name in callback.\n * Wildcard (`\"*\"`): receives `(eventName, data)` for every event.\n * Returns a no-op `Subscription` when disposed.\n */\n on<K extends keyof FsmEventMap<TStateNames> & string>(\n eventName: K,\n callback: (data: FsmEventMap<TStateNames>[K]) => void\n ): Subscription;\n on(eventName: \"*\", callback: (eventName: string, data: unknown) => void): Subscription;\n on(eventName: string, callback: (...args: any[]) => void): Subscription {\n if (this.disposed) {\n return { off() {} };\n }\n return this.emitter.on(eventName as any, callback as any);\n }\n\n /**\n * Emit a custom event through the FSM. Built-in lifecycle events are\n * emitted automatically — this is for user-defined events from handlers.\n * Routes through the BehavioralFsm so all relay paths are consistent.\n * No-ops when disposed.\n */\n emit(eventName: string, data?: unknown): void {\n if (this.disposed) {\n return;\n }\n // Route through BehavioralFsm so custom events from handler emit()\n // and public fsm.emit() take the same path through the relay.\n this.bfsm.emit(eventName, data);\n }\n\n /**\n * Permanently shut down this FSM. Irreversible — all subsequent method\n * calls become silent no-ops. Clears all listeners and cascades disposal\n * to child FSMs (unless `preserveChildren` is set).\n */\n dispose(options?: DisposeOptions): void {\n this.disposed = true;\n this.bfsm.dispose(options);\n this.emitter.clear();\n }\n}\n\n/**\n * Create a single-client FSM from a config object.\n *\n * Generic parameters are inferred automatically:\n * - `TCtx` comes from `config.context` (defaults to `{}` if omitted).\n * - `TStates` is captured with `const` inference to preserve string literal\n * types, enabling compile-time validation of transition targets and `handle()`\n * input names.\n *\n * State names, input names, and all handler signatures derive from `TStates`.\n *\n * @example\n * ```ts\n * const light = createFsm({\n * id: \"traffic-light\",\n * initialState: \"green\",\n * context: { tickCount: 0 },\n * states: {\n * green: { timeout: \"yellow\" },\n * yellow: { timeout: \"red\" },\n * red: { timeout: \"green\" },\n * },\n * });\n *\n * light.handle(\"timeout\"); // transitions green → yellow\n * ```\n */\nexport function createFsm<\n TCtx extends object = Record<string, never>,\n const TStates extends Record<string, Record<string, unknown>> = Record<\n string,\n Record<string, unknown>\n >,\n>(config: FsmConfig<TCtx, TStates>): Fsm<TCtx, StateNamesOf<TStates>, InputNamesOf<TStates>> {\n return new Fsm(config as FsmConfig<TCtx, Record<string, Record<string, unknown>>>);\n}\n"]}
|