@alwatr/fsm 9.33.0 → 9.34.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.
@@ -0,0 +1,5 @@
1
+ /* 📦 @alwatr/fsm v9.34.0 */
2
+ import{createLogger as U}from"@alwatr/logger";import{queueMicrotask as W}from"@alwatr/delay";import{createPersistentStateSignal as X,createStateSignal as Y}from"@alwatr/signal";class R{config_;logger_;stateSignal;mailbox__=[];processing__=!0;destroyed__=!1;activeActorCleanups__=[];stateSignal__;constructor(j,z){this.config_=j;this.logger_=U(`fsm:${this.config_.name}`),this.logger_.logMethodArgs?.("constructor",j);let J={name:j.initial,context:j.context};this.stateSignal__=z??(j.persistent?X({name:`fsm-state-${j.name}`,storageKey:j.persistent.storageKey??j.name,initialValue:J,schemaVersion:j.persistent.schemaVersion}):Y({name:`fsm-state-${j.name}`,initialValue:J})),this.stateSignal=this.stateSignal__.asReadonly(),W(()=>this.start_())}get state(){return this.stateSignal__.get()}matches(...j){return j.includes(this.stateSignal__.get().name)}dispatch=(j)=>{this.logger_.logMethodArgs?.("dispatch",{event:j}),this.mailbox__.push(j),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 j=0;j<this.mailbox__.length;j++)if(this.processTransition__(this.mailbox__[j]),this.destroyed__)break}finally{this.processing__=!1,this.mailbox__.length=0}}processTransition__(j){let z=this.stateSignal__.get();this.logger_.logMethodArgs?.("processTransition__",{state:z.name,event:j});let J=this.findTransition__(j,z);if(!J){this.logger_.incident?.("processTransition__","ignored_event","No valid transition found for event",{state:z.name,event:j});return}let B=J.target??z.name,K=J.target!==void 0;if(K)this.executeEffects__(j,z.context,this.config_.states[z.name]?.exit),this.cleanupActors__();let O=this.applyAssigners__(j,z.context,J.assigner),Q={name:B,context:O};if(this.stateSignal__.set(Q),K)this.executeEffects__(j,Q.context,this.config_.states[Q.name]?.entry),this.spawnActors__(j,Q.context,this.config_.states[Q.name]?.actor)}findTransition__(j,z){this.logger_.logMethod?.("findTransition__");let B=this.config_.states[z.name]?.on?.[j.type];if(!B)return;if(!Array.isArray(B)){if(!B.guard)return B;try{if(B.guard(j,z.context))return B}catch(K){this.logger_.error("findTransition__","guard_failed",K,{state:z.name,eventType:j.type})}return}for(let K=0;K<B.length;K++){let O=B[K];if(!O.guard)return O;try{if(O.guard(j,z.context))return O}catch(Q){this.logger_.error("findTransition__","guard_failed",Q,{state:z.name,eventType:j.type,index:K})}}return}executeEffects__(j,z,J){if(!J){this.logger_.logMethod?.("executeEffects__.skipped");return}if(this.logger_.logMethod?.("executeEffects__"),!Array.isArray(J)){try{J(j,z)}catch(B){this.logger_.error("executeEffects__","effect_failed",B,{event:j,context:z})}return}for(let B=0;B<J.length;B++){let K=J[B];try{K(j,z)}catch(O){this.logger_.error("executeEffects__","effect_failed",O,{event:j,context:z,index:B})}}}applyAssigners__(j,z,J){if(!J)return this.logger_.logMethod?.("applyAssigners__.skipped"),z;if(this.logger_.logMethod?.("applyAssigners__"),!Array.isArray(J)){try{return J(j,z)??z}catch(B){this.logger_.error("applyAssigners__","assigner_failed_atomic",B,{event:j,context:z})}return z}try{let B=z;for(let K=0;K<J.length;K++){let O=J[K],Q=O(j,B);if(Q)B=Q}return B}catch(B){return this.logger_.error("applyAssigners__","assigner_failed_atomic",B,{event:j,context:z}),z}}start_(){if(this.destroyed__)return;this.logger_.logMethod?.("start_");let j=this.stateSignal__.get(),z={type:"__init__"};if(this.executeEffects__(z,j.context,this.config_.states[j.name]?.entry),this.spawnActors__(z,j.context,this.config_.states[j.name]?.actor),this.processing__=!1,this.mailbox__.length>0)this.processMailbox__()}spawnActors__(j,z,J){if(!J){this.logger_.logMethod?.("spawnActors__.skipped");return}if(this.logger_.logMethod?.("spawnActors__"),!Array.isArray(J)){try{let B=J(z,this.dispatch);if(typeof B==="function")this.activeActorCleanups__.push(B)}catch(B){this.logger_.error("spawnActors__","actor_failed",B,{event:j,context:z})}return}for(let B=0;B<J.length;B++){let K=J[B];try{let O=K(z,this.dispatch);if(typeof O==="function")this.activeActorCleanups__.push(O)}catch(O){this.logger_.error("spawnActors__","actor_failed",O,{event:j,context:z,index:B})}}}cleanupActors__(){this.logger_.logMethodArgs?.("cleanupActors__",{count:this.activeActorCleanups__.length});for(let j=this.activeActorCleanups__.length-1;j>=0;j--)try{this.activeActorCleanups__[j]()}catch(z){this.logger_.error("cleanupActors__","cleanup_failed",z,{index:j})}this.activeActorCleanups__.length=0}destroy(j=!0){if(this.destroyed__)return;if(this.logger_.logMethod?.("destroy"),this.destroyed__=!0,this.mailbox__.length=0,this.cleanupActors__(),j)this.stateSignal__.destroy()}}function N(j){return new R(j)}function P(j){return j}function k(){return{defineFsmConfig:(j)=>j,defineStateActor:(j)=>j,defineStateActors:(j)=>j,defineEffect:(j)=>j,defineEffects:(j)=>j,defineAssigner:(j)=>j,defineAssigners:(j)=>j,defineGuard:(j)=>j,defineGuards:(j)=>j,not:(j)=>(z,J)=>!j(z,J),and:(...j)=>(z,J)=>j.every((B)=>B(z,J)),or:(...j)=>(z,J)=>j.some((B)=>B(z,J))}}export{P as defineFsmConfig,N as createFsmService,k as createFsmHelpers,R as FsmService};
3
+
4
+ //# debugId=361CBC325832FAA464756E2164756E21
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, StateActor} 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<StateActor<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 {Assigner, Effect, Guard, MachineEvent, StateActor, 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\n/**\n * Utility for defining strongly-typed FSM configs with great DX.\n */\nexport function defineFsmConfig<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>(config: StateMachineConfig<TState, TEvent, TContext>): StateMachineConfig<TState, TEvent, TContext> {\n return config;\n}\n\n/**\n * Creates a set of type-safe helpers for defining StateActors, Effects, Assigners, and Guards bound to specific TEvent and TContext.\n * This avoids type casting (e.g. `as StateActor<TEvent, TContext>`) and provides contextual typing for function parameters.\n *\n * @template TEvent The union type of all events in the machine.\n * @template TContext The type of the machine's context.\n *\n * @example\n * ```ts\n * const {defineStateActors, defineEffects, defineAssigners, defineGuards, and, not} = createFsmHelpers<MyEvent, MyContext>();\n *\n * const actors = defineStateActors({\n * fetchData: (context, dispatch) => { ... } // context and dispatch are contextually typed!\n * });\n * ```\n */\nexport function createFsmHelpers<\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>() {\n return {\n defineFsmConfig: <TState extends string>(\n config: StateMachineConfig<TState, TEvent, TContext>,\n ): StateMachineConfig<TState, TEvent, TContext> => config,\n\n defineStateActor: (actor: StateActor<TEvent, TContext>): StateActor<TEvent, TContext> => actor,\n defineStateActors: <T extends Record<string, StateActor<TEvent, TContext>>>(actors: T): T => actors,\n\n defineEffect: (effect: Effect<TEvent, TContext>): Effect<TEvent, TContext> => effect,\n defineEffects: <T extends Record<string, Effect<TEvent, TContext>>>(effects: T): T => effects,\n\n defineAssigner: <E extends TEvent = TEvent>(assigner: Assigner<E, TContext>): Assigner<E, TContext> => assigner,\n defineAssigners: <T extends Record<string, Assigner<TEvent, TContext>>>(assigners: T): T => assigners,\n\n defineGuard: <E extends TEvent = TEvent>(guard: Guard<E, TContext>): Guard<E, TContext> => guard,\n defineGuards: <T extends Record<string, Guard<TEvent, TContext>>>(guards: T): T => guards,\n\n not:\n (guard: Guard<TEvent, TContext>): Guard<TEvent, TContext> =>\n (event, context) =>\n !guard(event, context),\n and:\n (...guards: Guard<TEvent, TContext>[]): Guard<TEvent, TContext> =>\n (event, context) =>\n guards.every((guard) => guard(event, context)),\n or:\n (...guards: Guard<TEvent, TContext>[]): Guard<TEvent, TContext> =>\n (event, context) =>\n guards.some((guard) => guard(event, context)),\n } as const;\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "361CBC325832FAA464756E2164756E21",
10
+ "names": []
11
+ }
package/dist/facade.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FsmService } from './fsm-service.js';
2
- import type { MachineEvent, StateMachineConfig } from './type.js';
2
+ import type { Assigner, Effect, Guard, MachineEvent, StateActor, StateMachineConfig } from './type.js';
3
3
  /**
4
4
  * A simple and clean factory function for creating an `FsmService` instance.
5
5
  * This is the recommended way to instantiate a new state machine.
@@ -61,4 +61,38 @@ import type { MachineEvent, StateMachineConfig } from './type.js';
61
61
  * ```
62
62
  */
