@alwatr/fsm 9.32.0 → 9.33.1

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.
@@ -0,0 +1,5 @@
1
+ /* 📦 @alwatr/fsm v9.33.1 */
2
+ import{createLogger as j}from"@alwatr/logger";import{queueMicrotask as q}from"@alwatr/delay";import{createPersistentStateSignal as P,createStateSignal as U}from"@alwatr/signal";class Z{config_;logger_;stateSignal;mailbox__=[];processing__=!0;destroyed__=!1;activeActorCleanups__=[];stateSignal__;constructor(z,J){this.config_=z;this.logger_=j(`fsm:${this.config_.name}`),this.logger_.logMethodArgs?.("constructor",z);let Q={name:z.initial,context:z.context};this.stateSignal__=J??(z.persistent?P({name:`fsm-state-${z.name}`,storageKey:z.persistent.storageKey??z.name,initialValue:Q,schemaVersion:z.persistent.schemaVersion}):U({name:`fsm-state-${z.name}`,initialValue:Q})),this.stateSignal=this.stateSignal__.asReadonly(),q(()=>this.start_())}get state(){return this.stateSignal__.get()}matches(...z){return z.includes(this.stateSignal__.get().name)}dispatch=(z)=>{this.logger_.logMethodArgs?.("dispatch",{event:z}),this.mailbox__.push(z),this.processMailbox__()};processMailbox__(){if(this.processing__)return;if(this.logger_.logMethod?.("processMailbox__"),this.destroyed__){this.logger_.incident?.("dispatch","dispatch_after_destroy");return}this.processing__=!0;try{for(let z=0;z<this.mailbox__.length;z++)if(this.processTransition__(this.mailbox__[z]),this.destroyed__)break}finally{this.processing__=!1,this.mailbox__.length=0}}processTransition__(z){let J=this.stateSignal__.get();this.logger_.logMethodArgs?.("processTransition__",{state:J.name,event:z});let Q=this.findTransition__(z,J);if(!Q){this.logger_.incident?.("processTransition__","ignored_event","No valid transition found for event",{state:J.name,event:z});return}let K=Q.target??J.name,W=Q.target!==void 0;if(W)this.executeEffects__(z,J.context,this.config_.states[J.name]?.exit),this.cleanupActors__();let X=this.applyAssigners__(z,J.context,Q.assigner),Y={name:K,context:X};if(this.stateSignal__.set(Y),W)this.executeEffects__(z,Y.context,this.config_.states[Y.name]?.entry),this.spawnActors__(z,Y.context,this.config_.states[Y.name]?.actor)}findTransition__(z,J){this.logger_.logMethod?.("findTransition__");let K=this.config_.states[J.name]?.on?.[z.type];if(!K)return;if(!Array.isArray(K)){if(!K.guard)return K;try{if(K.guard(z,J.context))return K}catch(W){this.logger_.error("findTransition__","guard_failed",W,{state:J.name,eventType:z.type})}return}for(let W=0;W<K.length;W++){let X=K[W];if(!X.guard)return X;try{if(X.guard(z,J.context))return X}catch(Y){this.logger_.error("findTransition__","guard_failed",Y,{state:J.name,eventType:z.type,index:W})}}return}executeEffects__(z,J,Q){if(!Q){this.logger_.logMethod?.("executeEffects__.skipped");return}if(this.logger_.logMethod?.("executeEffects__"),!Array.isArray(Q)){try{Q(z,J)}catch(K){this.logger_.error("executeEffects__","effect_failed",K,{event:z,context:J})}return}for(let K=0;K<Q.length;K++){let W=Q[K];try{W(z,J)}catch(X){this.logger_.error("executeEffects__","effect_failed",X,{event:z,context:J,index:K})}}}applyAssigners__(z,J,Q){if(!Q)return this.logger_.logMethod?.("applyAssigners__.skipped"),J;if(this.logger_.logMethod?.("applyAssigners__"),!Array.isArray(Q)){try{return Q(z,J)??J}catch(K){this.logger_.error("applyAssigners__","assigner_failed_atomic",K,{event:z,context:J})}return J}try{let K=J;for(let W=0;W<Q.length;W++){let X=Q[W],Y=X(z,K);if(Y)K=Y}return K}catch(K){return this.logger_.error("applyAssigners__","assigner_failed_atomic",K,{event:z,context:J}),J}}start_(){if(this.destroyed__)return;this.logger_.logMethod?.("start_");let z=this.stateSignal__.get(),J={type:"__init__"};if(this.executeEffects__(J,z.context,this.config_.states[z.name]?.entry),this.spawnActors__(J,z.context,this.config_.states[z.name]?.actor),this.processing__=!1,this.mailbox__.length>0)this.processMailbox__()}spawnActors__(z,J,Q){if(!Q){this.logger_.logMethod?.("spawnActors__.skipped");return}if(this.logger_.logMethod?.("spawnActors__"),!Array.isArray(Q)){try{let K=Q(J,this.dispatch);if(typeof K==="function")this.activeActorCleanups__.push(K)}catch(K){this.logger_.error("spawnActors__","actor_failed",K,{event:z,context:J})}return}for(let K=0;K<Q.length;K++){let W=Q[K];try{let X=W(J,this.dispatch);if(typeof X==="function")this.activeActorCleanups__.push(X)}catch(X){this.logger_.error("spawnActors__","actor_failed",X,{event:z,context:J,index:K})}}}cleanupActors__(){this.logger_.logMethodArgs?.("cleanupActors__",{count:this.activeActorCleanups__.length});for(let z=this.activeActorCleanups__.length-1;z>=0;z--)try{this.activeActorCleanups__[z]()}catch(J){this.logger_.error("cleanupActors__","cleanup_failed",J,{index:z})}this.activeActorCleanups__.length=0}destroy(z=!0){if(this.destroyed__)return;if(this.logger_.logMethod?.("destroy"),this.destroyed__=!0,this.mailbox__.length=0,this.cleanupActors__(),z)this.stateSignal__.destroy()}}function B(z){return new Z(z)}export{B as createFsmService,Z as FsmService};
3
+
4
+ //# debugId=2CCCCEC3109D01E664756E2164756E21
5
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fsm-service.ts", "../../src/facade.ts"],
4
+ "sourcesContent": [
5
+ "import {createLogger, type AlwatrLogger} from '@alwatr/logger';\nimport {queueMicrotask} from '@alwatr/delay';\nimport type {SingleOrArray} from '@alwatr/type-helper';\nimport {\n createPersistentStateSignal,\n createStateSignal,\n type StateSignal,\n type PersistentStateSignal,\n type IReadonlySignal,\n} from '@alwatr/signal';\n\nimport type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner, Actor} from './type.js';\n\n/**\n * A generic, encapsulated service that creates, runs, and manages a finite state machine.\n * It handles signal creation, logic connection, and lifecycle management, providing a clean,\n * reactive API for interacting with the FSM.\n *\n * @template TState The union type of all possible state names.\n * @template TEvent The union type of all possible events.\n * @template TContext The type of the machine's context (extended state).\n */\nexport class FsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n> {\n protected readonly logger_: AlwatrLogger;\n\n /** The public, read-only state signal. Subscribe to react to state changes. */\n public readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;\n\n /**\n * The FIFO event mailbox. Events are processed strictly in dispatch order.\n */\n private readonly mailbox__: TEvent[] = [];\n\n /**\n * RTC re-entrancy guard. While `true`, an active loop is draining the mailbox;\n * re-entrant dispatches just enqueue and return.\n */\n private processing__ = true;\n\n /** Set once by `destroy()`. All dispatches after destruction are ignored (and logged). */\n private destroyed__ = false;\n\n /**\n * Cleanup callbacks for currently active state actors, in spawn order.\n * Executed in REVERSE (LIFO) order on state exit — standard resource semantics\n * (last acquired, first released).\n */\n private readonly activeActorCleanups__: (() => void)[] = [];\n\n private readonly stateSignal__:\n | StateSignal<MachineState<TState, TContext>>\n | PersistentStateSignal<MachineState<TState, TContext>>;\n\n constructor(\n protected readonly config_: StateMachineConfig<TState, TEvent, TContext>,\n stateSignal?: StateSignal<MachineState<TState, TContext>> | PersistentStateSignal<MachineState<TState, TContext>>,\n ) {\n this.logger_ = createLogger(`fsm:${this.config_.name}`);\n DEV_MODE && this.logger_.logMethodArgs?.('constructor', config_);\n\n const initialValue: MachineState<TState, TContext> = {\n name: config_.initial,\n context: config_.context,\n };\n this.stateSignal__ =\n stateSignal\n ?? (config_.persistent ?\n createPersistentStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config_.name}`,\n storageKey: config_.persistent.storageKey ?? config_.name,\n initialValue,\n schemaVersion: config_.persistent.schemaVersion,\n })\n : createStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config_.name}`,\n initialValue,\n }));\n\n this.stateSignal = this.stateSignal__.asReadonly();\n\n // Execute initial/rehydrated state entry effects and spawn its actors.\n queueMicrotask(() => this.start_());\n }\n\n /**\n * Synchronous accessor for the current machine state.\n * Prefer `stateSignal.subscribe()` for reactive consumers; use this getter for\n * imperative checks inside controllers/services.\n */\n public get state(): MachineState<TState, TContext> {\n return this.stateSignal__.get();\n }\n\n /**\n * Convenience predicate: returns true if the current finite state matches any\n * of the given names. Sugar for `service.state.name === 'x' || ...`.\n */\n public matches(...names: TState[]): boolean {\n return names.includes(this.stateSignal__.get().name);\n }\n\n /**\n * Dispatches an event to the FSM mailbox.\n *\n * Events are processed with Run-to-Completion semantics: if dispatched while a\n * transition is in flight (re-entrant dispatch from a guard/effect/actor), the\n * event is enqueued and processed deterministically right after the current\n * transition completes — in the same call stack, in FIFO order, with no loss.\n *\n * @param event The event to process.\n */\n public readonly dispatch = (event: TEvent): void => {\n DEV_MODE && this.logger_.logMethodArgs?.('dispatch', {event});\n this.mailbox__.push(event);\n this.processMailbox__();\n };\n\n private processMailbox__(): void {\n // RTC guard: an active loop is already draining the mailbox; it will pick\n // this event up after the current transition finishes.\n if (this.processing__) return;\n DEV_MODE && this.logger_.logMethod?.('processMailbox__');\n if (this.destroyed__) {\n DEV_MODE && this.logger_.incident?.('dispatch', 'dispatch_after_destroy');\n return;\n }\n this.processing__ = true;\n try {\n // Do NOT cache length. New events may be added during processing, and they MUST be processed in the same order (FIFO).\n for (let index = 0; index < this.mailbox__.length; index++) {\n this.processTransition__(this.mailbox__[index]);\n if (this.destroyed__) break;\n }\n } finally {\n this.processing__ = false;\n this.mailbox__.length = 0;\n }\n }\n\n /**\n * The core FSM logic that processes a single event and transitions the machine to a new state.\n * This step is atomic: exit effects -> assigners -> state commit -> entry effects -> actors.\n *\n * @param event The event to process.\n */\n private processTransition__(event: TEvent): void {\n const currentState = this.stateSignal__.get();\n DEV_MODE && this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});\n\n const transition = this.findTransition__(event, currentState);\n\n if (!transition) {\n DEV_MODE\n && this.logger_.incident?.('processTransition__', 'ignored_event', 'No valid transition found for event', {\n state: currentState.name,\n event,\n });\n return; // Event ignored, no transition occurs.\n }\n\n const targetStateName = transition.target ?? currentState.name;\n const isExternalTransition = transition.target !== undefined;\n\n // 1. External transition: run exit effects (with the OLD context, per SCXML semantics) and tear down the current state's actors.\n if (isExternalTransition) {\n this.executeEffects__(event, currentState.context, this.config_.states[currentState.name]?.exit);\n this.cleanupActors__();\n }\n\n // 2. Apply assigners to compute the next context (pure, atomic).\n const nextContext = this.applyAssigners__(event, currentState.context, transition.assigner);\n\n // 3. Commit the new state, notifying all subscribers (async via signal layer).\n const nextState: MachineState<TState, TContext> = {\n name: targetStateName,\n context: nextContext,\n };\n this.stateSignal__.set(nextState);\n\n // 4. External transition: run entry effects (with the NEW context) and spawn the target state's actors.\n if (isExternalTransition) {\n this.executeEffects__(event, nextState.context, this.config_.states[nextState.name]?.entry);\n this.spawnActors__(event, nextState.context, this.config_.states[nextState.name]?.actor);\n }\n }\n\n /**\n * Finds the first valid transition for the given event by evaluating guards in declaration order. A guard-less transition acts as an unconditional fallback.\n *\n * @param event The triggering event.\n * @param currentState The current state of the machine.\n * @returns The first matching transition or `undefined` if none are found.\n */\n private findTransition__(\n event: TEvent,\n currentState: MachineState<TState, TContext>,\n ): Transition<TState, TEvent, TContext> | undefined {\n DEV_MODE && this.logger_.logMethod?.('findTransition__');\n\n const currentStateConfig = this.config_.states[currentState.name];\n const transitions = currentStateConfig?.on?.[event.type as TEvent['type']] as\n | SingleOrArray<Transition<TState, TEvent, TContext>>\n | undefined;\n\n if (!transitions) return undefined;\n\n if (!Array.isArray(transitions)) {\n if (!transitions.guard) return transitions; // Unconditional fallback branch.\n try {\n if (transitions.guard(event, currentState.context)) {\n return transitions;\n }\n } catch (error) {\n this.logger_.error('findTransition__', 'guard_failed', error, {\n state: currentState.name,\n eventType: event.type,\n });\n }\n return undefined;\n }\n\n // else if transitions is an array\n\n for (let index = 0; index < transitions.length; index++) {\n const transition = transitions[index];\n if (!transition.guard) return transition; // Unconditional fallback branch.\n try {\n if (transition.guard(event, currentState.context)) {\n return transition;\n }\n } catch (error) {\n this.logger_.error('findTransition__', 'guard_failed', error, {\n state: currentState.name,\n eventType: event.type,\n index,\n });\n // Treated as guard === false: continue evaluating the next branch.\n }\n }\n\n return undefined;\n }\n\n /**\n * Sequentially executes a list of synchronous effects (side-effects).\n * Errors are caught and logged without stopping the FSM.\n *\n * @param event The event that triggered these effects.\n * @param context The context at the time of execution.\n * @param effects A single effect or an array of effects.\n */\n private executeEffects__(\n event: TEvent,\n context: Readonly<TContext>,\n effects?: SingleOrArray<Effect<TEvent, TContext>>,\n ): void {\n if (!effects) {\n DEV_MODE && this.logger_.logMethod?.('executeEffects__.skipped');\n return;\n }\n\n DEV_MODE && this.logger_.logMethod?.('executeEffects__');\n\n if (!Array.isArray(effects)) {\n try {\n effects(event, context);\n } catch (error) {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n event,\n context,\n });\n }\n return;\n }\n\n // else if effects is an array\n\n for (let index = 0; index < effects.length; index++) {\n const effect = effects[index];\n try {\n effect(event, context);\n } catch (error) {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n event,\n context,\n index,\n });\n }\n }\n }\n\n /**\n * Applies all assigner functions to the context to produce a new, updated context.\n *\n * This process is atomic (all-or-nothing): if any assigner throws, the original\n * context is returned and all updates are discarded.\n *\n * @param event The event that triggered the transition.\n * @param context The current context.\n * @param assigners A single assigner or an array of assigners.\n * @returns The new, updated context, or the original context if any assigner fails.\n */\n private applyAssigners__(\n event: TEvent,\n context: TContext,\n assigners?: SingleOrArray<Assigner<TEvent, TContext>>,\n ): TContext {\n if (!assigners) {\n DEV_MODE && this.logger_.logMethod?.('applyAssigners__.skipped');\n return context;\n }\n\n DEV_MODE && this.logger_.logMethod?.('applyAssigners__');\n\n if (!Array.isArray(assigners)) {\n try {\n return assigners(event, context) ?? context;\n } catch (error) {\n this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {event, context});\n }\n return context;\n }\n\n // else if assigners is an array\n\n try {\n let accContext = context;\n for (let index = 0; index < assigners.length; index++) {\n const assigner = assigners[index];\n const nextContext = assigner(event, accContext);\n if (nextContext) {\n accContext = nextContext;\n }\n }\n return accContext;\n } catch (error) {\n this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {event, context});\n // On ANY failure, discard all changes and return the original context.\n return context;\n }\n }\n\n /**\n * Starts the FSM by executing the entry effects and spawning the actors of the\n * initial (or rehydrated) state, using the synthetic `{type: '__init__'}` event.\n */\n protected start_(): void {\n if (this.destroyed__) return;\n DEV_MODE && this.logger_.logMethod?.('start_');\n const currentState = this.stateSignal__.get();\n const initEvent = {type: '__init__'} as unknown as TEvent;\n this.executeEffects__(initEvent, currentState.context, this.config_.states[currentState.name]?.entry);\n this.spawnActors__(initEvent, currentState.context, this.config_.states[currentState.name]?.actor);\n this.processing__ = false; // Allow processing of dispatched events after the initial setup is complete.\n if (this.mailbox__.length > 0) {\n this.processMailbox__(); // Process any events that were dispatched during the initial setup.\n }\n }\n\n /**\n * Spawns all configured actors for the entered state.\n */\n private spawnActors__(\n event: TEvent,\n context: Readonly<TContext>,\n actors?: SingleOrArray<Actor<TEvent, TContext>>,\n ): void {\n if (!actors) {\n DEV_MODE && this.logger_.logMethod?.('spawnActors__.skipped');\n return;\n }\n\n DEV_MODE && this.logger_.logMethod?.('spawnActors__');\n\n if (!Array.isArray(actors)) {\n try {\n const cleanup = actors(context, this.dispatch);\n if (typeof cleanup === 'function') {\n this.activeActorCleanups__.push(cleanup);\n }\n } catch (error) {\n this.logger_.error('spawnActors__', 'actor_failed', error, {event, context});\n }\n return;\n }\n\n // else if actors is an array\n\n for (let index = 0; index < actors.length; index++) {\n const actor = actors[index];\n try {\n const cleanup = actor(context, this.dispatch);\n if (typeof cleanup === 'function') {\n this.activeActorCleanups__.push(cleanup);\n }\n } catch (error) {\n this.logger_.error('spawnActors__', 'actor_failed', error, {event, context, index});\n }\n }\n }\n\n /**\n * Cleans up (destroys) all currently active state actors in REVERSE (LIFO) spawn order — standard resource-release semantics.\n */\n private cleanupActors__(): void {\n DEV_MODE && this.logger_.logMethodArgs?.('cleanupActors__', {count: this.activeActorCleanups__.length});\n for (let index = this.activeActorCleanups__.length - 1; index >= 0; index--) {\n try {\n this.activeActorCleanups__[index]();\n } catch (error) {\n this.logger_.error('cleanupActors__', 'cleanup_failed', error, {index});\n }\n }\n this.activeActorCleanups__.length = 0;\n }\n\n /**\n * Destroys the service, cleaning up actors, the mailbox, and owned signals to\n * prevent memory leaks. Idempotent — safe to call multiple times.\n *\n * @param destroyState If `true` (default), also destroys the state signal, preventing any future subscriptions or updates. Set to `false` to preserve the last state value for late subscribers even after destruction.\n */\n public destroy(destroyState = true): void {\n if (this.destroyed__) return;\n DEV_MODE && this.logger_.logMethod?.('destroy');\n this.destroyed__ = true;\n this.mailbox__.length = 0;\n this.cleanupActors__();\n if (destroyState) {\n this.stateSignal__.destroy();\n }\n }\n}\n",
6
+ "import {FsmService} from './fsm-service.js';\n\nimport type {MachineEvent, StateMachineConfig} from './type.js';\n\n/**\n * A simple and clean factory function for creating an `FsmService` instance.\n * This is the recommended way to instantiate a new state machine.\n *\n * @template TState - The union type of all possible states.\n * @template TEvent - The union type of all possible events.\n * @template TContext - The type of the machine's context.\n *\n * @param config - The machine's configuration object.\n * @returns A new, ready-to-use instance of `FsmService`.\n *\n * @example\n * ```ts\n * import {createFsmService} from '@alwatr/fsm';\n * import type {StateMachineConfig} from '@alwatr/fsm';\n *\n * // 1. Define types\n * type LightContext = {brightness: number};\n * type LightState = 'on' | 'off';\n * type LightEvent = {type: 'TOGGLE'} | {type: 'SET_BRIGHTNESS'; level: number};\n *\n * // 2. Config the state machine\n * const lightMachineConfig: StateMachineConfig<LightState, LightEvent, LightContext> = {\n * name: 'light-switch',\n * initial: 'off',\n * context: {brightness: 0},\n * states: {\n * off: {\n * on: {\n * TOGGLE: {\n * target: 'on',\n * assigners: [({context}) => ({...context, brightness: 100})],\n * },\n * },\n * },\n * on: {\n * on: {\n * TOGGLE: {target: 'off', assigners: [({context}) => ({...context, brightness: 0})]},\n * SET_BRIGHTNESS: {assigners: [({context, event}) => ({...context, brightness: event.level})]},\n * },\n * },\n * },\n * };\n *\n * // 3. Create the service\n * const lightService = createFsmService(lightMachineConfig);\n *\n * // 4. Use it in your application\n * lightService.stateSignal.subscribe((state) => {\n * console.log(`Light is ${state.name} with brightness ${state.context.brightness}`);\n * });\n *\n * lightService.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100\n *\n * lightService.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50\n *\n * // 5. Cleanup\n * // lightService.destroy();\n * ```\n */\nexport function createFsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext> {\n return new FsmService(config);\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "2CCCCEC3109D01E664756E2164756E21",
10
+ "names": []
11
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../src/facade.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAgB,kBAAkB,EAAC,MAAM,WAAW,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,SAAS,MAAM,EACrB,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAChE,MAAM,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAoB5F"}
1
+ {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../src/facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,WAAW,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,SAAS,MAAM,EACrB,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAChE,MAAM,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAE5F"}
@@ -12,38 +12,68 @@ import type { StateMachineConfig, MachineState, MachineEvent } from './type.js';
12
12
  */
13
13
  export declare class FsmService<TState extends string, TEvent extends MachineEvent, TContext extends Record<string, unknown> = Record<string, never>> {
14
14
  protected readonly config_: StateMachineConfig<TState, TEvent, TContext>;
15
- private readonly stateSignal__;
16
15
  protected readonly logger_: AlwatrLogger;
17
- /** The private event signal for sending events to the FSM. */
18
- private readonly eventSignal__;
19
16
  /** The public, read-only state signal. Subscribe to react to state changes. */
20
17
  readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;
21
- /** The set of cleanup functions for currently active state actors. */
18
+ /**
19
+ * The FIFO event mailbox. Events are processed strictly in dispatch order.
20
+ */
21
+ private readonly mailbox__;
22
+ /**
23
+ * RTC re-entrancy guard. While `true`, an active loop is draining the mailbox;
24
+ * re-entrant dispatches just enqueue and return.
25
+ */
26
+ private processing__;
27
+ /** Set once by `destroy()`. All dispatches after destruction are ignored (and logged). */
28
+ private destroyed__;
29
+ /**
30
+ * Cleanup callbacks for currently active state actors, in spawn order.
31
+ * Executed in REVERSE (LIFO) order on state exit — standard resource semantics
32
+ * (last acquired, first released).
33
+ */
22
34
  private readonly activeActorCleanups__;
23
- constructor(config_: StateMachineConfig<TState, TEvent, TContext>, stateSignal__: StateSignal<MachineState<TState, TContext>> | PersistentStateSignal<MachineState<TState, TContext>>);
35
+ private readonly stateSignal__;
36
+ constructor(config_: StateMachineConfig<TState, TEvent, TContext>, stateSignal?: StateSignal<MachineState<TState, TContext>> | PersistentStateSignal<MachineState<TState, TContext>>);
37
+ /**
38
+ * Synchronous accessor for the current machine state.
39
+ * Prefer `stateSignal.subscribe()` for reactive consumers; use this getter for
40
+ * imperative checks inside controllers/services.
41
+ */
42
+ get state(): MachineState<TState, TContext>;
43
+ /**
44
+ * Convenience predicate: returns true if the current finite state matches any
45
+ * of the given names. Sugar for `service.state.name === 'x' || ...`.
46
+ */
47
+ matches(...names: TState[]): boolean;
24
48
  /**
25
49
  * Dispatches an event to the FSM mailbox.
26
50
  *
51
+ * Events are processed with Run-to-Completion semantics: if dispatched while a
52
+ * transition is in flight (re-entrant dispatch from a guard/effect/actor), the
53
+ * event is enqueued and processed deterministically right after the current
54
+ * transition completes — in the same call stack, in FIFO order, with no loss.
55
+ *
27
56
  * @param event The event to process.
28
57
  */
29
58
  readonly dispatch: (event: TEvent) => void;
59
+ private processMailbox__;
30
60
  /**
31
61
  * The core FSM logic that processes a single event and transitions the machine to a new state.
32
- * This process is atomic and follows the Run-to-Completion (RTC) model.
62
+ * This step is atomic: exit effects -> assigners -> state commit -> entry effects -> actors.
33
63
  *
34
64
  * @param event The event to process.
35
65
  */
36
66
  private processTransition__;
37
67
  /**
38
- * Finds the first valid transition for the given event and context by evaluating guards.
68
+ * Finds the first valid transition for the given event by evaluating guards in declaration order. A guard-less transition acts as an unconditional fallback.
39
69
  *
40
70
  * @param event The triggering event.
41
- * @param context The current machine context.
71
+ * @param currentState The current state of the machine.
42
72
  * @returns The first matching transition or `undefined` if none are found.
43
73
  */
44
74
  private findTransition__;
45
75
  /**
46
- * Sequentially executes a list of effects (side-effects).
76
+ * Sequentially executes a list of synchronous effects (side-effects).
47
77
  * Errors are caught and logged without stopping the FSM.
48
78
  *
49
79
  * @param event The event that triggered these effects.
@@ -53,8 +83,9 @@ export declare class FsmService<TState extends string, TEvent extends MachineEve
53
83
  private executeEffects__;
54
84
  /**
55
85
  * Applies all assigner functions to the context to produce a new, updated context.
56
- * This process is atomic (all-or-nothing). If any assigner fails, the original
57
- * context is returned, and all updates are discarded.
86
+ *
87
+ * This process is atomic (all-or-nothing): if any assigner throws, the original
88
+ * context is returned and all updates are discarded.
58
89
  *
59
90
  * @param event The event that triggered the transition.
60
91
  * @param context The current context.
@@ -63,8 +94,8 @@ export declare class FsmService<TState extends string, TEvent extends MachineEve
63
94
  */
64
95
  private applyAssigners__;
65
96
  /**
66
- * Starts the FSM by executing the entry effects and spawning the actors
67
- * of the initial/current state.
97
+ * Starts the FSM by executing the entry effects and spawning the actors of the
98
+ * initial (or rehydrated) state, using the synthetic `{type: '__init__'}` event.
68
99
  */
69
100
  protected start_(): void;
70
101
  /**
@@ -72,13 +103,15 @@ export declare class FsmService<TState extends string, TEvent extends MachineEve
72
103
  */
73
104
  private spawnActors__;
74
105
  /**
75
- * Cleans up (destroys) all currently active state actors.
106
+ * Cleans up (destroys) all currently active state actors in REVERSE (LIFO) spawn order — standard resource-release semantics.
76
107
  */
77
108
  private cleanupActors__;
78
109
  /**
79
- * Destroys the service, cleaning up all internal signals and subscriptions
80
- * to prevent memory leaks.
110
+ * Destroys the service, cleaning up actors, the mailbox, and owned signals to
111
+ * prevent memory leaks. Idempotent — safe to call multiple times.
112
+ *
113
+ * @param destroyState If `true` (default), also destroys the state signal, preventing any future subscriptions or updates. Set to `false` to preserve the last state value for late subscribers even after destruction.
81
114
  */
82
- destroy(): void;
115
+ destroy(destroyState?: boolean): void;
83
116
  }
84
117
  //# sourceMappingURL=fsm-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fsm-service.d.ts","sourceRoot":"","sources":["../src/fsm-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAE1B,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAC,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAsC,MAAM,WAAW,CAAC;AAEnH;;;;;;;;GAQG;AACH,qBAAa,UAAU,CACrB,MAAM,SAAS,MAAM,EACrB,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAc9D,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;IACxE,OAAO,CAAC,QAAQ,CAAC,aAAa;IAbhC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAEzC,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IAEpD,+EAA+E;IAC/E,SAAgB,WAAW,EAAE,eAAe,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE7E,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;gBAG1C,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EACvD,aAAa,EAC1B,WAAW,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GAC3C,qBAAqB,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAe3D;;;;OAIG;IACH,SAAgB,QAAQ,GAAI,OAAO,MAAM,KAAG,IAAI,CAG9C;IAEF;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IA0CxB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IAsCxB;;;OAGG;IACH,SAAS,CAAC,MAAM,IAAI,IAAI;IASxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkCrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;;OAGG;IACI,OAAO,IAAI,IAAI;CAMvB"}
1
+ {"version":3,"file":"fsm-service.d.ts","sourceRoot":"","sources":["../src/fsm-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAC,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAsC,MAAM,WAAW,CAAC;AAEnH;;;;;;;;GAQG;AACH,qBAAa,UAAU,CACrB,MAAM,SAAS,MAAM,EACrB,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAiC9D,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;IA/B1E,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAEzC,+EAA+E;IAC/E,SAAgB,WAAW,EAAE,eAAe,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE7E;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAE1C;;;OAGG;IACH,OAAO,CAAC,YAAY,CAAQ;IAE5B,0FAA0F;IAC1F,OAAO,CAAC,WAAW,CAAS;IAE5B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAsB;IAE5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAE4B;gBAGrC,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EACxE,WAAW,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GAAG,qBAAqB,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IA6BnH;;;;OAIG;IACH,IAAW,KAAK,IAAI,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAEjD;IAED;;;OAGG;IACI,OAAO,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO;IAI3C;;;;;;;;;OASG;IACH,SAAgB,QAAQ,GAAI,OAAO,MAAM,KAAG,IAAI,CAI9C;IAEF,OAAO,CAAC,gBAAgB;IAsBxB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAyC3B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkDxB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAwCxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,gBAAgB;IAwCxB;;;OAGG;IACH,SAAS,CAAC,MAAM,IAAI,IAAI;IAaxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAuCrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;;;;OAKG;IACI,OAAO,CAAC,YAAY,UAAO,GAAG,IAAI;CAU1C"}
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
- /* 📦 @alwatr/fsm v9.32.0 */
2
- import{createPersistentStateSignal as U,createStateSignal as D}from"@alwatr/signal";import{createLogger as k}from"@alwatr/logger";import{createEventSignal as P}from"@alwatr/signal";class j{config_;stateSignal__;logger_;eventSignal__;stateSignal;activeActorCleanups__=new Set;constructor(q,z){this.config_=q;this.stateSignal__=z;this.logger_=k(`fsm:${this.config_.name}`),this.logger_.logMethodArgs?.("constructor",q),this.stateSignal=this.stateSignal__.asReadonly(),this.eventSignal__=P({name:`fsm-event-${this.config_.name}`}),this.eventSignal__.subscribe((J)=>this.processTransition__(J),{receivePrevious:!1}),this.start_()}dispatch=(q)=>{this.logger_.logMethodArgs?.("dispatch",{event:q}),this.eventSignal__.dispatch(q)};processTransition__(q){let z=this.stateSignal__.get();this.logger_.logMethodArgs?.("processTransition__",{state:z.name,event:q});let J=this.findTransition__(q,z.context);if(!J){this.logger_.incident?.("processTransition__","ignored_event","No valid transition found for event",{state:z.name,event:q});return}let X=J.target??z.name,K=J.target!==void 0;if(K)this.executeEffects__(q,z.context,this.config_.states[z.name]?.exit),this.cleanupActors__();let W=this.applyAssigners__(q,z.context,J.assigners),Q={name:X,context:W};if(this.stateSignal__.set(Q),K)this.executeEffects__(q,Q.context,this.config_.states[Q.name]?.entry),this.spawnActors__(q,Q.context,this.config_.states[Q.name]?.actors)}findTransition__(q,z){this.logger_.logMethod?.("findTransition__");let J=this.stateSignal__.get().name,K=this.config_.states[J]?.on?.[q.type];if(!K)return;let W=Array.isArray(K)?K:[K];for(let Q=0;Q<W.length;Q++){let Y=W[Q];if(!Y.guard)return Y;try{let Z=Y.guard({event:q,context:z});if(this.logger_.logStep?.("findTransition__","check_guard",{state:J,eventType:q.type,transitionIndex:Q,guard:Y.guard.name||"anonymous",result:Z}),Z)return Y}catch(Z){this.logger_.error("findTransition__","guard_failed",Z,{state:J,eventType:q.type,transitionIndex:Q,guard:Y.guard.name||"anonymous"})}}return}executeEffects__(q,z,J){if(!J){this.logger_.logMethodArgs?.("executeEffects__//skipped",{count:0});return}let X=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("executeEffects__",{count:X.length});for(let K of X)try{let W=K({event:q,context:z});if(W instanceof Promise)W.catch((Q)=>{this.logger_.error("executeEffects__","effect_failed",Q,{effect:K.name||"anonymous",state:this.stateSignal__.get().name,event:q,context:z})})}catch(W){this.logger_.error("executeEffects__","effect_failed",W,{effect:K.name||"anonymous",state:this.stateSignal__.get().name,event:q,context:z})}}applyAssigners__(q,z,J){if(!J)return this.logger_.logMethodArgs?.("applyAssigners__//skipped",{count:0}),z;let X=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("applyAssigners__",{count:X.length});try{let K=z;for(let W of X){let Q=W({event:q,context:K});if(this.logger_.logMethodFull?.(`event.${q.type}.action.${W.name||"anonymous"}`,{event:q,accContext:K},Q),Q!==void 0&&Q!==null)K=Q}return K}catch(K){return this.logger_.error("applyAssigners__","assigner_failed_atomic",K,{event:q,context:z}),z}}start_(){if(this.eventSignal__.isDestroyed)return;this.logger_.logMethod?.("start_");let q=this.stateSignal__.get(),z={type:"__init__"};this.executeEffects__(z,q.context,this.config_.states[q.name]?.entry),this.spawnActors__(z,q.context,this.config_.states[q.name]?.actors)}spawnActors__(q,z,J){if(!J){this.logger_.logMethodArgs?.("spawnActors__//skipped",{count:0});return}let X=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("spawnActors__",{count:X.length});for(let K of X)try{let W=K({event:q,context:z,dispatch:this.dispatch});if(typeof W==="function")this.activeActorCleanups__.add(W)}catch(W){this.logger_.error("spawnActors__","actor_failed",W,{actor:K.name||"anonymous",state:this.stateSignal__.get().name,event:q,context:z})}}cleanupActors__(){this.logger_.logMethodArgs?.("cleanupActors__",{count:this.activeActorCleanups__.size});for(let q of this.activeActorCleanups__)try{q()}catch(z){this.logger_.error("cleanupActors__","cleanup_failed",z)}this.activeActorCleanups__.clear()}destroy(){this.logger_.logMethod?.("destroy"),this.cleanupActors__(),this.eventSignal__.destroy(),this.stateSignal__.destroy()}}function O(q){let z={name:q.initial,context:q.context},J=q.persistent?U({name:`fsm-state-${q.name}`,storageKey:q.persistent.storageKey??q.name,initialValue:z,schemaVersion:q.persistent.schemaVersion}):D({name:`fsm-state-${q.name}`,initialValue:z});return new j(q,J)}export{O as createFsmService,j as FsmService};
1
+ /* 📦 @alwatr/fsm v9.33.1 */
2
+ import{createLogger as j}from"@alwatr/logger";import{queueMicrotask as q}from"@alwatr/delay";import{createPersistentStateSignal as P,createStateSignal as U}from"@alwatr/signal";class Z{config_;logger_;stateSignal;mailbox__=[];processing__=!0;destroyed__=!1;activeActorCleanups__=[];stateSignal__;constructor(z,J){this.config_=z;this.logger_=j(`fsm:${this.config_.name}`);let Q={name:z.initial,context:z.context};this.stateSignal__=J??(z.persistent?P({name:`fsm-state-${z.name}`,storageKey:z.persistent.storageKey??z.name,initialValue:Q,schemaVersion:z.persistent.schemaVersion}):U({name:`fsm-state-${z.name}`,initialValue:Q})),this.stateSignal=this.stateSignal__.asReadonly(),q(()=>this.start_())}get state(){return this.stateSignal__.get()}matches(...z){return z.includes(this.stateSignal__.get().name)}dispatch=(z)=>{this.mailbox__.push(z),this.processMailbox__()};processMailbox__(){if(this.processing__)return;if(this.destroyed__)return;this.processing__=!0;try{for(let z=0;z<this.mailbox__.length;z++)if(this.processTransition__(this.mailbox__[z]),this.destroyed__)break}finally{this.processing__=!1,this.mailbox__.length=0}}processTransition__(z){let J=this.stateSignal__.get(),Q=this.findTransition__(z,J);if(!Q)return;let K=Q.target??J.name,W=Q.target!==void 0;if(W)this.executeEffects__(z,J.context,this.config_.states[J.name]?.exit),this.cleanupActors__();let X=this.applyAssigners__(z,J.context,Q.assigner),Y={name:K,context:X};if(this.stateSignal__.set(Y),W)this.executeEffects__(z,Y.context,this.config_.states[Y.name]?.entry),this.spawnActors__(z,Y.context,this.config_.states[Y.name]?.actor)}findTransition__(z,J){let K=this.config_.states[J.name]?.on?.[z.type];if(!K)return;if(!Array.isArray(K)){if(!K.guard)return K;try{if(K.guard(z,J.context))return K}catch(W){this.logger_.error("findTransition__","guard_failed",W,{state:J.name,eventType:z.type})}return}for(let W=0;W<K.length;W++){let X=K[W];if(!X.guard)return X;try{if(X.guard(z,J.context))return X}catch(Y){this.logger_.error("findTransition__","guard_failed",Y,{state:J.name,eventType:z.type,index:W})}}return}executeEffects__(z,J,Q){if(!Q)return;if(!Array.isArray(Q)){try{Q(z,J)}catch(K){this.logger_.error("executeEffects__","effect_failed",K,{event:z,context:J})}return}for(let K=0;K<Q.length;K++){let W=Q[K];try{W(z,J)}catch(X){this.logger_.error("executeEffects__","effect_failed",X,{event:z,context:J,index:K})}}}applyAssigners__(z,J,Q){if(!Q)return J;if(!Array.isArray(Q)){try{return Q(z,J)??J}catch(K){this.logger_.error("applyAssigners__","assigner_failed_atomic",K,{event:z,context:J})}return J}try{let K=J;for(let W=0;W<Q.length;W++){let X=Q[W],Y=X(z,K);if(Y)K=Y}return K}catch(K){return this.logger_.error("applyAssigners__","assigner_failed_atomic",K,{event:z,context:J}),J}}start_(){if(this.destroyed__)return;let z=this.stateSignal__.get(),J={type:"__init__"};if(this.executeEffects__(J,z.context,this.config_.states[z.name]?.entry),this.spawnActors__(J,z.context,this.config_.states[z.name]?.actor),this.processing__=!1,this.mailbox__.length>0)this.processMailbox__()}spawnActors__(z,J,Q){if(!Q)return;if(!Array.isArray(Q)){try{let K=Q(J,this.dispatch);if(typeof K==="function")this.activeActorCleanups__.push(K)}catch(K){this.logger_.error("spawnActors__","actor_failed",K,{event:z,context:J})}return}for(let K=0;K<Q.length;K++){let W=Q[K];try{let X=W(J,this.dispatch);if(typeof X==="function")this.activeActorCleanups__.push(X)}catch(X){this.logger_.error("spawnActors__","actor_failed",X,{event:z,context:J,index:K})}}}cleanupActors__(){for(let z=this.activeActorCleanups__.length-1;z>=0;z--)try{this.activeActorCleanups__[z]()}catch(J){this.logger_.error("cleanupActors__","cleanup_failed",J,{index:z})}this.activeActorCleanups__.length=0}destroy(z=!0){if(this.destroyed__)return;if(this.destroyed__=!0,this.mailbox__.length=0,this.cleanupActors__(),z)this.stateSignal__.destroy()}}function B(z){return new Z(z)}export{B as createFsmService,Z as FsmService};
3
3
 
4
- //# debugId=BC19972D6D3E964E64756E2164756E21
4
+ //# debugId=21C5CE2D42EDADD164756E2164756E21
5
5
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/facade.ts", "../src/fsm-service.ts"],
3
+ "sources": ["../src/fsm-service.ts", "../src/facade.ts"],
4
4
  "sourcesContent": [
5
- "import {createPersistentStateSignal, createStateSignal} from '@alwatr/signal';\n\nimport {FsmService} from './fsm-service.js';\n\nimport type {MachineEvent, MachineState, StateMachineConfig} from './type.js';\n\n/**\n * A simple and clean factory function for creating an `FsmService` instance.\n * This is the recommended way to instantiate a new state machine.\n *\n * @template TState - The union type of all possible states.\n * @template TEvent - The union type of all possible events.\n * @template TContext - The type of the machine's context.\n *\n * @param config - The machine's configuration object.\n * @returns A new, ready-to-use instance of `FsmService`.\n *\n * @example\n * ```ts\n * import {createFsmService} from '@alwatr/fsm';\n * import type {StateMachineConfig} from '@alwatr/fsm';\n *\n * // 1. Define types\n * type LightContext = {brightness: number};\n * type LightState = 'on' | 'off';\n * type LightEvent = {type: 'TOGGLE'} | {type: 'SET_BRIGHTNESS'; level: number};\n *\n * // 2. Config the state machine\n * const lightMachineConfig: StateMachineConfig<LightState, LightEvent, LightContext> = {\n * name: 'light-switch',\n * initial: 'off',\n * context: {brightness: 0},\n * states: {\n * off: {\n * on: {\n * TOGGLE: {\n * target: 'on',\n * assigners: [({context}) => ({...context, brightness: 100})],\n * },\n * },\n * },\n * on: {\n * on: {\n * TOGGLE: {target: 'off', assigners: [({context}) => ({...context, brightness: 0})]},\n * SET_BRIGHTNESS: {assigners: [({context, event}) => ({...context, brightness: event.level})]},\n * },\n * },\n * },\n * };\n *\n * // 3. Create the service\n * const lightService = createFsmService(lightMachineConfig);\n *\n * // 4. Use it in your application\n * lightService.stateSignal.subscribe((state) => {\n * console.log(`Light is ${state.name} with brightness ${state.context.brightness}`);\n * });\n *\n * lightService.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100\n *\n * lightService.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50\n *\n * // 5. Cleanup\n * // lightService.destroy();\n * ```\n */\nexport function createFsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext> {\n const initialValue: MachineState<TState, TContext> = {\n name: config.initial,\n context: config.context,\n };\n\n const stateSignal =\n config.persistent ?\n createPersistentStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config.name}`,\n storageKey: config.persistent.storageKey ?? config.name,\n initialValue,\n schemaVersion: config.persistent.schemaVersion,\n })\n : createStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config.name}`,\n initialValue: initialValue,\n });\n\n return new FsmService(config, stateSignal);\n}\n",
6
- "import type {SingleOrArray} from '@alwatr/type-helper';\nimport {createLogger, type AlwatrLogger} from '@alwatr/logger';\nimport {\n createEventSignal,\n type StateSignal,\n type PersistentStateSignal,\n EventSignal,\n type IReadonlySignal,\n} from '@alwatr/signal';\n\nimport type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner, Actor} from './type.js';\n\n/**\n * A generic, encapsulated service that creates, runs, and manages a finite state machine.\n * It handles signal creation, logic connection, and lifecycle management, providing a clean,\n * reactive API for interacting with the FSM.\n *\n * @template TState The union type of all possible state names.\n * @template TEvent The union type of all possible events.\n * @template TContext The type of the machine's context (extended state).\n */\nexport class FsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n> {\n protected readonly logger_: AlwatrLogger;\n\n /** The private event signal for sending events to the FSM. */\n private readonly eventSignal__: EventSignal<TEvent>;\n\n /** The public, read-only state signal. Subscribe to react to state changes. */\n public readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;\n\n /** The set of cleanup functions for currently active state actors. */\n private readonly activeActorCleanups__ = new Set<() => void>();\n\n constructor(\n protected readonly config_: StateMachineConfig<TState, TEvent, TContext>,\n private readonly stateSignal__:\n | StateSignal<MachineState<TState, TContext>>\n | PersistentStateSignal<MachineState<TState, TContext>>,\n ) {\n this.logger_ = createLogger(`fsm:${this.config_.name}`);\n this.logger_.logMethodArgs?.('constructor', config_);\n\n this.stateSignal = this.stateSignal__.asReadonly();\n this.eventSignal__ = createEventSignal<TEvent>({\n name: `fsm-event-${this.config_.name}`,\n });\n this.eventSignal__.subscribe((event) => this.processTransition__(event), {receivePrevious: false});\n\n // Execute initial state entry effects and actors.\n this.start_();\n }\n\n /**\n * Dispatches an event to the FSM mailbox.\n *\n * @param event The event to process.\n */\n public readonly dispatch = (event: TEvent): void => {\n this.logger_.logMethodArgs?.('dispatch', {event});\n this.eventSignal__.dispatch(event);\n };\n\n /**\n * The core FSM logic that processes a single event and transitions the machine to a new state.\n * This process is atomic and follows the Run-to-Completion (RTC) model.\n *\n * @param event The event to process.\n */\n private processTransition__(event: TEvent): void {\n const currentState = this.stateSignal__.get();\n this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});\n\n const transition = this.findTransition__(event, currentState.context);\n\n if (!transition) {\n this.logger_.incident?.('processTransition__', 'ignored_event', 'No valid transition found for event', {\n state: currentState.name,\n event,\n });\n return; // Event ignored, no transition occurs.\n }\n\n const targetStateName = transition.target ?? currentState.name;\n const isExternalTransition = transition.target !== undefined;\n\n // 1. Execute exit effects and cleanup actors of the current state if it's an external transition.\n if (isExternalTransition) {\n this.executeEffects__(event, currentState.context, this.config_.states[currentState.name]?.exit);\n this.cleanupActors__();\n }\n\n // 2. Apply assigners to compute the next context. This is a pure function.\n const nextContext = this.applyAssigners__(event, currentState.context, transition.assigners);\n\n // 3. Create the final next state object.\n const nextState: MachineState<TState, TContext> = {\n name: targetStateName,\n context: nextContext,\n };\n\n // 4. Set the new state, notifying all subscribers.\n this.stateSignal__.set(nextState);\n\n // 5. Execute entry effects and spawn actors of the new state if it's an external transition.\n if (isExternalTransition) {\n this.executeEffects__(event, nextState.context, this.config_.states[nextState.name]?.entry);\n this.spawnActors__(event, nextState.context, this.config_.states[nextState.name]?.actors);\n }\n }\n\n /**\n * Finds the first valid transition for the given event and context by evaluating guards.\n *\n * @param event The triggering event.\n * @param context The current machine context.\n * @returns The first matching transition or `undefined` if none are found.\n */\n private findTransition__(\n event: TEvent,\n context: Readonly<TContext>,\n ): Transition<TState, TEvent, TContext> | undefined {\n this.logger_.logMethod?.('findTransition__');\n\n const currentStateName = this.stateSignal__.get().name;\n const currentStateConfig = this.config_.states[currentStateName];\n const transitions = currentStateConfig?.on?.[event.type as TEvent['type']] as\n | SingleOrArray<Transition<TState, TEvent, TContext>>\n | undefined;\n\n if (!transitions) return undefined;\n\n const transitionsArray = Array.isArray(transitions) ? transitions : [transitions];\n\n for (let index = 0; index < transitionsArray.length; index++) {\n const transition = transitionsArray[index];\n if (!transition.guard) return transition;\n try {\n const guardMet = transition.guard({event, context});\n this.logger_.logStep?.('findTransition__', 'check_guard', {\n state: currentStateName,\n eventType: event.type,\n transitionIndex: index,\n guard: transition.guard.name || 'anonymous',\n result: guardMet,\n });\n if (guardMet) return transition;\n } catch (error) {\n this.logger_.error('findTransition__', 'guard_failed', error, {\n state: currentStateName,\n eventType: event.type,\n transitionIndex: index,\n guard: transition.guard.name || 'anonymous',\n });\n }\n }\n\n return undefined;\n }\n\n /**\n * Sequentially executes a list of effects (side-effects).\n * Errors are caught and logged without stopping the FSM.\n *\n * @param event The event that triggered these effects.\n * @param context The context at the time of execution.\n * @param effects A single effect or an array of effects.\n */\n private executeEffects__(\n event: TEvent,\n context: Readonly<TContext>,\n effects?: SingleOrArray<Effect<TEvent, TContext>>,\n ): void {\n if (!effects) {\n this.logger_.logMethodArgs?.('executeEffects__//skipped', {count: 0});\n return;\n }\n const effectsArray = Array.isArray(effects) ? effects : [effects];\n\n this.logger_.logMethodArgs?.('executeEffects__', {count: effectsArray.length});\n\n for (const effect of effectsArray) {\n try {\n const result = effect({event, context});\n if (result instanceof Promise) {\n result.catch((error) => {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n effect: effect.name || 'anonymous',\n state: this.stateSignal__.get().name,\n event,\n context,\n });\n });\n }\n } catch (error) {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n effect: effect.name || 'anonymous',\n state: this.stateSignal__.get().name,\n event,\n context,\n });\n }\n }\n }\n\n /**\n * Applies all assigner functions to the context to produce a new, updated context.\n * This process is atomic (all-or-nothing). If any assigner fails, the original\n * context is returned, and all updates are discarded.\n *\n * @param event The event that triggered the transition.\n * @param context The current context.\n * @param assigners A single assigner or an array of assigners.\n * @returns The new, updated context, or the original context if any assigner fails.\n */\n private applyAssigners__(\n event: TEvent,\n context: TContext,\n assigners?: SingleOrArray<Assigner<TEvent, TContext>>,\n ): TContext {\n if (!assigners) {\n this.logger_.logMethodArgs?.('applyAssigners__//skipped', {count: 0});\n return context;\n }\n\n const assignersArray = Array.isArray(assigners) ? assigners : [assigners];\n\n this.logger_.logMethodArgs?.('applyAssigners__', {count: assignersArray.length});\n\n try {\n let accContext = context;\n for (const assigner of assignersArray) {\n const nextContext = assigner({event, context: accContext});\n this.logger_.logMethodFull?.(\n `event.${event.type}.action.${assigner.name || 'anonymous'}`,\n {event, accContext},\n nextContext,\n );\n if (nextContext !== undefined && nextContext !== null) {\n accContext = nextContext;\n }\n }\n return accContext;\n } catch (error) {\n this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {\n event,\n context, // Log the original context for debugging.\n });\n // On ANY failure, discard all changes and return the original context.\n return context;\n }\n }\n\n /**\n * Starts the FSM by executing the entry effects and spawning the actors\n * of the initial/current state.\n */\n protected start_(): void {\n if (this.eventSignal__.isDestroyed) return;\n this.logger_.logMethod?.('start_');\n const currentState = this.stateSignal__.get();\n const initEvent = {type: '__init__'} as unknown as TEvent;\n this.executeEffects__(initEvent, currentState.context, this.config_.states[currentState.name]?.entry);\n this.spawnActors__(initEvent, currentState.context, this.config_.states[currentState.name]?.actors);\n }\n\n /**\n * Spawns all configured actors for the entered state.\n */\n private spawnActors__(\n event: TEvent,\n context: Readonly<TContext>,\n actors?: SingleOrArray<Actor<TEvent, TContext>>,\n ): void {\n if (!actors) {\n this.logger_.logMethodArgs?.('spawnActors__//skipped', {count: 0});\n return;\n }\n const actorsArray = Array.isArray(actors) ? actors : [actors];\n\n this.logger_.logMethodArgs?.('spawnActors__', {count: actorsArray.length});\n\n for (const actor of actorsArray) {\n try {\n const cleanup = actor({\n event,\n context,\n dispatch: this.dispatch,\n });\n if (typeof cleanup === 'function') {\n this.activeActorCleanups__.add(cleanup);\n }\n } catch (error) {\n this.logger_.error('spawnActors__', 'actor_failed', error, {\n actor: actor.name || 'anonymous',\n state: this.stateSignal__.get().name,\n event,\n context,\n });\n }\n }\n }\n\n /**\n * Cleans up (destroys) all currently active state actors.\n */\n private cleanupActors__(): void {\n this.logger_.logMethodArgs?.('cleanupActors__', {count: this.activeActorCleanups__.size});\n for (const cleanup of this.activeActorCleanups__) {\n try {\n cleanup();\n } catch (error) {\n this.logger_.error('cleanupActors__', 'cleanup_failed', error);\n }\n }\n this.activeActorCleanups__.clear();\n }\n\n /**\n * Destroys the service, cleaning up all internal signals and subscriptions\n * to prevent memory leaks.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n this.cleanupActors__();\n this.eventSignal__.destroy();\n this.stateSignal__.destroy();\n }\n}\n"
5
+ "import {createLogger, type AlwatrLogger} from '@alwatr/logger';\nimport {queueMicrotask} from '@alwatr/delay';\nimport type {SingleOrArray} from '@alwatr/type-helper';\nimport {\n createPersistentStateSignal,\n createStateSignal,\n type StateSignal,\n type PersistentStateSignal,\n type IReadonlySignal,\n} from '@alwatr/signal';\n\nimport type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner, Actor} from './type.js';\n\n/**\n * A generic, encapsulated service that creates, runs, and manages a finite state machine.\n * It handles signal creation, logic connection, and lifecycle management, providing a clean,\n * reactive API for interacting with the FSM.\n *\n * @template TState The union type of all possible state names.\n * @template TEvent The union type of all possible events.\n * @template TContext The type of the machine's context (extended state).\n */\nexport class FsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n> {\n protected readonly logger_: AlwatrLogger;\n\n /** The public, read-only state signal. Subscribe to react to state changes. */\n public readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;\n\n /**\n * The FIFO event mailbox. Events are processed strictly in dispatch order.\n */\n private readonly mailbox__: TEvent[] = [];\n\n /**\n * RTC re-entrancy guard. While `true`, an active loop is draining the mailbox;\n * re-entrant dispatches just enqueue and return.\n */\n private processing__ = true;\n\n /** Set once by `destroy()`. All dispatches after destruction are ignored (and logged). */\n private destroyed__ = false;\n\n /**\n * Cleanup callbacks for currently active state actors, in spawn order.\n * Executed in REVERSE (LIFO) order on state exit — standard resource semantics\n * (last acquired, first released).\n */\n private readonly activeActorCleanups__: (() => void)[] = [];\n\n private readonly stateSignal__:\n | StateSignal<MachineState<TState, TContext>>\n | PersistentStateSignal<MachineState<TState, TContext>>;\n\n constructor(\n protected readonly config_: StateMachineConfig<TState, TEvent, TContext>,\n stateSignal?: StateSignal<MachineState<TState, TContext>> | PersistentStateSignal<MachineState<TState, TContext>>,\n ) {\n this.logger_ = createLogger(`fsm:${this.config_.name}`);\n DEV_MODE && this.logger_.logMethodArgs?.('constructor', config_);\n\n const initialValue: MachineState<TState, TContext> = {\n name: config_.initial,\n context: config_.context,\n };\n this.stateSignal__ =\n stateSignal\n ?? (config_.persistent ?\n createPersistentStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config_.name}`,\n storageKey: config_.persistent.storageKey ?? config_.name,\n initialValue,\n schemaVersion: config_.persistent.schemaVersion,\n })\n : createStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config_.name}`,\n initialValue,\n }));\n\n this.stateSignal = this.stateSignal__.asReadonly();\n\n // Execute initial/rehydrated state entry effects and spawn its actors.\n queueMicrotask(() => this.start_());\n }\n\n /**\n * Synchronous accessor for the current machine state.\n * Prefer `stateSignal.subscribe()` for reactive consumers; use this getter for\n * imperative checks inside controllers/services.\n */\n public get state(): MachineState<TState, TContext> {\n return this.stateSignal__.get();\n }\n\n /**\n * Convenience predicate: returns true if the current finite state matches any\n * of the given names. Sugar for `service.state.name === 'x' || ...`.\n */\n public matches(...names: TState[]): boolean {\n return names.includes(this.stateSignal__.get().name);\n }\n\n /**\n * Dispatches an event to the FSM mailbox.\n *\n * Events are processed with Run-to-Completion semantics: if dispatched while a\n * transition is in flight (re-entrant dispatch from a guard/effect/actor), the\n * event is enqueued and processed deterministically right after the current\n * transition completes — in the same call stack, in FIFO order, with no loss.\n *\n * @param event The event to process.\n */\n public readonly dispatch = (event: TEvent): void => {\n DEV_MODE && this.logger_.logMethodArgs?.('dispatch', {event});\n this.mailbox__.push(event);\n this.processMailbox__();\n };\n\n private processMailbox__(): void {\n // RTC guard: an active loop is already draining the mailbox; it will pick\n // this event up after the current transition finishes.\n if (this.processing__) return;\n DEV_MODE && this.logger_.logMethod?.('processMailbox__');\n if (this.destroyed__) {\n DEV_MODE && this.logger_.incident?.('dispatch', 'dispatch_after_destroy');\n return;\n }\n this.processing__ = true;\n try {\n // Do NOT cache length. New events may be added during processing, and they MUST be processed in the same order (FIFO).\n for (let index = 0; index < this.mailbox__.length; index++) {\n this.processTransition__(this.mailbox__[index]);\n if (this.destroyed__) break;\n }\n } finally {\n this.processing__ = false;\n this.mailbox__.length = 0;\n }\n }\n\n /**\n * The core FSM logic that processes a single event and transitions the machine to a new state.\n * This step is atomic: exit effects -> assigners -> state commit -> entry effects -> actors.\n *\n * @param event The event to process.\n */\n private processTransition__(event: TEvent): void {\n const currentState = this.stateSignal__.get();\n DEV_MODE && this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});\n\n const transition = this.findTransition__(event, currentState);\n\n if (!transition) {\n DEV_MODE\n && this.logger_.incident?.('processTransition__', 'ignored_event', 'No valid transition found for event', {\n state: currentState.name,\n event,\n });\n return; // Event ignored, no transition occurs.\n }\n\n const targetStateName = transition.target ?? currentState.name;\n const isExternalTransition = transition.target !== undefined;\n\n // 1. External transition: run exit effects (with the OLD context, per SCXML semantics) and tear down the current state's actors.\n if (isExternalTransition) {\n this.executeEffects__(event, currentState.context, this.config_.states[currentState.name]?.exit);\n this.cleanupActors__();\n }\n\n // 2. Apply assigners to compute the next context (pure, atomic).\n const nextContext = this.applyAssigners__(event, currentState.context, transition.assigner);\n\n // 3. Commit the new state, notifying all subscribers (async via signal layer).\n const nextState: MachineState<TState, TContext> = {\n name: targetStateName,\n context: nextContext,\n };\n this.stateSignal__.set(nextState);\n\n // 4. External transition: run entry effects (with the NEW context) and spawn the target state's actors.\n if (isExternalTransition) {\n this.executeEffects__(event, nextState.context, this.config_.states[nextState.name]?.entry);\n this.spawnActors__(event, nextState.context, this.config_.states[nextState.name]?.actor);\n }\n }\n\n /**\n * Finds the first valid transition for the given event by evaluating guards in declaration order. A guard-less transition acts as an unconditional fallback.\n *\n * @param event The triggering event.\n * @param currentState The current state of the machine.\n * @returns The first matching transition or `undefined` if none are found.\n */\n private findTransition__(\n event: TEvent,\n currentState: MachineState<TState, TContext>,\n ): Transition<TState, TEvent, TContext> | undefined {\n DEV_MODE && this.logger_.logMethod?.('findTransition__');\n\n const currentStateConfig = this.config_.states[currentState.name];\n const transitions = currentStateConfig?.on?.[event.type as TEvent['type']] as\n | SingleOrArray<Transition<TState, TEvent, TContext>>\n | undefined;\n\n if (!transitions) return undefined;\n\n if (!Array.isArray(transitions)) {\n if (!transitions.guard) return transitions; // Unconditional fallback branch.\n try {\n if (transitions.guard(event, currentState.context)) {\n return transitions;\n }\n } catch (error) {\n this.logger_.error('findTransition__', 'guard_failed', error, {\n state: currentState.name,\n eventType: event.type,\n });\n }\n return undefined;\n }\n\n // else if transitions is an array\n\n for (let index = 0; index < transitions.length; index++) {\n const transition = transitions[index];\n if (!transition.guard) return transition; // Unconditional fallback branch.\n try {\n if (transition.guard(event, currentState.context)) {\n return transition;\n }\n } catch (error) {\n this.logger_.error('findTransition__', 'guard_failed', error, {\n state: currentState.name,\n eventType: event.type,\n index,\n });\n // Treated as guard === false: continue evaluating the next branch.\n }\n }\n\n return undefined;\n }\n\n /**\n * Sequentially executes a list of synchronous effects (side-effects).\n * Errors are caught and logged without stopping the FSM.\n *\n * @param event The event that triggered these effects.\n * @param context The context at the time of execution.\n * @param effects A single effect or an array of effects.\n */\n private executeEffects__(\n event: TEvent,\n context: Readonly<TContext>,\n effects?: SingleOrArray<Effect<TEvent, TContext>>,\n ): void {\n if (!effects) {\n DEV_MODE && this.logger_.logMethod?.('executeEffects__.skipped');\n return;\n }\n\n DEV_MODE && this.logger_.logMethod?.('executeEffects__');\n\n if (!Array.isArray(effects)) {\n try {\n effects(event, context);\n } catch (error) {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n event,\n context,\n });\n }\n return;\n }\n\n // else if effects is an array\n\n for (let index = 0; index < effects.length; index++) {\n const effect = effects[index];\n try {\n effect(event, context);\n } catch (error) {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n event,\n context,\n index,\n });\n }\n }\n }\n\n /**\n * Applies all assigner functions to the context to produce a new, updated context.\n *\n * This process is atomic (all-or-nothing): if any assigner throws, the original\n * context is returned and all updates are discarded.\n *\n * @param event The event that triggered the transition.\n * @param context The current context.\n * @param assigners A single assigner or an array of assigners.\n * @returns The new, updated context, or the original context if any assigner fails.\n */\n private applyAssigners__(\n event: TEvent,\n context: TContext,\n assigners?: SingleOrArray<Assigner<TEvent, TContext>>,\n ): TContext {\n if (!assigners) {\n DEV_MODE && this.logger_.logMethod?.('applyAssigners__.skipped');\n return context;\n }\n\n DEV_MODE && this.logger_.logMethod?.('applyAssigners__');\n\n if (!Array.isArray(assigners)) {\n try {\n return assigners(event, context) ?? context;\n } catch (error) {\n this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {event, context});\n }\n return context;\n }\n\n // else if assigners is an array\n\n try {\n let accContext = context;\n for (let index = 0; index < assigners.length; index++) {\n const assigner = assigners[index];\n const nextContext = assigner(event, accContext);\n if (nextContext) {\n accContext = nextContext;\n }\n }\n return accContext;\n } catch (error) {\n this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {event, context});\n // On ANY failure, discard all changes and return the original context.\n return context;\n }\n }\n\n /**\n * Starts the FSM by executing the entry effects and spawning the actors of the\n * initial (or rehydrated) state, using the synthetic `{type: '__init__'}` event.\n */\n protected start_(): void {\n if (this.destroyed__) return;\n DEV_MODE && this.logger_.logMethod?.('start_');\n const currentState = this.stateSignal__.get();\n const initEvent = {type: '__init__'} as unknown as TEvent;\n this.executeEffects__(initEvent, currentState.context, this.config_.states[currentState.name]?.entry);\n this.spawnActors__(initEvent, currentState.context, this.config_.states[currentState.name]?.actor);\n this.processing__ = false; // Allow processing of dispatched events after the initial setup is complete.\n if (this.mailbox__.length > 0) {\n this.processMailbox__(); // Process any events that were dispatched during the initial setup.\n }\n }\n\n /**\n * Spawns all configured actors for the entered state.\n */\n private spawnActors__(\n event: TEvent,\n context: Readonly<TContext>,\n actors?: SingleOrArray<Actor<TEvent, TContext>>,\n ): void {\n if (!actors) {\n DEV_MODE && this.logger_.logMethod?.('spawnActors__.skipped');\n return;\n }\n\n DEV_MODE && this.logger_.logMethod?.('spawnActors__');\n\n if (!Array.isArray(actors)) {\n try {\n const cleanup = actors(context, this.dispatch);\n if (typeof cleanup === 'function') {\n this.activeActorCleanups__.push(cleanup);\n }\n } catch (error) {\n this.logger_.error('spawnActors__', 'actor_failed', error, {event, context});\n }\n return;\n }\n\n // else if actors is an array\n\n for (let index = 0; index < actors.length; index++) {\n const actor = actors[index];\n try {\n const cleanup = actor(context, this.dispatch);\n if (typeof cleanup === 'function') {\n this.activeActorCleanups__.push(cleanup);\n }\n } catch (error) {\n this.logger_.error('spawnActors__', 'actor_failed', error, {event, context, index});\n }\n }\n }\n\n /**\n * Cleans up (destroys) all currently active state actors in REVERSE (LIFO) spawn order — standard resource-release semantics.\n */\n private cleanupActors__(): void {\n DEV_MODE && this.logger_.logMethodArgs?.('cleanupActors__', {count: this.activeActorCleanups__.length});\n for (let index = this.activeActorCleanups__.length - 1; index >= 0; index--) {\n try {\n this.activeActorCleanups__[index]();\n } catch (error) {\n this.logger_.error('cleanupActors__', 'cleanup_failed', error, {index});\n }\n }\n this.activeActorCleanups__.length = 0;\n }\n\n /**\n * Destroys the service, cleaning up actors, the mailbox, and owned signals to\n * prevent memory leaks. Idempotent — safe to call multiple times.\n *\n * @param destroyState If `true` (default), also destroys the state signal, preventing any future subscriptions or updates. Set to `false` to preserve the last state value for late subscribers even after destruction.\n */\n public destroy(destroyState = true): void {\n if (this.destroyed__) return;\n DEV_MODE && this.logger_.logMethod?.('destroy');\n this.destroyed__ = true;\n this.mailbox__.length = 0;\n this.cleanupActors__();\n if (destroyState) {\n this.stateSignal__.destroy();\n }\n }\n}\n",
6
+ "import {FsmService} from './fsm-service.js';\n\nimport type {MachineEvent, StateMachineConfig} from './type.js';\n\n/**\n * A simple and clean factory function for creating an `FsmService` instance.\n * This is the recommended way to instantiate a new state machine.\n *\n * @template TState - The union type of all possible states.\n * @template TEvent - The union type of all possible events.\n * @template TContext - The type of the machine's context.\n *\n * @param config - The machine's configuration object.\n * @returns A new, ready-to-use instance of `FsmService`.\n *\n * @example\n * ```ts\n * import {createFsmService} from '@alwatr/fsm';\n * import type {StateMachineConfig} from '@alwatr/fsm';\n *\n * // 1. Define types\n * type LightContext = {brightness: number};\n * type LightState = 'on' | 'off';\n * type LightEvent = {type: 'TOGGLE'} | {type: 'SET_BRIGHTNESS'; level: number};\n *\n * // 2. Config the state machine\n * const lightMachineConfig: StateMachineConfig<LightState, LightEvent, LightContext> = {\n * name: 'light-switch',\n * initial: 'off',\n * context: {brightness: 0},\n * states: {\n * off: {\n * on: {\n * TOGGLE: {\n * target: 'on',\n * assigners: [({context}) => ({...context, brightness: 100})],\n * },\n * },\n * },\n * on: {\n * on: {\n * TOGGLE: {target: 'off', assigners: [({context}) => ({...context, brightness: 0})]},\n * SET_BRIGHTNESS: {assigners: [({context, event}) => ({...context, brightness: event.level})]},\n * },\n * },\n * },\n * };\n *\n * // 3. Create the service\n * const lightService = createFsmService(lightMachineConfig);\n *\n * // 4. Use it in your application\n * lightService.stateSignal.subscribe((state) => {\n * console.log(`Light is ${state.name} with brightness ${state.context.brightness}`);\n * });\n *\n * lightService.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100\n *\n * lightService.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50\n *\n * // 5. Cleanup\n * // lightService.destroy();\n * ```\n */\nexport function createFsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext> {\n return new FsmService(config);\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "BC19972D6D3E964E64756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "21C5CE2D42EDADD164756E2164756E21",
10
10
  "names": []
11
11
  }
package/dist/type.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Awaitable, SingleOrArray } from '@alwatr/type-helper';
1
+ import type { JsonValue, SingleOrArray } from '@alwatr/type-helper';
2
2
  import type { SignalConfig } from '@alwatr/signal';
3
3
  /**
4
4
  * Represents the state of a state machine, including its current finite state value
@@ -22,61 +22,73 @@ export type MachineState<TState extends string, TContext extends Record<string,
22
22
  export interface MachineEvent<TEventType extends string = string> {
23
23
  /** The unique type of the event. */
24
24
  readonly type: TEventType;
25
- /** An event can carry an optional payload. */
26
- [key: string]: unknown;
25
+ /** An event can carry an optional, serializable payload. */
26
+ [key: string]: JsonValue;
27
27
  }
28
28
  /**
29
- * Defines an assigner (synchronous action) that updates the context during transitions.
30
- * It returns the complete new context object or void/undefined if no changes are made.
29
+ * Defines an assigner a **pure, synchronous context reducer** applied during transitions.
31
30
  *
32
- * @template TContext The type of the machine's context.
33
- * @template TEvent The type of the event that triggered this assigner.
34
- * @returns The complete next context object or void.
31
+ * @param event The event that triggered the transition. Readonly to prevent mutations.
32
+ * @param context The current context before the transition. Mutable for convenience, but treat it as immutable — return a new context object instead of mutating it.
33
+ * @returns The complete next context object, or void.
35
34
  */
36
- export type Assigner<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
37
- readonly event: Readonly<TEvent>;
38
- readonly context: TContext;
39
- }) => TContext | void;
35
+ export type Assigner<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (event: Readonly<TEvent>, context: TContext) => TContext | void;
40
36
  /**
41
- * Defines an effect (fire-and-forget side-effect action) executed on state entry/exit.
42
- * It can interact with the outside world, but does not return new events to trigger transitions.
37
+ * Defines an effect — a **strictly synchronous**, fire-and-forget side-effect
38
+ * executed on state entry/exit.
43
39
  *
44
- * @template TContext The type of the machine's context.
45
- * @template TEvent The type of the event that triggered this effect.
46
- * @returns void or a Promise<void>.
40
+ * ## Why synchronous-only? (Architectural decision)
41
+ *
42
+ * The FSM core is a deterministic, Run-to-Completion (RTC) step function:
43
+ * `(state, event) -> (state', effects)`. Allowing async effects inside the core
44
+ * creates ordering ambiguity — the continuation of an async effect may run against
45
+ * a state/context that no longer exists. This mirrors the design of SCXML actions,
46
+ * XState actions, and Erlang's gen_statem.
47
+ *
48
+ * **Any asynchronous work belongs in an {@link Actor}**, which has a proper
49
+ * lifecycle (spawn on entry, cleanup on exit) and communicates results back to
50
+ * the machine via `dispatch`, keeping the core deterministic.
51
+ *
52
+ * @param event The event that triggered the effect. Readonly to prevent mutations.
53
+ * @param context The current context of the machine. Readonly to prevent mutations.
47
54
  */
