@alwatr/fsm 9.26.0 → 9.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/facade.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { JsonObject } from '@alwatr/type-helper';
2
1
  import { FsmService } from './fsm-service.js';
3
2
  import type { MachineEvent, StateMachineConfig } from './type.js';
4
3
  /**
@@ -32,14 +31,14 @@ import type { MachineEvent, StateMachineConfig } from './type.js';
32
31
  * on: {
33
32
  * TOGGLE: {
34
33
  * target: 'on',
35
- * assigners: [() => ({brightness: 100})],
34
+ * assigners: [({context}) => ({...context, brightness: 100})],
36
35
  * },
37
36
  * },
38
37
  * },
39
38
  * on: {
40
39
  * on: {
41
- * TOGGLE: {target: 'off', assigners: [() => ({brightness: 0})]},
42
- * SET_BRIGHTNESS: {assigners: [(event) => ({brightness: event.level})]},
40
+ * TOGGLE: {target: 'off', assigners: [({context}) => ({...context, brightness: 0})]},
41
+ * SET_BRIGHTNESS: {assigners: [({context, event}) => ({...context, brightness: event.level})]},
43
42
  * },
44
43
  * },
45
44
  * },
@@ -53,13 +52,13 @@ import type { MachineEvent, StateMachineConfig } from './type.js';
53
52
  * console.log(`Light is ${state.name} with brightness ${state.context.brightness}`);
54
53
  * });
55
54
  *
56
- * lightService.eventSignal.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100
55
+ * lightService.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100
57
56
  *
58
- * lightService.eventSignal.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50
57
+ * lightService.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50
59
58
  *
60
59
  * // 5. Cleanup
61
60
  * // lightService.destroy();
62
61
  * ```
63
62
  */