63
63
  export declare function createFsmService<TState extends string, TEvent extends MachineEvent, TContext extends Record<string, unknown> = Record<string, never>>(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext>;
64
+ /**
65
+ * Utility for defining strongly-typed FSM configs with great DX.
66
+ */
67
+ export declare function defineFsmConfig<TState extends string, TEvent extends MachineEvent, TContext extends Record<string, unknown> = Record<string, never>>(config: StateMachineConfig<TState, TEvent, TContext>): StateMachineConfig<TState, TEvent, TContext>;
68
+ /**
69
+ * Creates a set of type-safe helpers for defining StateActors, Effects, Assigners, and Guards bound to specific TEvent and TContext.
70
+ * This avoids type casting (e.g. `as StateActor<TEvent, TContext>`) and provides contextual typing for function parameters.
71
+ *
72
+ * @template TEvent The union type of all events in the machine.
73
+ * @template TContext The type of the machine's context.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const {defineStateActors, defineEffects, defineAssigners, defineGuards, and, not} = createFsmHelpers<MyEvent, MyContext>();
78
+ *
79
+ * const actors = defineStateActors({
80
+ * fetchData: (context, dispatch) => { ... } // context and dispatch are contextually typed!
81
+ * });
82
+ * ```
83
+ */
84
+ export declare function createFsmHelpers<TEvent extends MachineEvent, TContext extends Record<string, unknown> = Record<string, never>>(): {
85
+ readonly defineFsmConfig: <TState extends string>(config: StateMachineConfig<TState, TEvent, TContext>) => StateMachineConfig<TState, TEvent, TContext>;
86
+ readonly defineStateActor: (actor: StateActor<TEvent, TContext>) => StateActor<TEvent, TContext>;
87
+ readonly defineStateActors: <T extends Record<string, StateActor<TEvent, TContext>>>(actors: T) => T;
88
+ readonly defineEffect: (effect: Effect<TEvent, TContext>) => Effect<TEvent, TContext>;
89
+ readonly defineEffects: <T extends Record<string, Effect<TEvent, TContext>>>(effects: T) => T;
90
+ readonly defineAssigner: <E extends TEvent = TEvent>(assigner: Assigner<E, TContext>) => Assigner<E, TContext>;
91
+ readonly defineAssigners: <T extends Record<string, Assigner<TEvent, TContext>>>(assigners: T) => T;
92
+ readonly defineGuard: <E extends TEvent = TEvent>(guard: Guard<E, TContext>) => Guard<E, TContext>;
93
+ readonly defineGuards: <T extends Record<string, Guard<TEvent, TContext>>>(guards: T) => T;
94
+ readonly not: (guard: Guard<TEvent, TContext>) => Guard<TEvent, TContext>;
95
+ readonly and: (...guards: Guard<TEvent, TContext>[]) => Guard<TEvent, TContext>;
96
+ readonly or: (...guards: Guard<TEvent, TContext>[]) => Guard<TEvent, TContext>;
97
+ };
64
98
  //# sourceMappingURL=facade.d.ts.map
@@ -1 +1 @@
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"}
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,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAC,MAAM,WAAW,CAAC;AAErG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,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,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAEpG;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;+BAG5C,MAAM,SAAS,MAAM,UAC7B,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,KACnD,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;uCAErB,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAG,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC;iCACjE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU,CAAC,KAAG,CAAC;oCAElE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;6BAC1D,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,WAAW,CAAC,KAAG,CAAC;8BAEjE,CAAC,SAAS,MAAM,qBAAqB,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAG,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC;+BACjF,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,aAAa,CAAC,KAAG,CAAC;2BAE1E,CAAC,SAAS,MAAM,kBAAkB,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAG,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;4BACxE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU,CAAC,KAAG,CAAC;0BAGrE,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;8BAI7C,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,KAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;6BAInD,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,KAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;EAIpE"}
@@ -1 +1 @@
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;IAwC3B;;;;;;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"}
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,EAA2C,MAAM,WAAW,CAAC;AAExH;;;;;;;;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.33.0 */
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",{event});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};
1
+ /* 📦 @alwatr/fsm v9.34.0 */
2
+ import{createLogger as U}from"@alwatr/logger";import{queueMicrotask as W}from"@alwatr/delay";import{createPersistentStateSignal as X,createStateSignal as Y}from"@alwatr/signal";class R{config_;logger_;stateSignal;mailbox__=[];processing__=!0;destroyed__=!1;activeActorCleanups__=[];stateSignal__;constructor(j,z){this.config_=j;this.logger_=U(`fsm:${this.config_.name}`);let J={name:j.initial,context:j.context};this.stateSignal__=z??(j.persistent?X({name:`fsm-state-${j.name}`,storageKey:j.persistent.storageKey??j.name,initialValue:J,schemaVersion:j.persistent.schemaVersion}):Y({name:`fsm-state-${j.name}`,initialValue:J})),this.stateSignal=this.stateSignal__.asReadonly(),W(()=>this.start_())}get state(){return this.stateSignal__.get()}matches(...j){return j.includes(this.stateSignal__.get().name)}dispatch=(j)=>{this.mailbox__.push(j),this.processMailbox__()};processMailbox__(){if(this.processing__)return;if(this.destroyed__)return;this.processing__=!0;try{for(let j=0;j<this.mailbox__.length;j++)if(this.processTransition__(this.mailbox__[j]),this.destroyed__)break}finally{this.processing__=!1,this.mailbox__.length=0}}processTransition__(j){let z=this.stateSignal__.get(),J=this.findTransition__(j,z);if(!J)return;let B=J.target??z.name,K=J.target!==void 0;if(K)this.executeEffects__(j,z.context,this.config_.states[z.name]?.exit),this.cleanupActors__();let O=this.applyAssigners__(j,z.context,J.assigner),Q={name:B,context:O};if(this.stateSignal__.set(Q),K)this.executeEffects__(j,Q.context,this.config_.states[Q.name]?.entry),this.spawnActors__(j,Q.context,this.config_.states[Q.name]?.actor)}findTransition__(j,z){let B=this.config_.states[z.name]?.on?.[j.type];if(!B)return;if(!Array.isArray(B)){if(!B.guard)return B;try{if(B.guard(j,z.context))return B}catch(K){this.logger_.error("findTransition__","guard_failed",K,{state:z.name,eventType:j.type})}return}for(let K=0;K<B.length;K++){let O=B[K];if(!O.guard)return O;try{if(O.guard(j,z.context))return O}catch(Q){this.logger_.error("findTransition__","guard_failed",Q,{state:z.name,eventType:j.type,index:K})}}return}executeEffects__(j,z,J){if(!J)return;if(!Array.isArray(J)){try{J(j,z)}catch(B){this.logger_.error("executeEffects__","effect_failed",B,{event:j,context:z})}return}for(let B=0;B<J.length;B++){let K=J[B];try{K(j,z)}catch(O){this.logger_.error("executeEffects__","effect_failed",O,{event:j,context:z,index:B})}}}applyAssigners__(j,z,J){if(!J)return z;if(!Array.isArray(J)){try{return J(j,z)??z}catch(B){this.logger_.error("applyAssigners__","assigner_failed_atomic",B,{event:j,context:z})}return z}try{let B=z;for(let K=0;K<J.length;K++){let O=J[K],Q=O(j,B);if(Q)B=Q}return B}catch(B){return this.logger_.error("applyAssigners__","assigner_failed_atomic",B,{event:j,context:z}),z}}start_(){if(this.destroyed__)return;let j=this.stateSignal__.get(),z={type:"__init__"};if(this.executeEffects__(z,j.context,this.config_.states[j.name]?.entry),this.spawnActors__(z,j.context,this.config_.states[j.name]?.actor),this.processing__=!1,this.mailbox__.length>0)this.processMailbox__()}spawnActors__(j,z,J){if(!J)return;if(!Array.isArray(J)){try{let B=J(z,this.dispatch);if(typeof B==="function")this.activeActorCleanups__.push(B)}catch(B){this.logger_.error("spawnActors__","actor_failed",B,{event:j,context:z})}return}for(let B=0;B<J.length;B++){let K=J[B];try{let O=K(z,this.dispatch);if(typeof O==="function")this.activeActorCleanups__.push(O)}catch(O){this.logger_.error("spawnActors__","actor_failed",O,{event:j,context:z,index:B})}}}cleanupActors__(){for(let j=this.activeActorCleanups__.length-1;j>=0;j--)try{this.activeActorCleanups__[j]()}catch(z){this.logger_.error("cleanupActors__","cleanup_failed",z,{index:j})}this.activeActorCleanups__.length=0}destroy(j=!0){if(this.destroyed__)return;if(this.destroyed__=!0,this.mailbox__.length=0,this.cleanupActors__(),j)this.stateSignal__.destroy()}}function N(j){return new R(j)}function P(j){return j}function k(){return{defineFsmConfig:(j)=>j,defineStateActor:(j)=>j,defineStateActors:(j)=>j,defineEffect:(j)=>j,defineEffects:(j)=>j,defineAssigner:(j)=>j,defineAssigners:(j)=>j,defineGuard:(j)=>j,defineGuards:(j)=>j,not:(j)=>(z,J)=>!j(z,J),and:(...j)=>(z,J)=>j.every((B)=>B(z,J)),or:(...j)=>(z,J)=>j.some((B)=>B(z,J))}}export{P as defineFsmConfig,N as createFsmService,k as createFsmHelpers,R as FsmService};
3
3
 
4
- //# debugId=8F76F872C92BD90364756E2164756E21
4
+ //# debugId=73D80100D4A3C43A64756E2164756E21
5
5
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/fsm-service.ts", "../src/facade.ts"],
4
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 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 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 this.logger_.logMethod?.('processMailbox__');\n if (this.destroyed__) {\n this.logger_.incident?.('dispatch', 'dispatch_after_destroy', {event});\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 this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});\n\n const transition = this.findTransition__(event, currentState);\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. 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 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 this.logger_.logMethod?.('executeEffects__.skipped');\n return;\n }\n\n 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 this.logger_.logMethod?.('applyAssigners__.skipped');\n return context;\n }\n\n 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 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 this.logger_.logMethod?.('spawnActors__.skipped');\n return;\n }\n\n 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 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 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"
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, StateActor} 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<StateActor<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 {Assigner, Effect, Guard, MachineEvent, StateActor, 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\n/**\n * Utility for defining strongly-typed FSM configs with great DX.\n */\nexport function defineFsmConfig<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>(config: StateMachineConfig<TState, TEvent, TContext>): StateMachineConfig<TState, TEvent, TContext> {\n return config;\n}\n\n/**\n * Creates a set of type-safe helpers for defining StateActors, Effects, Assigners, and Guards bound to specific TEvent and TContext.\n * This avoids type casting (e.g. `as StateActor<TEvent, TContext>`) and provides contextual typing for function parameters.\n *\n * @template TEvent The union type of all events in the machine.\n * @template TContext The type of the machine's context.\n *\n * @example\n * ```ts\n * const {defineStateActors, defineEffects, defineAssigners, defineGuards, and, not} = createFsmHelpers<MyEvent, MyContext>();\n *\n * const actors = defineStateActors({\n * fetchData: (context, dispatch) => { ... } // context and dispatch are contextually typed!\n * });\n * ```\n */\nexport function createFsmHelpers<\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>() {\n return {\n defineFsmConfig: <TState extends string>(\n config: StateMachineConfig<TState, TEvent, TContext>,\n ): StateMachineConfig<TState, TEvent, TContext> => config,\n\n defineStateActor: (actor: StateActor<TEvent, TContext>): StateActor<TEvent, TContext> => actor,\n defineStateActors: <T extends Record<string, StateActor<TEvent, TContext>>>(actors: T): T => actors,\n\n defineEffect: (effect: Effect<TEvent, TContext>): Effect<TEvent, TContext> => effect,\n defineEffects: <T extends Record<string, Effect<TEvent, TContext>>>(effects: T): T => effects,\n\n defineAssigner: <E extends TEvent = TEvent>(assigner: Assigner<E, TContext>): Assigner<E, TContext> => assigner,\n defineAssigners: <T extends Record<string, Assigner<TEvent, TContext>>>(assigners: T): T => assigners,\n\n defineGuard: <E extends TEvent = TEvent>(guard: Guard<E, TContext>): Guard<E, TContext> => guard,\n defineGuards: <T extends Record<string, Guard<TEvent, TContext>>>(guards: T): T => guards,\n\n not:\n (guard: Guard<TEvent, TContext>): Guard<TEvent, TContext> =>\n (event, context) =>\n !guard(event, context),\n and:\n (...guards: Guard<TEvent, TContext>[]): Guard<TEvent, TContext> =>\n (event, context) =>\n guards.every((guard) => guard(event, context)),\n or:\n (...guards: Guard<TEvent, TContext>[]): Guard<TEvent, TContext> =>\n (event, context) =>\n guards.some((guard) => guard(event, context)),\n } as const;\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "8F76F872C92BD90364756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "73D80100D4A3C43A64756E2164756E21",
10
10
  "names": []
11
11
  }
package/dist/type.d.ts CHANGED
@@ -45,7 +45,7 @@ export type Assigner<TEvent extends MachineEvent, TContext extends Record<string
45
45
  * a state/context that no longer exists. This mirrors the design of SCXML actions,
46
46
  * XState actions, and Erlang's gen_statem.
47
47
  *
48
- * **Any asynchronous work belongs in an {@link Actor}**, which has a proper
48
+ * **Any asynchronous work belongs in an {@link StateActor}**, which has a proper
49
49
  * lifecycle (spawn on entry, cleanup on exit) and communicates results back to
50
50
  * the machine via `dispatch`, keeping the core deterministic.
51
51
  *
@@ -67,10 +67,10 @@ export type Effect<TEvent extends MachineEvent, TContext extends Record<string,
67
67
  */
68
68
  export type Guard<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (event: Readonly<TEvent>, context: Readonly<TContext>) => boolean;
69
69
  /**
70
- * Defines an actor — an **asynchronous lifecycle process** spawned on state entry.
70
+ * Defines a state actor — an **asynchronous lifecycle process** spawned on state entry.
71
71
  *
72
72
  * This is the ONLY sanctioned home for async work in the machine (network requests,
73
- * polling intervals, websocket listeners, timers). An actor:
73
+ * polling intervals, websocket listeners, timers). A state actor:
74
74
  *
75
75
  * 1. Is spawned when the machine enters the state.
76
76
  * 2. Receives `dispatch` to asynchronously send events back to the parent FSM.
@@ -80,7 +80,7 @@ export type Guard<TEvent extends MachineEvent, TContext extends Record<string, u
80
80
  * @template TEvent The union type of all events in the machine.
81
81
  * @template TContext The type of the machine's context.
82
82
  */
83
- export type Actor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (context: Readonly<TContext>, dispatch: (event: TEvent) => void) => VoidFunction | void;
83
+ export type StateActor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (context: Readonly<TContext>, dispatch: (event: TEvent) => void) => VoidFunction | void;
84
84
  /**
85
85
  * Defines a transition for a given state and event. It specifies the target state,
86
86
  * assigners, and an optional guard.
@@ -154,7 +154,7 @@ export interface StateMachineConfig<TState extends string, TEvent extends Machin
154
154
  /** Synchronous side-effects executed upon exiting this state. */
155
155
  readonly exit?: SingleOrArray<Effect<TEvent, TContext>>;
156
156
  /** Async lifecycle actors spawned upon entering this state, cleaned up (LIFO) when leaving. */
157
- readonly actor?: SingleOrArray<Actor<TEvent, TContext>>;
157
+ readonly actor?: SingleOrArray<StateActor<TEvent, TContext>>;
158
158
  };