48
- export type Effect<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
49
- readonly event: Readonly<TEvent>;
50
- readonly context: Readonly<TContext>;
51
- }) => Awaitable<void>;
55
+ export type Effect<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (event: Readonly<TEvent>, context: Readonly<TContext>) => void;
52
56
  /**
53
57
  * Defines a conditional guard function for a transition.
54
58
  * The transition is only taken if this function returns true.
55
59
  *
56
- * @template TContext The type of the machine's context.
57
- * @template TEvent The type of the event.
60
+ * Guards MUST be pure and synchronous. A guard that throws is treated as `false`
61
+ * (logged, transition branch skipped) so a single faulty predicate cannot brick
62
+ * the machine.
63
+ *
64
+ * @param event The event that triggered the transition. Readonly to prevent mutations.
65
+ * @param context The current context of the machine. Readonly to prevent mutations.
58
66
  * @returns `true` if the transition should be taken, `false` otherwise.
59
67
  */
60
- export type Guard<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
61
- readonly event: Readonly<TEvent>;
62
- readonly context: Readonly<TContext>;
63
- }) => boolean;
68
+ export type Guard<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (event: Readonly<TEvent>, context: Readonly<TContext>) => boolean;
64
69
  /**
65
- * Defines an actor (asynchronous lifecycle process) invoked on state entry.
66
- * It starts an operation and can send events back to the parent FSM via `dispatch`.
67
- * It can return a cleanup function to be called when exiting the state or destroying the machine.
70
+ * Defines an actor — an **asynchronous lifecycle process** spawned on state entry.
71
+ *
72
+ * This is the ONLY sanctioned home for async work in the machine (network requests,
73
+ * polling intervals, websocket listeners, timers). An actor:
74
+ *
75
+ * 1. Is spawned when the machine enters the state.
76
+ * 2. Receives `dispatch` to asynchronously send events back to the parent FSM.
77
+ * 3. May return a synchronous cleanup function, executed automatically (in LIFO
78
+ * order) when the machine exits the state or is destroyed.
68
79
  *
69
80
  * @template TEvent The union type of all events in the machine.
70
81
  * @template TContext The type of the machine's context.
71
82
  */