64
- export declare function createFsmService<TState extends string, TEvent extends MachineEvent, TContext extends JsonObject>(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext>;
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>;
65
64
  //# sourceMappingURL=facade.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../src/facade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAgB,kBAAkB,EAAC,MAAM,WAAW,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,SAAS,YAAY,EAAE,QAAQ,SAAS,UAAU,EAC9G,MAAM,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,GACnD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAoBtC"}
1
+ {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../src/facade.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAgB,kBAAkB,EAAC,MAAM,WAAW,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,SAAS,MAAM,EACrB,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAChE,MAAM,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAoB5F"}
@@ -1,6 +1,5 @@
1
- import type { JsonObject } from '@alwatr/type-helper';
2
1
  import { type AlwatrLogger } from '@alwatr/logger';
3
- import { type StateSignal, type PersistentStateSignal, EventSignal, type IReadonlySignal } from '@alwatr/signal';
2
+ import { type StateSignal, type PersistentStateSignal, type IReadonlySignal } from '@alwatr/signal';
4
3
  import type { StateMachineConfig, MachineState, MachineEvent } from './type.js';
5
4
  /**
6
5
  * A generic, encapsulated service that creates, runs, and manages a finite state machine.
@@ -11,15 +10,23 @@ import type { StateMachineConfig, MachineState, MachineEvent } from './type.js';
11
10
  * @template TEvent The union type of all possible events.
12
11
  * @template TContext The type of the machine's context (extended state).
13
12
  */
14
- export declare class FsmService<TState extends string, TEvent extends MachineEvent, TContext extends JsonObject> {
13
+ export declare class FsmService<TState extends string, TEvent extends MachineEvent, TContext extends Record<string, unknown> = Record<string, never>> {
15
14
  protected readonly config_: StateMachineConfig<TState, TEvent, TContext>;
16
15
  private readonly stateSignal__;
17
16
  protected readonly logger_: AlwatrLogger;
18
- /** The event signal for sending events to the FSM. */
19
- readonly eventSignal: EventSignal<TEvent>;
17
+ /** The private event signal for sending events to the FSM. */
18
+ private readonly eventSignal__;
20
19
  /** The public, read-only state signal. Subscribe to react to state changes. */
21
20
  readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;
21
+ /** The set of cleanup functions for currently active state actors. */
22
+ private readonly activeActorCleanups__;
22
23
  constructor(config_: StateMachineConfig<TState, TEvent, TContext>, stateSignal__: StateSignal<MachineState<TState, TContext>> | PersistentStateSignal<MachineState<TState, TContext>>);
24
+ /**
25
+ * Dispatches an event to the FSM mailbox.
26
+ *
27
+ * @param event The event to process.
28
+ */
29
+ readonly dispatch: (event: TEvent) => void;
23
30
  /**
24
31
  * The core FSM logic that processes a single event and transitions the machine to a new state.
25
32
  * This process is atomic and follows the Run-to-Completion (RTC) model.
@@ -28,7 +35,7 @@ export declare class FsmService<TState extends string, TEvent extends MachineEve
28
35
  */
29
36
  private processTransition__;
30
37
  /**
31
- * Finds the first valid transition for the given event and context by evaluating conditions.
38
+ * Finds the first valid transition for the given event and context by evaluating guards.
32
39
  *
33
40
  * @param event The triggering event.
34
41
  * @param context The current machine context.
@@ -55,6 +62,19 @@ export declare class FsmService<TState extends string, TEvent extends MachineEve
55
62
  * @returns The new, updated context, or the original context if any assigner fails.
56
63
  */
57
64
  private applyAssigners__;
65
+ /**
66
+ * Starts the FSM by executing the entry effects and spawning the actors
67
+ * of the initial/current state.
68
+ */
69
+ protected start_(): void;
70
+ /**
71
+ * Spawns all configured actors for the entered state.
72
+ */
73
+ private spawnActors__;
74
+ /**
75
+ * Cleans up (destroys) all currently active state actors.
76
+ */
77
+ private cleanupActors__;
58
78
  /**
59
79
  * Destroys the service, cleaning up all internal signals and subscriptions
60
80
  * to prevent memory leaks.
@@ -1 +1 @@
1
- {"version":3,"file":"fsm-service.d.ts","sourceRoot":"","sources":["../src/fsm-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAgB,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAe,KAAK,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,WAAW,EACX,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAC,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAA+B,MAAM,WAAW,CAAC;AAE5G;;;;;;;;GAQG;AACH,qBAAa,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,SAAS,YAAY,EAAE,QAAQ,SAAS,UAAU;IAUnG,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;IACxE,OAAO,CAAC,QAAQ,CAAC,aAAa;IAVhC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAEzC,sDAAsD;IACtD,SAAgB,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAEjD,+EAA+E;IAC/E,SAAgB,WAAW,EAAE,eAAe,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAGxD,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EACvD,aAAa,EAC1B,WAAW,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GAC3C,qBAAqB,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAY3D;;;;;OAKG;YACW,mBAAmB;IAuCjC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IA0CxB;;;;;;;OAOG;YACW,gBAAgB;IAoC9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IAyCxB;;;OAGG;IACI,OAAO,IAAI,IAAI;CAKvB"}
1
+ {"version":3,"file":"fsm-service.d.ts","sourceRoot":"","sources":["../src/fsm-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAE1B,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAC,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAsC,MAAM,WAAW,CAAC;AAEnH;;;;;;;;GAQG;AACH,qBAAa,UAAU,CACrB,MAAM,SAAS,MAAM,EACrB,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAc9D,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;IACxE,OAAO,CAAC,QAAQ,CAAC,aAAa;IAbhC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAEzC,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IAEpD,+EAA+E;IAC/E,SAAgB,WAAW,EAAE,eAAe,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE7E,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;gBAG1C,OAAO,EAAE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EACvD,aAAa,EAC1B,WAAW,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GAC3C,qBAAqB,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAe3D;;;;OAIG;IACH,SAAgB,QAAQ,GAAI,OAAO,MAAM,KAAG,IAAI,CAG9C;IAEF;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IA0CxB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IAsCxB;;;OAGG;IACH,SAAS,CAAC,MAAM,IAAI,IAAI;IASxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkCrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;;OAGG;IACI,OAAO,IAAI,IAAI;CAMvB"}
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
- /* 📦 @alwatr/fsm v9.26.0 */
2
- import{createPersistentStateSignal as D,createStateSignal as V}from"@alwatr/signal";import{createLogger as k}from"@alwatr/logger";import{createEventSignal as P}from"@alwatr/signal";class Z{config_;stateSignal__;logger_;eventSignal;stateSignal;constructor(q,z){this.config_=q;this.stateSignal__=z;this.logger_=k(`fsm:${this.config_.name}`),this.logger_.logMethodArgs?.("constructor",q),this.stateSignal=this.stateSignal__.asReadonly(),this.eventSignal=P({name:`fsm-event-${this.config_.name}`}),this.eventSignal.subscribe(this.processTransition__.bind(this),{receivePrevious:!1})}async processTransition__(q){let z=this.stateSignal__.get();this.logger_.logMethodArgs?.("processTransition__",{state:z.name,event:q});let J=this.findTransition__(q,z.context);if(!J){this.logger_.incident?.("processTransition__","ignored_event","No valid transition found for event",{state:z.name,event:q});return}let W=J.target??z.name;if(W!==z.name)this.executeEffects__(q,z.context,this.config_.states[z.name]?.exit);let K=this.applyAssigners__(q,z.context,J.assigners),Q={name:W,context:K};if(this.stateSignal__.set(Q),Q.name!==z.name)this.executeEffects__(q,Q.context,this.config_.states[Q.name]?.entry)}findTransition__(q,z){this.logger_.logMethod?.("findTransition__");let J=this.stateSignal__.get().name,K=this.config_.states[J]?.on?.[q.type];if(!K)return;return(Array.isArray(K)?K:[K]).find((X,j)=>{if(!X.condition)return!0;try{let Y=X.condition(q,z);return this.logger_.logStep?.("findTransition__","check_condition",{state:J,eventType:q.type,transitionIndex:j,condition:X.condition.name||"anonymous",result:Y}),Y}catch(Y){return this.logger_.error("findTransition__","condition_failed",Y,{state:J,eventType:q.type,transitionIndex:j,condition:X.condition.name||"anonymous"}),!1}})}async executeEffects__(q,z,J){if(!J){this.logger_.logMethodArgs?.("executeEffects__//skipped",{count:0});return}let W=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("executeEffects__",{count:W.length});for(let K of W)try{let Q=await K(q,z);if(Q&&"type"in Q)this.logger_.logStep?.("executeEffects__","new_event_from_effect",{effect:K.name||"anonymous",state:this.stateSignal__.get().name,newEvent:Q.type}),this.eventSignal.dispatch(Q)}catch(Q){this.logger_.error("executeEffects__","effect_failed",Q,{effect:K.name||"anonymous",state:this.stateSignal__.get().name,event:q,context:z})}}applyAssigners__(q,z,J){if(!J)return this.logger_.logMethodArgs?.("applyAssigners__//skipped",{count:0}),z;let W=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("applyAssigners__",{count:W.length});try{return W.reduce((K,Q)=>{let X=Q(q,K);if(this.logger_.logMethodFull?.(`event.${q.type}.action.${Q.name||"anonymous"}`,{event:q,accContext:K},X),typeof X==="object"&&X!==null)return{...K,...X};return K},z)}catch(K){return this.logger_.error("applyAssigners__","assigner_failed_atomic",K,{event:q,context:z}),z}}destroy(){this.logger_.logMethod?.("destroy"),this.eventSignal.destroy(),this.stateSignal.destroy()}}function F(q){let z={name:q.initial,context:q.context},J=q.persistent?D({name:`fsm-state-${q.name}`,storageKey:q.persistent.storageKey??q.name,initialValue:z,schemaVersion:q.persistent.schemaVersion}):V({name:`fsm-state-${q.name}`,initialValue:z});return new Z(q,J)}export{F as createFsmService,Z as FsmService};
1
+ /* 📦 @alwatr/fsm v9.29.0 */
2
+ import{createPersistentStateSignal as U,createStateSignal as D}from"@alwatr/signal";import{createLogger as k}from"@alwatr/logger";import{createEventSignal as P}from"@alwatr/signal";class j{config_;stateSignal__;logger_;eventSignal__;stateSignal;activeActorCleanups__=new Set;constructor(q,z){this.config_=q;this.stateSignal__=z;this.logger_=k(`fsm:${this.config_.name}`),this.logger_.logMethodArgs?.("constructor",q),this.stateSignal=this.stateSignal__.asReadonly(),this.eventSignal__=P({name:`fsm-event-${this.config_.name}`}),this.eventSignal__.subscribe((J)=>this.processTransition__(J),{receivePrevious:!1}),this.start_()}dispatch=(q)=>{this.logger_.logMethodArgs?.("dispatch",{event:q}),this.eventSignal__.dispatch(q)};processTransition__(q){let z=this.stateSignal__.get();this.logger_.logMethodArgs?.("processTransition__",{state:z.name,event:q});let J=this.findTransition__(q,z.context);if(!J){this.logger_.incident?.("processTransition__","ignored_event","No valid transition found for event",{state:z.name,event:q});return}let X=J.target??z.name,K=J.target!==void 0;if(K)this.executeEffects__(q,z.context,this.config_.states[z.name]?.exit),this.cleanupActors__();let W=this.applyAssigners__(q,z.context,J.assigners),Q={name:X,context:W};if(this.stateSignal__.set(Q),K)this.executeEffects__(q,Q.context,this.config_.states[Q.name]?.entry),this.spawnActors__(q,Q.context,this.config_.states[Q.name]?.actors)}findTransition__(q,z){this.logger_.logMethod?.("findTransition__");let J=this.stateSignal__.get().name,K=this.config_.states[J]?.on?.[q.type];if(!K)return;let W=Array.isArray(K)?K:[K];for(let Q=0;Q<W.length;Q++){let Y=W[Q];if(!Y.guard)return Y;try{let Z=Y.guard({event:q,context:z});if(this.logger_.logStep?.("findTransition__","check_guard",{state:J,eventType:q.type,transitionIndex:Q,guard:Y.guard.name||"anonymous",result:Z}),Z)return Y}catch(Z){this.logger_.error("findTransition__","guard_failed",Z,{state:J,eventType:q.type,transitionIndex:Q,guard:Y.guard.name||"anonymous"})}}return}executeEffects__(q,z,J){if(!J){this.logger_.logMethodArgs?.("executeEffects__//skipped",{count:0});return}let X=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("executeEffects__",{count:X.length});for(let K of X)try{let W=K({event:q,context:z});if(W instanceof Promise)W.catch((Q)=>{this.logger_.error("executeEffects__","effect_failed",Q,{effect:K.name||"anonymous",state:this.stateSignal__.get().name,event:q,context:z})})}catch(W){this.logger_.error("executeEffects__","effect_failed",W,{effect:K.name||"anonymous",state:this.stateSignal__.get().name,event:q,context:z})}}applyAssigners__(q,z,J){if(!J)return this.logger_.logMethodArgs?.("applyAssigners__//skipped",{count:0}),z;let X=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("applyAssigners__",{count:X.length});try{let K=z;for(let W of X){let Q=W({event:q,context:K});if(this.logger_.logMethodFull?.(`event.${q.type}.action.${W.name||"anonymous"}`,{event:q,accContext:K},Q),Q!==void 0&&Q!==null)K=Q}return K}catch(K){return this.logger_.error("applyAssigners__","assigner_failed_atomic",K,{event:q,context:z}),z}}start_(){if(this.eventSignal__.isDestroyed)return;this.logger_.logMethod?.("start_");let q=this.stateSignal__.get(),z={type:"__init__"};this.executeEffects__(z,q.context,this.config_.states[q.name]?.entry),this.spawnActors__(z,q.context,this.config_.states[q.name]?.actors)}spawnActors__(q,z,J){if(!J){this.logger_.logMethodArgs?.("spawnActors__//skipped",{count:0});return}let X=Array.isArray(J)?J:[J];this.logger_.logMethodArgs?.("spawnActors__",{count:X.length});for(let K of X)try{let W=K({event:q,context:z,dispatch:this.dispatch});if(typeof W==="function")this.activeActorCleanups__.add(W)}catch(W){this.logger_.error("spawnActors__","actor_failed",W,{actor:K.name||"anonymous",state:this.stateSignal__.get().name,event:q,context:z})}}cleanupActors__(){this.logger_.logMethodArgs?.("cleanupActors__",{count:this.activeActorCleanups__.size});for(let q of this.activeActorCleanups__)try{q()}catch(z){this.logger_.error("cleanupActors__","cleanup_failed",z)}this.activeActorCleanups__.clear()}destroy(){this.logger_.logMethod?.("destroy"),this.cleanupActors__(),this.eventSignal__.destroy(),this.stateSignal__.destroy()}}function O(q){let z={name:q.initial,context:q.context},J=q.persistent?U({name:`fsm-state-${q.name}`,storageKey:q.persistent.storageKey??q.name,initialValue:z,schemaVersion:q.persistent.schemaVersion}):D({name:`fsm-state-${q.name}`,initialValue:z});return new j(q,J)}export{O as createFsmService,j as FsmService};
3
3
 
4
- //# debugId=79475A1B16609BC164756E2164756E21
4
+ //# debugId=C2B6164C11701D3564756E2164756E21
5
5
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/facade.ts", "../src/fsm-service.ts"],
4
4
  "sourcesContent": [
5
- "import type {JsonObject} from '@alwatr/type-helper';\nimport {createPersistentStateSignal, createStateSignal} from '@alwatr/signal';\n\nimport {FsmService} from './fsm-service.js';\n\nimport type {MachineEvent, MachineState, StateMachineConfig} from './type.js';\n\n/**\n * A simple and clean factory function for creating an `FsmService` instance.\n * This is the recommended way to instantiate a new state machine.\n *\n * @template TState - The union type of all possible states.\n * @template TEvent - The union type of all possible events.\n * @template TContext - The type of the machine's context.\n *\n * @param config - The machine's configuration object.\n * @returns A new, ready-to-use instance of `FsmService`.\n *\n * @example\n * ```ts\n * import {createFsmService} from '@alwatr/fsm';\n * import type {StateMachineConfig} from '@alwatr/fsm';\n *\n * // 1. Define types\n * type LightContext = {brightness: number};\n * type LightState = 'on' | 'off';\n * type LightEvent = {type: 'TOGGLE'} | {type: 'SET_BRIGHTNESS'; level: number};\n *\n * // 2. Config the state machine\n * const lightMachineConfig: StateMachineConfig<LightState, LightEvent, LightContext> = {\n * name: 'light-switch',\n * initial: 'off',\n * context: {brightness: 0},\n * states: {\n * off: {\n * on: {\n * TOGGLE: {\n * target: 'on',\n * assigners: [() => ({brightness: 100})],\n * },\n * },\n * },\n * on: {\n * on: {\n * TOGGLE: {target: 'off', assigners: [() => ({brightness: 0})]},\n * SET_BRIGHTNESS: {assigners: [(event) => ({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.eventSignal.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100\n *\n * lightService.eventSignal.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50\n *\n * // 5. Cleanup\n * // lightService.destroy();\n * ```\n */\nexport function createFsmService<TState extends string, TEvent extends MachineEvent, TContext extends JsonObject>(\n config: StateMachineConfig<TState, TEvent, TContext>,\n): FsmService<TState, TEvent, TContext> {\n const initialValue: MachineState<TState, TContext> = {\n name: config.initial,\n context: config.context,\n };\n\n const stateSignal =\n config.persistent ?\n createPersistentStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config.name}`,\n storageKey: config.persistent.storageKey ?? config.name,\n initialValue,\n schemaVersion: config.persistent.schemaVersion,\n })\n : createStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config.name}`,\n initialValue: initialValue,\n });\n\n return new FsmService(config, stateSignal);\n}\n",
6
- "import type {JsonObject, SingleOrArray} from '@alwatr/type-helper';\nimport {createLogger, type AlwatrLogger} from '@alwatr/logger';\nimport {\n createEventSignal,\n type StateSignal,\n type PersistentStateSignal,\n EventSignal,\n type IReadonlySignal,\n} from '@alwatr/signal';\n\nimport type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner} 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<TState extends string, TEvent extends MachineEvent, TContext extends JsonObject> {\n protected readonly logger_: AlwatrLogger;\n\n /** The event signal for sending events to the FSM. */\n public readonly eventSignal: EventSignal<TEvent>;\n\n /** The public, read-only state signal. Subscribe to react to state changes. */\n public readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;\n\n constructor(\n protected readonly config_: StateMachineConfig<TState, TEvent, TContext>,\n private readonly stateSignal__:\n | StateSignal<MachineState<TState, TContext>>\n | PersistentStateSignal<MachineState<TState, TContext>>,\n ) {\n this.logger_ = createLogger(`fsm:${this.config_.name}`);\n this.logger_.logMethodArgs?.('constructor', config_);\n\n this.stateSignal = this.stateSignal__.asReadonly();\n this.eventSignal = createEventSignal<TEvent>({\n name: `fsm-event-${this.config_.name}`,\n });\n this.eventSignal.subscribe(this.processTransition__.bind(this), {receivePrevious: false});\n }\n\n /**\n * The core FSM logic that processes a single event and transitions the machine to a new state.\n * This process is atomic and follows the Run-to-Completion (RTC) model.\n *\n * @param event The event to process.\n */\n private async processTransition__(event: TEvent): Promise<void> {\n const currentState = this.stateSignal__.get();\n this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});\n\n const transition = this.findTransition__(event, currentState.context);\n\n if (!transition) {\n this.logger_.incident?.('processTransition__', 'ignored_event', 'No valid transition found for event', {\n state: currentState.name,\n event,\n });\n return; // Event ignored, no transition occurs.\n }\n\n const targetStateName = transition.target ?? currentState.name;\n\n // 1. Execute exit effects of the current state if transitioning to a new state.\n if (targetStateName !== currentState.name) {\n void this.executeEffects__(event, currentState.context, this.config_.states[currentState.name]?.exit);\n }\n\n // 2. Apply assigners to compute the next context. This is a pure function.\n const nextContext = this.applyAssigners__(event, currentState.context, transition.assigners);\n\n // 3. Create the final next state object.\n const nextState: MachineState<TState, TContext> = {\n name: targetStateName,\n context: nextContext,\n };\n\n // 4. Set the new state, notifying all subscribers.\n this.stateSignal__.set(nextState);\n\n // 5. Execute entry effects of the new state if a transition occurred.\n if (nextState.name !== currentState.name) {\n void this.executeEffects__(event, nextState.context, this.config_.states[nextState.name]?.entry);\n }\n }\n\n /**\n * Finds the first valid transition for the given event and context by evaluating conditions.\n *\n * @param event The triggering event.\n * @param context The current machine context.\n * @returns The first matching transition or `undefined` if none are found.\n */\n private findTransition__(\n event: TEvent,\n context: Readonly<TContext>,\n ): Transition<TState, TEvent, TContext> | undefined {\n this.logger_.logMethod?.('findTransition__');\n\n const currentStateName = this.stateSignal__.get().name;\n const currentStateConfig = this.config_.states[currentStateName];\n const transitions = currentStateConfig?.on?.[event.type as TEvent['type']] as\n | SingleOrArray<Transition<TState, TEvent, TContext>>\n | undefined;\n\n if (!transitions) return undefined;\n\n // Normalize to an array to handle both single and multiple transitions uniformly.\n const transitionsArray = Array.isArray(transitions) ? transitions : [transitions];\n\n return transitionsArray.find((transition, index) => {\n if (!transition.condition) return true; // A transition without a condition is always valid.\n\n try {\n const conditionMet = transition.condition(event, context);\n this.logger_.logStep?.('findTransition__', 'check_condition', {\n state: currentStateName,\n eventType: event.type,\n transitionIndex: index,\n condition: transition.condition.name || 'anonymous',\n result: conditionMet,\n });\n return conditionMet;\n } catch (error) {\n this.logger_.error('findTransition__', 'condition_failed', error, {\n state: currentStateName,\n eventType: event.type,\n transitionIndex: index,\n condition: transition.condition.name || 'anonymous',\n });\n return false; // Treat a failing condition as not met.\n }\n });\n }\n\n /**\n * Sequentially executes a list of effects (side-effects).\n * Errors are caught and logged without stopping the FSM.\n *\n * @param event The event that triggered these effects.\n * @param context The context at the time of execution.\n * @param effects A single effect or an array of effects.\n */\n private async executeEffects__(\n event: TEvent,\n context: Readonly<TContext>,\n effects?: SingleOrArray<Effect<TEvent, TContext>>,\n ): Promise<void> {\n if (!effects) {\n this.logger_.logMethodArgs?.('executeEffects__//skipped', {count: 0});\n return;\n }\n const effectsArray: Effect<TEvent, TContext>[] = Array.isArray(effects) ? effects : [effects];\n\n this.logger_.logMethodArgs?.('executeEffects__', {count: effectsArray.length});\n\n for (const effect of effectsArray) {\n try {\n const result = await effect(event, context);\n // If an effect returns a new event, dispatch it to be processed next.\n if (result && 'type' in result) {\n this.logger_.logStep?.('executeEffects__', 'new_event_from_effect', {\n effect: effect.name || 'anonymous',\n state: this.stateSignal__.get().name,\n newEvent: result.type,\n });\n this.eventSignal.dispatch(result);\n }\n } catch (error) {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n effect: effect.name || 'anonymous',\n state: this.stateSignal__.get().name,\n event,\n context,\n });\n }\n }\n }\n\n /**\n * Applies all assigner functions to the context to produce a new, updated context.\n * This process is atomic (all-or-nothing). If any assigner fails, the original\n * context is returned, and all updates are discarded.\n *\n * @param event The event that triggered the transition.\n * @param context The current context.\n * @param assigners A single assigner or an array of assigners.\n * @returns The new, updated context, or the original context if any assigner fails.\n */\n private applyAssigners__(\n event: TEvent,\n context: Readonly<TContext>,\n assigners?: SingleOrArray<Assigner<TEvent, TContext>>,\n ): TContext {\n if (!assigners) {\n this.logger_.logMethodArgs?.('applyAssigners__//skipped', {count: 0});\n return context;\n }\n\n const assignersArray: Assigner<TEvent, TContext>[] = Array.isArray(assigners) ? assigners : [assigners];\n\n this.logger_.logMethodArgs?.('applyAssigners__', {count: assignersArray.length});\n\n try {\n // The entire reduce operation is wrapped in a single try/catch block\n // to ensure atomic updates.\n return assignersArray.reduce((accContext, assigner) => {\n const partialUpdate = assigner(event, accContext);\n this.logger_.logMethodFull?.(\n `event.${event.type}.action.${assigner.name || 'anonymous'}`,\n {event, accContext},\n partialUpdate,\n );\n if (typeof partialUpdate === 'object' && partialUpdate !== null) {\n // The next assigner receives the updated context from the previous one.\n return {...accContext, ...partialUpdate};\n }\n // If an assigner returns nothing, pass the accumulated context along.\n return accContext;\n }, context);\n } catch (error) {\n this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {\n event,\n context, // Log the original context for debugging.\n });\n // On ANY failure, discard all changes and return the original context.\n return context;\n }\n }\n\n /**\n * Destroys the service, cleaning up all internal signals and subscriptions\n * to prevent memory leaks.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n this.eventSignal.destroy();\n this.stateSignal.destroy();\n }\n}\n"
5
+ "import {createPersistentStateSignal, createStateSignal} from '@alwatr/signal';\n\nimport {FsmService} from './fsm-service.js';\n\nimport type {MachineEvent, MachineState, StateMachineConfig} from './type.js';\n\n/**\n * A simple and clean factory function for creating an `FsmService` instance.\n * This is the recommended way to instantiate a new state machine.\n *\n * @template TState - The union type of all possible states.\n * @template TEvent - The union type of all possible events.\n * @template TContext - The type of the machine's context.\n *\n * @param config - The machine's configuration object.\n * @returns A new, ready-to-use instance of `FsmService`.\n *\n * @example\n * ```ts\n * import {createFsmService} from '@alwatr/fsm';\n * import type {StateMachineConfig} from '@alwatr/fsm';\n *\n * // 1. Define types\n * type LightContext = {brightness: number};\n * type LightState = 'on' | 'off';\n * type LightEvent = {type: 'TOGGLE'} | {type: 'SET_BRIGHTNESS'; level: number};\n *\n * // 2. Config the state machine\n * const lightMachineConfig: StateMachineConfig<LightState, LightEvent, LightContext> = {\n * name: 'light-switch',\n * initial: 'off',\n * context: {brightness: 0},\n * states: {\n * off: {\n * on: {\n * TOGGLE: {\n * target: 'on',\n * assigners: [({context}) => ({...context, brightness: 100})],\n * },\n * },\n * },\n * on: {\n * on: {\n * TOGGLE: {target: 'off', assigners: [({context}) => ({...context, brightness: 0})]},\n * SET_BRIGHTNESS: {assigners: [({context, event}) => ({...context, brightness: event.level})]},\n * },\n * },\n * },\n * };\n *\n * // 3. Create the service\n * const lightService = createFsmService(lightMachineConfig);\n *\n * // 4. Use it in your application\n * lightService.stateSignal.subscribe((state) => {\n * console.log(`Light is ${state.name} with brightness ${state.context.brightness}`);\n * });\n *\n * lightService.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100\n *\n * lightService.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50\n *\n * // 5. Cleanup\n * // lightService.destroy();\n * ```\n */\nexport function createFsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n>(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext> {\n const initialValue: MachineState<TState, TContext> = {\n name: config.initial,\n context: config.context,\n };\n\n const stateSignal =\n config.persistent ?\n createPersistentStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config.name}`,\n storageKey: config.persistent.storageKey ?? config.name,\n initialValue,\n schemaVersion: config.persistent.schemaVersion,\n })\n : createStateSignal<MachineState<TState, TContext>>({\n name: `fsm-state-${config.name}`,\n initialValue: initialValue,\n });\n\n return new FsmService(config, stateSignal);\n}\n",
6
+ "import type {SingleOrArray} from '@alwatr/type-helper';\nimport {createLogger, type AlwatrLogger} from '@alwatr/logger';\nimport {\n createEventSignal,\n type StateSignal,\n type PersistentStateSignal,\n EventSignal,\n type IReadonlySignal,\n} from '@alwatr/signal';\n\nimport type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner, Actor} from './type.js';\n\n/**\n * A generic, encapsulated service that creates, runs, and manages a finite state machine.\n * It handles signal creation, logic connection, and lifecycle management, providing a clean,\n * reactive API for interacting with the FSM.\n *\n * @template TState The union type of all possible state names.\n * @template TEvent The union type of all possible events.\n * @template TContext The type of the machine's context (extended state).\n */\nexport class FsmService<\n TState extends string,\n TEvent extends MachineEvent,\n TContext extends Record<string, unknown> = Record<string, never>,\n> {\n protected readonly logger_: AlwatrLogger;\n\n /** The private event signal for sending events to the FSM. */\n private readonly eventSignal__: EventSignal<TEvent>;\n\n /** The public, read-only state signal. Subscribe to react to state changes. */\n public readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;\n\n /** The set of cleanup functions for currently active state actors. */\n private readonly activeActorCleanups__ = new Set<() => void>();\n\n constructor(\n protected readonly config_: StateMachineConfig<TState, TEvent, TContext>,\n private readonly stateSignal__:\n | StateSignal<MachineState<TState, TContext>>\n | PersistentStateSignal<MachineState<TState, TContext>>,\n ) {\n this.logger_ = createLogger(`fsm:${this.config_.name}`);\n this.logger_.logMethodArgs?.('constructor', config_);\n\n this.stateSignal = this.stateSignal__.asReadonly();\n this.eventSignal__ = createEventSignal<TEvent>({\n name: `fsm-event-${this.config_.name}`,\n });\n this.eventSignal__.subscribe((event) => this.processTransition__(event), {receivePrevious: false});\n\n // Execute initial state entry effects and actors.\n this.start_();\n }\n\n /**\n * Dispatches an event to the FSM mailbox.\n *\n * @param event The event to process.\n */\n public readonly dispatch = (event: TEvent): void => {\n this.logger_.logMethodArgs?.('dispatch', {event});\n this.eventSignal__.dispatch(event);\n };\n\n /**\n * The core FSM logic that processes a single event and transitions the machine to a new state.\n * This process is atomic and follows the Run-to-Completion (RTC) model.\n *\n * @param event The event to process.\n */\n private processTransition__(event: TEvent): void {\n const currentState = this.stateSignal__.get();\n this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});\n\n const transition = this.findTransition__(event, currentState.context);\n\n if (!transition) {\n this.logger_.incident?.('processTransition__', 'ignored_event', 'No valid transition found for event', {\n state: currentState.name,\n event,\n });\n return; // Event ignored, no transition occurs.\n }\n\n const targetStateName = transition.target ?? currentState.name;\n const isExternalTransition = transition.target !== undefined;\n\n // 1. Execute exit effects and cleanup actors of the current state if it's an external transition.\n if (isExternalTransition) {\n this.executeEffects__(event, currentState.context, this.config_.states[currentState.name]?.exit);\n this.cleanupActors__();\n }\n\n // 2. Apply assigners to compute the next context. This is a pure function.\n const nextContext = this.applyAssigners__(event, currentState.context, transition.assigners);\n\n // 3. Create the final next state object.\n const nextState: MachineState<TState, TContext> = {\n name: targetStateName,\n context: nextContext,\n };\n\n // 4. Set the new state, notifying all subscribers.\n this.stateSignal__.set(nextState);\n\n // 5. Execute entry effects and spawn actors of the new state if it's an external transition.\n if (isExternalTransition) {\n this.executeEffects__(event, nextState.context, this.config_.states[nextState.name]?.entry);\n this.spawnActors__(event, nextState.context, this.config_.states[nextState.name]?.actors);\n }\n }\n\n /**\n * Finds the first valid transition for the given event and context by evaluating guards.\n *\n * @param event The triggering event.\n * @param context The current machine context.\n * @returns The first matching transition or `undefined` if none are found.\n */\n private findTransition__(\n event: TEvent,\n context: Readonly<TContext>,\n ): Transition<TState, TEvent, TContext> | undefined {\n this.logger_.logMethod?.('findTransition__');\n\n const currentStateName = this.stateSignal__.get().name;\n const currentStateConfig = this.config_.states[currentStateName];\n const transitions = currentStateConfig?.on?.[event.type as TEvent['type']] as\n | SingleOrArray<Transition<TState, TEvent, TContext>>\n | undefined;\n\n if (!transitions) return undefined;\n\n const transitionsArray = Array.isArray(transitions) ? transitions : [transitions];\n\n for (let index = 0; index < transitionsArray.length; index++) {\n const transition = transitionsArray[index];\n if (!transition.guard) return transition;\n try {\n const guardMet = transition.guard({event, context});\n this.logger_.logStep?.('findTransition__', 'check_guard', {\n state: currentStateName,\n eventType: event.type,\n transitionIndex: index,\n guard: transition.guard.name || 'anonymous',\n result: guardMet,\n });\n if (guardMet) return transition;\n } catch (error) {\n this.logger_.error('findTransition__', 'guard_failed', error, {\n state: currentStateName,\n eventType: event.type,\n transitionIndex: index,\n guard: transition.guard.name || 'anonymous',\n });\n }\n }\n\n return undefined;\n }\n\n /**\n * Sequentially executes a list of effects (side-effects).\n * Errors are caught and logged without stopping the FSM.\n *\n * @param event The event that triggered these effects.\n * @param context The context at the time of execution.\n * @param effects A single effect or an array of effects.\n */\n private executeEffects__(\n event: TEvent,\n context: Readonly<TContext>,\n effects?: SingleOrArray<Effect<TEvent, TContext>>,\n ): void {\n if (!effects) {\n this.logger_.logMethodArgs?.('executeEffects__//skipped', {count: 0});\n return;\n }\n const effectsArray = Array.isArray(effects) ? effects : [effects];\n\n this.logger_.logMethodArgs?.('executeEffects__', {count: effectsArray.length});\n\n for (const effect of effectsArray) {\n try {\n const result = effect({event, context});\n if (result instanceof Promise) {\n result.catch((error) => {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n effect: effect.name || 'anonymous',\n state: this.stateSignal__.get().name,\n event,\n context,\n });\n });\n }\n } catch (error) {\n this.logger_.error('executeEffects__', 'effect_failed', error, {\n effect: effect.name || 'anonymous',\n state: this.stateSignal__.get().name,\n event,\n context,\n });\n }\n }\n }\n\n /**\n * Applies all assigner functions to the context to produce a new, updated context.\n * This process is atomic (all-or-nothing). If any assigner fails, the original\n * context is returned, and all updates are discarded.\n *\n * @param event The event that triggered the transition.\n * @param context The current context.\n * @param assigners A single assigner or an array of assigners.\n * @returns The new, updated context, or the original context if any assigner fails.\n */\n private applyAssigners__(\n event: TEvent,\n context: TContext,\n assigners?: SingleOrArray<Assigner<TEvent, TContext>>,\n ): TContext {\n if (!assigners) {\n this.logger_.logMethodArgs?.('applyAssigners__//skipped', {count: 0});\n return context;\n }\n\n const assignersArray = Array.isArray(assigners) ? assigners : [assigners];\n\n this.logger_.logMethodArgs?.('applyAssigners__', {count: assignersArray.length});\n\n try {\n let accContext = context;\n for (const assigner of assignersArray) {\n const nextContext = assigner({event, context: accContext});\n this.logger_.logMethodFull?.(\n `event.${event.type}.action.${assigner.name || 'anonymous'}`,\n {event, accContext},\n nextContext,\n );\n if (nextContext !== undefined && nextContext !== null) {\n accContext = nextContext;\n }\n }\n return accContext;\n } catch (error) {\n this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {\n event,\n context, // Log the original context for debugging.\n });\n // On ANY failure, discard all changes and return the original context.\n return context;\n }\n }\n\n /**\n * Starts the FSM by executing the entry effects and spawning the actors\n * of the initial/current state.\n */\n protected start_(): void {\n if (this.eventSignal__.isDestroyed) return;\n this.logger_.logMethod?.('start_');\n const currentState = this.stateSignal__.get();\n const initEvent = {type: '__init__'} as unknown as TEvent;\n this.executeEffects__(initEvent, currentState.context, this.config_.states[currentState.name]?.entry);\n this.spawnActors__(initEvent, currentState.context, this.config_.states[currentState.name]?.actors);\n }\n\n /**\n * Spawns all configured actors for the entered state.\n */\n private spawnActors__(\n event: TEvent,\n context: Readonly<TContext>,\n actors?: SingleOrArray<Actor<TEvent, TContext>>,\n ): void {\n if (!actors) {\n this.logger_.logMethodArgs?.('spawnActors__//skipped', {count: 0});\n return;\n }\n const actorsArray = Array.isArray(actors) ? actors : [actors];\n\n this.logger_.logMethodArgs?.('spawnActors__', {count: actorsArray.length});\n\n for (const actor of actorsArray) {\n try {\n const cleanup = actor({\n event,\n context,\n dispatch: this.dispatch,\n });\n if (typeof cleanup === 'function') {\n this.activeActorCleanups__.add(cleanup);\n }\n } catch (error) {\n this.logger_.error('spawnActors__', 'actor_failed', error, {\n actor: actor.name || 'anonymous',\n state: this.stateSignal__.get().name,\n event,\n context,\n });\n }\n }\n }\n\n /**\n * Cleans up (destroys) all currently active state actors.\n */\n private cleanupActors__(): void {\n this.logger_.logMethodArgs?.('cleanupActors__', {count: this.activeActorCleanups__.size});\n for (const cleanup of this.activeActorCleanups__) {\n try {\n cleanup();\n } catch (error) {\n this.logger_.error('cleanupActors__', 'cleanup_failed', error);\n }\n }\n this.activeActorCleanups__.clear();\n }\n\n /**\n * Destroys the service, cleaning up all internal signals and subscriptions\n * to prevent memory leaks.\n */\n public destroy(): void {\n this.logger_.logMethod?.('destroy');\n this.cleanupActors__();\n this.eventSignal__.destroy();\n this.stateSignal__.destroy();\n }\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "79475A1B16609BC164756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "C2B6164C11701D3564756E2164756E21",
10
10
  "names": []
11
11
  }
package/dist/type.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- import type { Awaitable, JsonObject, SingleOrArray } from '@alwatr/type-helper';
1
+ import type { Awaitable, SingleOrArray } from '@alwatr/type-helper';
2
2
  import type { SignalConfig } from '@alwatr/signal';
3
3
  /**
4
4
  * Represents the state of a state machine, including its current finite state value
5
5
  * and its extended state (context).
6
6
  *
7
7
  * @template TState The union type of the finite state values.
8
- * @template TContext The type of the context object (extended state).
8
+ * @template TContext The type of the machine's context (extended state).
9
9
  */
10
- export type MachineState<TState extends string, TContext extends JsonObject> = {
10
+ export type MachineState<TState extends string, TContext extends Record<string, unknown>> = {
11
11
  /** The current finite state value. */
12
12
  readonly name: TState;
13
13
  /** The context (extended state) of the machine, holding quantitative data. */
@@ -27,22 +27,28 @@ export interface MachineEvent<TEventType extends string = string> {
27
27
  }
28
28
  /**
29
29
  * Defines an assigner (synchronous action) that updates the context during transitions.
30
- * It must return a partial context object to merge.
30
+ * It returns the complete new context object or void/undefined if no changes are made.
31
31
  *
32
32
  * @template TContext The type of the machine's context.
33
33
  * @template TEvent The type of the event that triggered this assigner.
34
- * @returns A partial context object to be merged into the machine's context.
34
+ * @returns The complete next context object or void.
35
35
  */
36
- export type Assigner<TEvent extends MachineEvent, TContext extends JsonObject> = (event: Readonly<TEvent>, context: Readonly<TContext>) => Partial<TContext> | void;
36
+ export type Assigner<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
37
+ readonly event: Readonly<TEvent>;
38
+ readonly context: TContext;
39
+ }) => TContext | void;
37
40
  /**
38
- * Defines an effect (asynchronous side-effect action) executed on state entry/exit.
39
- * It can interact with the outside world and can dispatch new events.
41
+ * Defines an effect (fire-and-forget side-effect action) executed on state entry/exit.
42
+ * It can interact with the outside world, but does not return new events to trigger transitions.
40
43
  *
41
44
  * @template TContext The type of the machine's context.
42
45
  * @template TEvent The type of the event that triggered this effect.
43
46
  * @returns void or a Promise<void>.
44
47
  */
45
- export type Effect<TEvent extends MachineEvent, TContext extends JsonObject> = (event: Readonly<TEvent>, context: Readonly<TContext>) => Awaitable<TEvent | void>;
48
+ export type Effect<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
49
+ readonly event: Readonly<TEvent>;
50
+ readonly context: Readonly<TContext>;
51
+ }) => Awaitable<void>;
46
52
  /**
47
53
  * Defines a conditional guard function for a transition.
48
54
  * The transition is only taken if this function returns true.
@@ -51,20 +57,36 @@ export type Effect<TEvent extends MachineEvent, TContext extends JsonObject> = (
51
57
  * @template TEvent The type of the event.
52
58
  * @returns `true` if the transition should be taken, `false` otherwise.
53
59
  */
54
- export type Condition<TEvent extends MachineEvent, TContext extends JsonObject> = (event: Readonly<TEvent>, context: Readonly<TContext>) => boolean;
60
+ export type Guard<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
61
+ readonly event: Readonly<TEvent>;
62
+ readonly context: Readonly<TContext>;
63
+ }) => boolean;
64
+ /**
65
+ * Defines an actor (asynchronous lifecycle process) invoked on state entry.
66
+ * It starts an operation and can send events back to the parent FSM via `dispatch`.
67
+ * It can return a cleanup function to be called when exiting the state or destroying the machine.
68
+ *
69
+ * @template TEvent The union type of all events in the machine.
70
+ * @template TContext The type of the machine's context.
71
+ */
72
+ export type Actor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
73
+ readonly event: Readonly<TEvent>;
74
+ readonly context: Readonly<TContext>;
75
+ readonly dispatch: (event: TEvent) => void;
76
+ }) => (() => void) | void;
55
77
  /**
56
78
  * Defines a transition for a given state and event. It specifies the target state,
57
- * actions, and an optional condition.
79
+ * actions, and an optional guard.
58
80
  *
59
81
  * @template TState The type of the state.
60
82
  * @template TEvent The type of the event.
61
83
  * @template TContext The type of the machine's context.
62
84
  */