159
159
  };
160
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwatr/fsm",
3
- "version": "9.33.0",
3
+ "version": "9.34.0",
4
4
  "description": "A tiny, type-safe, declarative, and reactive finite state machine (FSM) library for modern TypeScript applications, built on top of Alwatr Signals.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
@@ -15,36 +15,39 @@
15
15
  "exports": {
16
16
  ".": {
17
17
  "types": "./dist/main.d.ts",
18
+ "development": "./dist/dev/main.js",
18
19
  "import": "./dist/main.js",
19
20
  "default": "./dist/main.js"
20
21
  }
21
22
  },
22
23
  "sideEffects": false,
23
24
  "dependencies": {
24
- "@alwatr/delay": "9.33.0",
25
- "@alwatr/logger": "9.33.0",
26
- "@alwatr/signal": "9.33.0"
25
+ "@alwatr/delay": "9.33.1",
26
+ "@alwatr/logger": "9.33.1",
27
+ "@alwatr/signal": "9.33.1"
27
28
  },
28
29
  "devDependencies": {
29
- "@alwatr/nano-build": "9.25.0",
30
+ "@alwatr/nano-build": "9.33.1",
30
31
  "@alwatr/standard": "9.33.0",
31
- "@alwatr/type-helper": "9.14.0",
32
+ "@alwatr/type-helper": "9.33.1",
32
33
  "typescript": "^6.0.3"
33
34
  },