72
- export type Actor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
73
- readonly event: Readonly<TEvent>;
74
- readonly context: Readonly<TContext>;
75
- readonly dispatch: (event: TEvent) => void;
76
- }) => (() => void) | void;
83
+ export type Actor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (context: Readonly<TContext>, dispatch: (event: TEvent) => void) => VoidFunction | void;
77
84
  /**
78
85
  * Defines a transition for a given state and event. It specifies the target state,
79
- * actions, and an optional guard.
86
+ * assigners, and an optional guard.
87
+ *
88
+ * - With `target`: an **external** transition — exit effects run, actors are cleaned
89
+ * up, then entry effects run and actors are re-spawned (even on self-transitions).
90
+ * - Without `target`: an **internal** transition — only assigners run; entry/exit
91
+ * effects and actors are untouched.
80
92
  *
81
93
  * @template TState The type of the state.
82
94
  * @template TEvent The type of the event.
@@ -87,8 +99,8 @@ export interface Transition<TState extends string, TEvent extends MachineEvent,
87
99
  readonly target?: TState;
88
100
  /** A guard function that must return true for the transition to occur. */
89
101
  readonly guard?: Guard<TEvent, TContext>;
90
- /** An array of assigners to execute. These update context synchronously. */
91
- readonly assigners?: SingleOrArray<Assigner<TEvent, TContext>>;
102
+ /** A single assigner or an ordered chain of assigners. Applied atomically. */
103
+ readonly assigner?: SingleOrArray<Assigner<TEvent, TContext>>;
92
104
  }