63
- export interface Transition<TState extends string, TEvent extends MachineEvent, TContext extends JsonObject> {
85
+ export interface Transition<TState extends string, TEvent extends MachineEvent, TContext extends Record<string, unknown>> {
64
86
  /** The target state to transition to. If undefined, it's an internal transition. */
65
87
  readonly target?: TState;
66
- /** A condition function that must return true for the transition to occur. */
67
- readonly condition?: Condition<TEvent, TContext>;
88
+ /** A guard function that must return true for the transition to occur. */
89
+ readonly guard?: Guard<TEvent, TContext>;
68
90
  /** An array of assigners to execute. These update context synchronously. */
69
91
  readonly assigners?: SingleOrArray<Assigner<TEvent, TContext>>;
70
92
  }
@@ -89,9 +111,9 @@ export interface FsmPersistenceConfig {
89
111
  *
90
112
  * @template TState The union type of all possible states.
91
113
  * @template TEvent The union type of all possible events.
92
- * @template TContext The type of the context object.
114
+ * @template TContext The type of the machine's context.
93
115
  */
94
- export interface StateMachineConfig<TState extends string, TEvent extends MachineEvent, TContext extends JsonObject> extends Pick<SignalConfig, 'name'> {
116
+ export interface StateMachineConfig<TState extends string, TEvent extends MachineEvent, TContext extends Record<string, unknown>> extends Pick<SignalConfig, 'name'> {
95
117
  /** The initial finite state value. */
96
118
  readonly initial: TState;
97
119
  /** The initial context (extended state) of the machine. */
@@ -111,6 +133,8 @@ export interface StateMachineConfig<TState extends string, TEvent extends Machin
111
133
  readonly entry?: SingleOrArray<Effect<TEvent, TContext>>;
112
134
  /** An array of side-effect effects to execute upon exiting this state. */
113
135
  readonly exit?: SingleOrArray<Effect<TEvent, TContext>>;
136
+ /** An array of actors to spawn upon entering this state, cleaned up when leaving. */
137
+ readonly actors?: SingleOrArray<Actor<TEvent, TContext>>;
114
138
  };
115
139
  };
116
140
  }
@@ -1 +1 @@
1
- {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,UAAU,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,CAAC,MAAM,SAAS,MAAM,EAAE,QAAQ,SAAS,UAAU,IAAI;IAC7E,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;CAC5B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,UAAU,SAAS,MAAM,GAAG,MAAM;IAC9D,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,8CAA8C;IAC9C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,CAAC,MAAM,SAAS,YAAY,EAAE,QAAQ,SAAS,UAAU,IAAI,CAC/E,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAExB,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;AAE9B;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,CAAC,MAAM,SAAS,YAAY,EAAE,QAAQ,SAAS,UAAU,IAAI,CAC7E,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAExB,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE9B;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,CAAC,MAAM,SAAS,YAAY,EAAE,QAAQ,SAAS,UAAU,IAAI,CAChF,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,KACxB,OAAO,CAAC;AAEb;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,SAAS,YAAY,EAAE,QAAQ,SAAS,UAAU;IACzG,oFAAoF;IACpF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;CAChE;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB,CACjC,MAAM,SAAS,MAAM,EACrB,MAAM,SAAS,YAAY,EAC3B,QAAQ,SAAS,UAAU,CAC3B,SAAQ,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC;IAClC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAE3B,sEAAsE;IACtE,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAElC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE;YACvB,0EAA0E;YAC1E,QAAQ,CAAC,EAAE,CAAC,EAAE;gBACZ,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;oBAAC,IAAI,EAAE,CAAC,CAAA;iBAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;aACzG,CAAC;YACF,2EAA2E;YAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;YACzD,0EAA0E;YAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;SACzD;KACF,CAAC;CACH"}
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.26.0",
3
+ "version": "9.29.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)",
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "sideEffects": false,
23
23
  "dependencies": {
24
- "@alwatr/logger": "9.25.0",
25
- "@alwatr/signal": "9.26.0"
24
+ "@alwatr/logger": "9.29.0",
25
+ "@alwatr/signal": "9.29.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@alwatr/nano-build": "9.25.0",
@@ -65,5 +65,5 @@
65
65
  "state",
66
66
  "typescript"
67
67
  ],
68
- "gitHead": "ef62969b649929413c640e69d241f4e99d6062e8"
68
+ "gitHead": "2f80c615d90e038dca634a2dfba8d900d3df6248"
69
69
  }
package/src/facade.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type {JsonObject} from '@alwatr/type-helper';
2
1
  import {createPersistentStateSignal, createStateSignal} from '@alwatr/signal';
3
2
 
4
3
  import {FsmService} from './fsm-service.js';
@@ -36,14 +35,14 @@ import type {MachineEvent, MachineState, StateMachineConfig} from './type.js';
36
35
  * on: {
37
36
  * TOGGLE: {
38
37
  * target: 'on',
39
- * assigners: [() => ({brightness: 100})],
38
+ * assigners: [({context}) => ({...context, brightness: 100})],
40
39
  * },
41
40
  * },
42
41
  * },