34
35
  "scripts": {
35
36
  "b": "bun run build",
36
37
  "build": "bun run build:ts && bun run build:es",
37
- "build:es": "nano-build --preset=module src/main.ts",
38
+ "build:es": "bun run build:es:dev && bun run build:es:prod",
39
+ "build:es:dev": "nano-build --preset=module --outdir=dist/dev src/main.ts",
40
+ "build:es:prod": "NODE_ENV=production nano-build --preset=module --outdir=dist src/main.ts",
38
41
  "build:ts": "tsc --build",
39
42
  "cl": "bun run clean",
40
43
  "clean": "rm -rfv dist *.tsbuildinfo",
41
44
  "format": "prettier --write \"src/**/*.ts\"",
42
45
  "lint": "eslint src/ --ext .ts",
43
46
  "t": "bun run test",
44
- "test": "ALWATR_DEBUG=0 bun test",
47
+ "test": "bun test",
45
48
  "w": "bun run watch",
46
49
  "watch": "bun run watch:ts & bun run watch:es",
47
- "watch:es": "bun run build:es --watch",
50
+ "watch:es": "bun run build:es:dev --watch",
48
51
  "watch:ts": "bun run build:ts --watch --preserveWatchOutput"
49
52
  },
50
53
  "files": [
@@ -66,5 +69,5 @@
66
69
  "state",
67
70
  "typescript"
68
71
  ],