93
105
  /**
94
106
  * Configuration options for persisting the FSM state in localStorage.
@@ -109,6 +121,14 @@ export interface FsmPersistenceConfig {
109
121
  * The declarative configuration object for creating a state machine.
110
122
  * This object defines the entire behavior of the machine.
111
123
  *
124
+ * ## Persistence requirement
125
+ *
126
+ * When `persistent` is enabled, EVERY state — including terminal states with no
127
+ * transitions — MUST be declared in `states` (e.g. `success: {}`). The engine uses
128
+ * the presence of a state's config entry to validate rehydrated state names from
129
+ * storage; an undeclared state is treated as removed/renamed and the machine is
130
+ * reset to `initial`.
131
+ *
112
132
  * @template TState The union type of all possible states.
113
133
  * @template TEvent The union type of all possible events.
114
134
  * @template TContext The type of the machine's context.
@@ -116,7 +136,7 @@ export interface FsmPersistenceConfig {
116
136
  export interface StateMachineConfig<TState extends string, TEvent extends MachineEvent, TContext extends Record<string, unknown>> extends Pick<SignalConfig, 'name'> {
117
137
  /** The initial finite state value. */
118
138
  readonly initial: TState;
119
- /** The initial context (extended state) of the machine. */
139
+ /** The initial context (extended state) of the machine. Must be serializable. */
120
140
  readonly context: TContext;
121
141
  /** If provided, the FSM's state will be persisted in localStorage. */
122
142
  persistent?: FsmPersistenceConfig;
@@ -129,12 +149,12 @@ export interface StateMachineConfig<TState extends string, TEvent extends Machin
129
149
  type: E;
130
150
  }>, TContext>>;
131
151
  };
132
- /** An array of side-effect effects to execute upon entering this state. */
152
+ /** Synchronous side-effects executed upon entering this state. */
133
153
  readonly entry?: SingleOrArray<Effect<TEvent, TContext>>;
134
- /** An array of side-effect effects to execute upon exiting this state. */
154
+ /** Synchronous side-effects executed upon exiting this state. */
135
155
  readonly exit?: SingleOrArray<Effect<TEvent, TContext>>;
136
- /** An array of actors to spawn upon entering this state, cleaned up when leaving. */
137
- readonly actors?: SingleOrArray<Actor<TEvent, TContext>>;
156
+ /** Async lifecycle actors spawned upon entering this state, cleaned up (LIFO) when leaving. */
157
+ readonly actor?: SingleOrArray<Actor<TEvent, TContext>>;
138
158
  };
139
159
  };
140
160
  }
@@ -1 +1 @@
1
- {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"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"}