43
42
  * on: {
44
43
  * on: {
45
- * TOGGLE: {target: 'off', assigners: [() => ({brightness: 0})]},
46
- * SET_BRIGHTNESS: {assigners: [(event) => ({brightness: event.level})]},
44
+ * TOGGLE: {target: 'off', assigners: [({context}) => ({...context, brightness: 0})]},
45
+ * SET_BRIGHTNESS: {assigners: [({context, event}) => ({...context, brightness: event.level})]},
47
46
  * },
48
47
  * },
49
48
  * },
@@ -57,17 +56,19 @@ import type {MachineEvent, MachineState, StateMachineConfig} from './type.js';
57
56
  * console.log(`Light is ${state.name} with brightness ${state.context.brightness}`);
58
57
  * });
59
58
  *
60
- * lightService.eventSignal.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100
59
+ * lightService.dispatch({type: 'TOGGLE'}); // Light is on with brightness 100
61
60
  *
62
- * lightService.eventSignal.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50
61
+ * lightService.dispatch({type: 'SET_BRIGHTNESS', level: 50}); // Light is on with brightness 50
63
62
  *
64
63
  * // 5. Cleanup
65
64
  * // lightService.destroy();
66
65
  * ```
67
66
  */
68
- export function createFsmService<TState extends string, TEvent extends MachineEvent, TContext extends JsonObject>(
69
- config: StateMachineConfig<TState, TEvent, TContext>,
70
- ): FsmService<TState, TEvent, TContext> {
67
+ export function createFsmService<
68
+ TState extends string,
69
+ TEvent extends MachineEvent,
70
+ TContext extends Record<string, unknown> = Record<string, never>,
71
+ >(config: StateMachineConfig<TState, TEvent, TContext>): FsmService<TState, TEvent, TContext> {
71
72
  const initialValue: MachineState<TState, TContext> = {
72
73
  name: config.initial,
73
74
  context: config.context,