69
- "gitHead": "ba7e63efa62c86c7845269f94c4cc9924c38ff21"
72
+ "gitHead": "6f7846523d08dabaea385bf4a7898aefd928ab8d"
70
73
  }
package/src/facade.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {FsmService} from './fsm-service.js';
2
2
 
3
- import type {MachineEvent, StateMachineConfig} from './type.js';
3
+ import type {Assigner, Effect, Guard, MachineEvent, StateActor, StateMachineConfig} from './type.js';
4
4
 
5
5
  /**
6
6
  * A simple and clean factory function for creating an `FsmService` instance.
@@ -69,3 +69,66 @@ export function createFsmService<
69
69
  >(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext> {
70
70
  return new FsmService(config);
71
71
  }
72
+
73
+ /**
74
+ * Utility for defining strongly-typed FSM configs with great DX.
75
+ */
76
+ export function defineFsmConfig<
77
+ TState extends string,
78
+ TEvent extends MachineEvent,
79
+ TContext extends Record<string, unknown> = Record<string, never>,
80
+ >(config: StateMachineConfig<TState, TEvent, TContext>): StateMachineConfig<TState, TEvent, TContext> {
81
+ return config;
82
+ }
83
+
84
+ /**
85
+ * Creates a set of type-safe helpers for defining StateActors, Effects, Assigners, and Guards bound to specific TEvent and TContext.
86
+ * This avoids type casting (e.g. `as StateActor<TEvent, TContext>`) and provides contextual typing for function parameters.
87
+ *
88
+ * @template TEvent The union type of all events in the machine.
89
+ * @template TContext The type of the machine's context.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const {defineStateActors, defineEffects, defineAssigners, defineGuards, and, not} = createFsmHelpers<MyEvent, MyContext>();
94
+ *
95
+ * const actors = defineStateActors({
96
+ * fetchData: (context, dispatch) => { ... } // context and dispatch are contextually typed!
97
+ * });
98
+ * ```
99
+ */
100
+ export function createFsmHelpers<
101
+ TEvent extends MachineEvent,
102
+ TContext extends Record<string, unknown> = Record<string, never>,
103
+ >() {
104
+ return {
105
+ defineFsmConfig: <TState extends string>(
106
+ config: StateMachineConfig<TState, TEvent, TContext>,
107
+ ): StateMachineConfig<TState, TEvent, TContext> => config,
108
+
109
+ defineStateActor: (actor: StateActor<TEvent, TContext>): StateActor<TEvent, TContext> => actor,
110
+ defineStateActors: <T extends Record<string, StateActor<TEvent, TContext>>>(actors: T): T => actors,
111
+
112
+ defineEffect: (effect: Effect<TEvent, TContext>): Effect<TEvent, TContext> => effect,
113
+ defineEffects: <T extends Record<string, Effect<TEvent, TContext>>>(effects: T): T => effects,
114
+
115
+ defineAssigner: <E extends TEvent = TEvent>(assigner: Assigner<E, TContext>): Assigner<E, TContext> => assigner,
116
+ defineAssigners: <T extends Record<string, Assigner<TEvent, TContext>>>(assigners: T): T => assigners,
117
+
118
+ defineGuard: <E extends TEvent = TEvent>(guard: Guard<E, TContext>): Guard<E, TContext> => guard,
119
+ defineGuards: <T extends Record<string, Guard<TEvent, TContext>>>(guards: T): T => guards,
120
+
121
+ not:
122
+ (guard: Guard<TEvent, TContext>): Guard<TEvent, TContext> =>
123
+ (event, context) =>
124
+ !guard(event, context),
125
+ and:
126
+ (...guards: Guard<TEvent, TContext>[]): Guard<TEvent, TContext> =>
127
+ (event, context) =>
128
+ guards.every((guard) => guard(event, context)),
129
+ or:
130
+ (...guards: Guard<TEvent, TContext>[]): Guard<TEvent, TContext> =>
131
+ (event, context) =>
132
+ guards.some((guard) => guard(event, context)),
133
+ } as const;
134
+ }
@@ -9,7 +9,7 @@ import {
9
9
  type IReadonlySignal,
10
10
  } from '@alwatr/signal';
11
11
 
12
- import type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner, Actor} from './type.js';
12
+ import type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner, StateActor} from './type.js';
13
13
 
14
14
  /**
15
15
  * A generic, encapsulated service that creates, runs, and manages a finite state machine.
@@ -60,7 +60,7 @@ export class FsmService<
60
60
  stateSignal?: StateSignal<MachineState<TState, TContext>> | PersistentStateSignal<MachineState<TState, TContext>>,
61
61
  ) {
62
62
  this.logger_ = createLogger(`fsm:${this.config_.name}`);
63
- this.logger_.logMethodArgs?.('constructor', config_);
63
+ DEV_MODE && this.logger_.logMethodArgs?.('constructor', config_);
64
64
 
65
65
  const initialValue: MachineState<TState, TContext> = {
66
66
  name: config_.initial,
@@ -114,7 +114,7 @@ export class FsmService<
114
114
  * @param event The event to process.
115
115
  */
116
116
  public readonly dispatch = (event: TEvent): void => {
117
- this.logger_.logMethodArgs?.('dispatch', {event});
117
+ DEV_MODE && this.logger_.logMethodArgs?.('dispatch', {event});
118
118
  this.mailbox__.push(event);
119
119
  this.processMailbox__();
120
120
  };
@@ -123,9 +123,9 @@ export class FsmService<
123
123
  // RTC guard: an active loop is already draining the mailbox; it will pick
124
124
  // this event up after the current transition finishes.
125
125
  if (this.processing__) return;
126
- this.logger_.logMethod?.('processMailbox__');
126
+ DEV_MODE && this.logger_.logMethod?.('processMailbox__');
127
127
  if (this.destroyed__) {
128
- this.logger_.incident?.('dispatch', 'dispatch_after_destroy', {event});
128
+ DEV_MODE && this.logger_.incident?.('dispatch', 'dispatch_after_destroy');
129
129
  return;
130
130
  }
131
131
  this.processing__ = true;
@@ -149,15 +149,16 @@ export class FsmService<
149
149
  */
150
150
  private processTransition__(event: TEvent): void {
151
151
  const currentState = this.stateSignal__.get();
152
- this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});
152
+ DEV_MODE && this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});
153
153
 
154
154
  const transition = this.findTransition__(event, currentState);
155
155
 
156
156
  if (!transition) {
157
- this.logger_.incident?.('processTransition__', 'ignored_event', 'No valid transition found for event', {
158
- state: currentState.name,
159
- event,
160
- });
157
+ DEV_MODE
158
+ && this.logger_.incident?.('processTransition__', 'ignored_event', 'No valid transition found for event', {
159
+ state: currentState.name,
160
+ event,
161
+ });
161
162
  return; // Event ignored, no transition occurs.
162
163
  }
163
164
 
@@ -198,7 +199,7 @@ export class FsmService<
198
199
  event: TEvent,
199
200
  currentState: MachineState<TState, TContext>,
200
201
  ): Transition<TState, TEvent, TContext> | undefined {
201
- this.logger_.logMethod?.('findTransition__');
202
+ DEV_MODE && this.logger_.logMethod?.('findTransition__');
202
203
 
203
204
  const currentStateConfig = this.config_.states[currentState.name];
204
205
  const transitions = currentStateConfig?.on?.[event.type as TEvent['type']] as
@@ -258,11 +259,11 @@ export class FsmService<
258
259
  effects?: SingleOrArray<Effect<TEvent, TContext>>,
259
260
  ): void {
260
261
  if (!effects) {
261
- this.logger_.logMethod?.('executeEffects__.skipped');
262
+ DEV_MODE && this.logger_.logMethod?.('executeEffects__.skipped');
262
263
  return;
263
264
  }
264
265
 
265
- this.logger_.logMethod?.('executeEffects__');
266
+ DEV_MODE && this.logger_.logMethod?.('executeEffects__');
266
267
 
267
268
  if (!Array.isArray(effects)) {
268
269
  try {
@@ -309,11 +310,11 @@ export class FsmService<
309
310
  assigners?: SingleOrArray<Assigner<TEvent, TContext>>,
310
311
  ): TContext {
311
312
  if (!assigners) {
312
- this.logger_.logMethod?.('applyAssigners__.skipped');
313
+ DEV_MODE && this.logger_.logMethod?.('applyAssigners__.skipped');
313
314
  return context;
314
315
  }
315
316
 
316
- this.logger_.logMethod?.('applyAssigners__');
317
+ DEV_MODE && this.logger_.logMethod?.('applyAssigners__');
317
318
 
318
319
  if (!Array.isArray(assigners)) {
319
320
  try {
@@ -349,7 +350,7 @@ export class FsmService<
349
350
  */
350
351
  protected start_(): void {
351
352
  if (this.destroyed__) return;
352
- this.logger_.logMethod?.('start_');
353
+ DEV_MODE && this.logger_.logMethod?.('start_');
353
354
  const currentState = this.stateSignal__.get();
354
355
  const initEvent = {type: '__init__'} as unknown as TEvent;
355
356
  this.executeEffects__(initEvent, currentState.context, this.config_.states[currentState.name]?.entry);
@@ -366,14 +367,14 @@ export class FsmService<
366
367
  private spawnActors__(
367
368
  event: TEvent,
368
369
  context: Readonly<TContext>,
369
- actors?: SingleOrArray<Actor<TEvent, TContext>>,
370
+ actors?: SingleOrArray<StateActor<TEvent, TContext>>,
370
371
  ): void {
371
372
  if (!actors) {
372
- this.logger_.logMethod?.('spawnActors__.skipped');
373
+ DEV_MODE && this.logger_.logMethod?.('spawnActors__.skipped');
373
374
  return;
374
375
  }
375
376
 
376
- this.logger_.logMethod?.('spawnActors__');
377
+ DEV_MODE && this.logger_.logMethod?.('spawnActors__');
377
378
 
378
379
  if (!Array.isArray(actors)) {
379
380
  try {
@@ -406,7 +407,7 @@ export class FsmService<
406
407
  * Cleans up (destroys) all currently active state actors in REVERSE (LIFO) spawn order — standard resource-release semantics.
407
408
  */
408
409
  private cleanupActors__(): void {
409
- this.logger_.logMethodArgs?.('cleanupActors__', {count: this.activeActorCleanups__.length});
410
+ DEV_MODE && this.logger_.logMethodArgs?.('cleanupActors__', {count: this.activeActorCleanups__.length});
410
411
  for (let index = this.activeActorCleanups__.length - 1; index >= 0; index--) {
411
412
  try {
412
413
  this.activeActorCleanups__[index]();
@@ -425,7 +426,7 @@ export class FsmService<
425
426
  */
426
427
  public destroy(destroyState = true): void {
427
428
  if (this.destroyed__) return;
428
- this.logger_.logMethod?.('destroy');
429
+ DEV_MODE && this.logger_.logMethod?.('destroy');
429
430
  this.destroyed__ = true;
430
431
  this.mailbox__.length = 0;
431
432
  this.cleanupActors__();
package/src/type.ts CHANGED
@@ -52,7 +52,7 @@ export type Assigner<TEvent extends MachineEvent, TContext extends Record<string
52
52
  * a state/context that no longer exists. This mirrors the design of SCXML actions,
53
53
  * XState actions, and Erlang's gen_statem.
54
54
  *
55
- * **Any asynchronous work belongs in an {@link Actor}**, which has a proper
55
+ * **Any asynchronous work belongs in an {@link StateActor}**, which has a proper
56
56
  * lifecycle (spawn on entry, cleanup on exit) and communicates results back to
57
57
  * the machine via `dispatch`, keeping the core deterministic.
58
58
  *
@@ -82,10 +82,10 @@ export type Guard<TEvent extends MachineEvent, TContext extends Record<string, u
82
82
  ) => boolean;
83
83
 
84
84
  /**
85
- * Defines an actor — an **asynchronous lifecycle process** spawned on state entry.
85
+ * Defines a state actor — an **asynchronous lifecycle process** spawned on state entry.
86
86
  *
87
87
  * This is the ONLY sanctioned home for async work in the machine (network requests,
88
- * polling intervals, websocket listeners, timers). An actor:
88
+ * polling intervals, websocket listeners, timers). A state actor:
89
89
  *
90
90
  * 1. Is spawned when the machine enters the state.
91
91
  * 2. Receives `dispatch` to asynchronously send events back to the parent FSM.
@@ -95,7 +95,7 @@ export type Guard<TEvent extends MachineEvent, TContext extends Record<string, u
95
95
  * @template TEvent The union type of all events in the machine.
96
96
  * @template TContext The type of the machine's context.
97
97
  */
98
- export type Actor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (
98
+ export type StateActor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (
99
99
  context: Readonly<TContext>,
100
100
  dispatch: (event: TEvent) => void,
101
101
  ) => VoidFunction | void;
@@ -185,7 +185,7 @@ export interface StateMachineConfig<
185
185
  /** Synchronous side-effects executed upon exiting this state. */
186
186
  readonly exit?: SingleOrArray<Effect<TEvent, TContext>>;
187
187
  /** Async lifecycle actors spawned upon entering this state, cleaned up (LIFO) when leaving. */
188
- readonly actor?: SingleOrArray<Actor<TEvent, TContext>>;
188
+ readonly actor?: SingleOrArray<StateActor<TEvent, TContext>>;
189
189
  };
190
190
  };
191
191
  }