@statewalker/fsm 0.29.0 → 0.30.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.
@@ -1,5 +1,8 @@
1
+ type Handler<P extends unknown[] = unknown[], R = unknown> = (...args: P) => R; // Function;
2
+ // type Handler = Function;
3
+
1
4
  export class FsmBaseClass {
2
- handlers: Record<string, Function[]> = {};
5
+ handlers: Record<string, Handler[]> = {};
3
6
  data: Record<string, unknown> = {};
4
7
  constructor() {
5
8
  bindMethods(this, "setData", "getData");
@@ -14,13 +17,17 @@ export class FsmBaseClass {
14
17
 
15
18
  // ----------------------------------------------
16
19
  // internal methods
17
- _addHandler(type: string, handler: Function, direct: boolean = true) {
18
- const list = (this.handlers[type] = this.handlers[type] || []);
19
- direct ? list.push(handler) : list.unshift(handler);
20
- return () => this._removeHandler(type, handler);
20
+ protected _addHandler<T>(type: string, handler: T, direct: boolean = true) {
21
+ let list = this.handlers[type];
22
+ if (!list) {
23
+ list = this.handlers[type] = [];
24
+ }
25
+ const h = handler as Handler;
26
+ direct ? list.push(h) : list.unshift(h);
27
+ return () => this._removeHandler(type, h);
21
28
  }
22
29
 
23
- _removeHandler(type: string, handler: Function) {
30
+ protected _removeHandler<T>(type: string, handler: T) {
24
31
  let list = this.handlers[type];
25
32
  if (!list) return;
26
33
  list = list.filter((h) => h !== handler);
package/src/FsmProcess.ts CHANGED
@@ -1,8 +1,12 @@
1
- import { FsmBaseClass, bindMethods } from "./FsmBaseClass.ts";
2
- import { FsmState, FsmStateDump, FsmStateHandler } from "./FsmState.ts";
1
+ import { bindMethods, FsmBaseClass } from "./FsmBaseClass.ts";
2
+ import {
3
+ FsmState,
4
+ type FsmStateDump,
5
+ type FsmStateHandler,
6
+ } from "./FsmState.ts";
3
7
  import {
4
8
  EVENT_EMPTY,
5
- FsmStateConfig,
9
+ type FsmStateConfig,
6
10
  STATE_FINAL,
7
11
  STATE_INITIAL,
8
12
  } from "./FsmStateConfig.ts";
package/src/FsmState.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { FsmBaseClass, bindMethods } from "./FsmBaseClass.ts";
2
- import { FsmProcess } from "./FsmProcess.ts";
3
- import { FsmStateDescriptor } from "./FsmStateDescriptor.ts";
1
+ import { bindMethods, FsmBaseClass } from "./FsmBaseClass.ts";
2
+ import type { FsmProcess } from "./FsmProcess.ts";
3
+ import type { FsmStateDescriptor } from "./FsmStateDescriptor.ts";
4
4
 
5
5
  export type FsmStateDump = Record<string, unknown> & {
6
6
  key: string;
@@ -13,12 +13,12 @@ export type FsmStateHandler = (
13
13
 
14
14
  export type FsmStateDumpHandler = (
15
15
  state: FsmState,
16
- dump: FsmStateDump
16
+ dump: FsmStateDump,
17
17
  ) => void | Promise<void>;
18
18
 
19
19
  export type FsmStateErrorHandler = (
20
20
  state: FsmState,
21
- error: unknown
21
+ error: unknown,
22
22
  ) => void | Promise<void>;
23
23
 
24
24
  export class FsmState extends FsmBaseClass {
@@ -31,7 +31,7 @@ export class FsmState extends FsmBaseClass {
31
31
  process: FsmProcess,
32
32
  parent: FsmState | undefined,
33
33
  key: string,
34
- descriptor?: FsmStateDescriptor
34
+ descriptor?: FsmStateDescriptor,
35
35
  ) {
36
36
  super();
37
37
  this.process = process;
@@ -45,7 +45,7 @@ export class FsmState extends FsmBaseClass {
45
45
  "dump",
46
46
  "restore",
47
47
  "useData",
48
- "onStateError"
48
+ "onStateError",
49
49
  );
50
50
  }
51
51
 
@@ -1,6 +1,6 @@
1
1
  import {
2
- type FsmStateConfig,
3
2
  EVENT_ANY,
3
+ type FsmStateConfig,
4
4
  STATE_ANY,
5
5
  STATE_FINAL,
6
6
  } from "./FsmStateConfig.ts";
@@ -12,13 +12,16 @@ export class FsmStateDescriptor {
12
12
  static build(config: FsmStateConfig) {
13
13
  const descriptor = new FsmStateDescriptor();
14
14
  for (const [from, event, to] of config.transitions || []) {
15
- const index = (descriptor.transitions[from] =
16
- descriptor.transitions[from] || {});
15
+ let index = descriptor.transitions[from];
16
+ if (!index) {
17
+ index = descriptor.transitions[from] = {};
18
+ }
17
19
  index[event] = to;
18
20
  }
19
21
  if (config.states) {
20
22
  for (const substateConfig of config.states) {
21
- descriptor.states[substateConfig.key] = this.build(substateConfig);
23
+ descriptor.states[substateConfig.key] =
24
+ FsmStateDescriptor.build(substateConfig);
22
25
  }
23
26
  }
24
27
  return descriptor;
@@ -31,7 +34,7 @@ export class FsmStateDescriptor {
31
34
  [stateKey, EVENT_ANY],
32
35
  [STATE_ANY, EVENT_ANY],
33
36
  ];
34
- let targetKey;
37
+ let targetKey: string | undefined;
35
38
  for (
36
39
  let i = 0, len = pairs.length;
37
40
  targetKey === undefined && i < len;
package/src/index.ts CHANGED
@@ -2,6 +2,6 @@ export * from "./FsmProcess.ts";
2
2
  export * from "./FsmState.ts";
3
3
  export * from "./FsmStateConfig.ts";
4
4
  export * from "./FsmStateDescriptor.ts";
5
+ export * from "./newFsmProcess.ts";
6
+ export * from "./orchestrator/index.ts";
5
7
  export * from "./utils/index.ts";
6
- export * from "./newFsmProcess.js";
7
- export * from "./startFsmProcess.js";
@@ -1,13 +1,14 @@
1
- import { FsmProcess } from "./FsmProcess.js";
2
- import type { FsmStateConfig } from "./FsmStateConfig.js";
3
- import { isStateTransitionEnabled } from "./utils/transitions.js";
1
+ import { FsmProcess } from "./FsmProcess.ts";
2
+ import type { FsmStateConfig } from "./FsmStateConfig.ts";
3
+ import { isStateTransitionEnabled } from "./utils/transitions.ts";
4
4
 
5
- export type Module<C = unknown> = (
6
- context: C,
7
- ) =>
5
+ export type Module<C = unknown> = (context: C) =>
8
6
  | void
9
7
  | (() => void | Promise<void>)
10
- | Promise<void | (() => void | Promise<void>)>;
8
+ | Promise<void | (() => void | Promise<void>)>
9
+ // Event emitters / triggers
10
+ | Generator<string>
11
+ | AsyncGenerator<string>;
11
12
 
12
13
  export function newFsmProcess<C>(
13
14
  context: C,
@@ -27,28 +28,56 @@ export function newFsmProcess<C>(
27
28
  let started = false;
28
29
  let terminated = false;
29
30
  const process = new FsmProcess(config);
31
+ async function dispatch(event: string): Promise<boolean> {
32
+ return terminated ||
33
+ (event !== undefined &&
34
+ (!started || isStateTransitionEnabled(process, event)))
35
+ ? process.dispatch(event)
36
+ : false;
37
+ }
30
38
  process.onStateCreate((state) => {
31
39
  started = true;
32
40
  state.onEnter(async () => {
33
41
  const modules = (await load(state.key, process.event)) ?? [];
34
42
  for (const module of Array.isArray(modules) ? modules : [modules]) {
35
- const handler = await module(context);
36
- if (typeof handler === "function") {
37
- state.onExit(handler);
43
+ const result = await module?.(context);
44
+ if (isGenerator(result)) {
45
+ state.onExit(() => {
46
+ result.return?.(void 0);
47
+ });
48
+ (async () => {
49
+ for await (const event of result) {
50
+ if (terminated) {
51
+ break;
52
+ }
53
+ await dispatch(event);
54
+ if (terminated) {
55
+ break;
56
+ }
57
+ }
58
+ })();
59
+ } else if (typeof result === "function") {
60
+ state.onExit(result);
38
61
  }
39
62
  }
40
63
  });
41
64
  });
42
- async function dispatch(event: string): Promise<boolean> {
43
- return terminated ||
44
- (event !== undefined &&
45
- (!started || isStateTransitionEnabled(process, event)))
46
- ? process.dispatch(event)
47
- : false;
48
- }
49
65
  async function shutdown(): Promise<void> {
50
66
  await process.shutdown();
51
67
  terminated = true;
52
68
  }
53
69
  return [dispatch, shutdown] as const;
70
+
71
+ function isGenerator(
72
+ value: unknown,
73
+ ): value is
74
+ | Generator<string, void, unknown>
75
+ | AsyncGenerator<string, void, unknown> {
76
+ return (
77
+ typeof value === "object" &&
78
+ value !== null &&
79
+ "next" in value &&
80
+ typeof (value as Generator<string, void, unknown>).next === "function"
81
+ );
82
+ }
54
83
  }
@@ -0,0 +1,70 @@
1
+ import type { FsmStateConfig } from "../FsmStateConfig.ts";
2
+ import { type StageHandler, toStageHandlers } from "./types.ts";
3
+
4
+ export interface IProcessConfigManager {
5
+ getProcessConfig(processName: string): FsmStateConfig;
6
+ getHandlersLoader<C = unknown>(
7
+ processName: string,
8
+ ): (state: string, event: undefined | string) => StageHandler<C>[];
9
+ registerConfig(name: string, config: FsmStateConfig): () => void;
10
+ registerHandlers(name: string, ...modules: unknown[]): () => void;
11
+ }
12
+
13
+ export class ProcessConfigManager implements IProcessConfigManager {
14
+ protected configs: Record<string, FsmStateConfig> = {};
15
+ protected handlers: Record<string, unknown[]> = {};
16
+
17
+ getProcessConfig(processName: string): FsmStateConfig {
18
+ return this.configs[processName] ?? { key: "Main" };
19
+ }
20
+
21
+ getHandlersLoader<C = unknown>(
22
+ processName: string,
23
+ ): (state: string, event: undefined | string) => StageHandler<C>[] {
24
+ const config = this.getProcessConfig(processName);
25
+ return (stateKey: string) => {
26
+ const modulesKeys: string[] = [];
27
+ if (stateKey === config.key) {
28
+ modulesKeys.push("default");
29
+ }
30
+ modulesKeys.push(
31
+ stateKey,
32
+ `${stateKey}Controller`,
33
+ `${stateKey}StateController`,
34
+ `${stateKey}View`,
35
+ `${stateKey}StateView`,
36
+ `${stateKey}Trigger`,
37
+ `${stateKey}StateTrigger`,
38
+ `${stateKey}Test`,
39
+ `${stateKey}StateTest`,
40
+ );
41
+ const keysSet = new Set(modulesKeys);
42
+ const processHandlers = this.handlers[processName] ?? [];
43
+ const handlers = toStageHandlers(processHandlers, (key: string) =>
44
+ keysSet.has(key),
45
+ );
46
+ return handlers;
47
+ };
48
+ }
49
+
50
+ registerConfig(name: string, config: FsmStateConfig): () => void {
51
+ this.configs[name] = config;
52
+ return () => {
53
+ delete this.configs[name];
54
+ };
55
+ }
56
+
57
+ registerHandlers(name: string, ...modules: unknown[]): () => void {
58
+ let list: unknown[] = this.handlers[name];
59
+ if (!list) {
60
+ list = this.handlers[name] = [];
61
+ }
62
+ list.push(modules);
63
+ return () => {
64
+ list = list.filter((v) => v !== modules);
65
+ if (list.length === 0) {
66
+ delete this.handlers[name];
67
+ }
68
+ };
69
+ }
70
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Key used to bind the dispatch function to the process context.
3
+ * The dispatch function allows triggering state transitions by dispatching events.
4
+ *
5
+ * Example usage:
6
+ * ```typescript
7
+ * const context: Record<string, unknown> = {};
8
+ * // Initialize the FSM process using `startFsmProcess`
9
+ * const dispatch = context["fsm:dispatch"] as (event: string) => Promise<void>;
10
+ * await dispatch("someEvent");
11
+ */
12
+ export const KEY_DISPATCH = "fsm:dispatch" as const;
13
+
14
+ /**
15
+ * Key used to bind the terminate function to the process context.
16
+ * The terminate function is used to gracefully shut down the FSM process.
17
+ * It returns a Promise that resolves when the process has been fully terminated.
18
+ * Example usage:
19
+ * ```typescript
20
+ * const context: Record<string, unknown> = {};
21
+ * // Initialize the FSM process using `startFsmProcess`
22
+ * const terminate = context["fsm:terminate"] as () => Promise<void>;
23
+ * await terminate();
24
+ * ```
25
+ */
26
+ export const KEY_TERMINATE = "fsm:terminate" as const;
27
+
28
+ /**
29
+ * Key used to bind the current stack of active states to the process context.
30
+ * This provides a snapshot of the states the FSM process has entered.
31
+ * It is represented as an array of strings -- state keys.
32
+ *
33
+ * Example usage:
34
+ * ```typescript
35
+ * const context: Record<string, unknown> = {};
36
+ * // Initialize the FSM process using `startFsmProcess`
37
+ * const states = context["fsm:states"] as string[];
38
+ * console.log("Current active states:", states);
39
+ * ```
40
+ */
41
+ export const KEY_STATES = "fsm:states" as const;
42
+
43
+ /**
44
+ * Key used to bind the current event being processed to the process context.
45
+ * This represents the event that triggered the current state transition.
46
+ * It is represented as a string.
47
+ * Example usage:
48
+ * ```typescript
49
+ * const context: Record<string, unknown> = {};
50
+ * // Initialize the FSM process using `startFsmProcess`
51
+ * const event = context["fsm:event"] as string;
52
+ * console.log("Current event:", event);
53
+ * ```
54
+ */
55
+ export const KEY_EVENT = "fsm:event" as const;
56
+
57
+ //-------------------------------------------------
58
+
59
+ // export const KEY_START_PROCESS = "fsm:sys:start" as const;
60
+ /**
61
+ * Key used to register a new FSM process configuration in the process context.
62
+ * This key is associated with a function that allows adding new FSM configurations.
63
+ * The function takes a process name and its configuration, and returns a cleanup function
64
+ * to remove the registered configuration.
65
+ * Example usage:
66
+ * ```typescript
67
+ * const context: Record<string, unknown> = {};
68
+ * initProcessManager(context); // Initialize the process manager in the context
69
+ * ...
70
+ * type StateConfig = { key: string; transitions?: string[][]; states?: StateConfig[] };
71
+ * const registerConfig = context["fsm:sys:registerConfig"] as (name: string, config: StateConfig) => () => void;
72
+ * const unregister = registerConfig("myProcess", { key: "Main", transitions: [...] });
73
+ * // To unregister the configuration later
74
+ * unregister();
75
+ * ```
76
+ */
77
+ export const KEY_REGISTER_CONFIG = "fsm:sys:registerConfig" as const;
78
+
79
+ /**
80
+ * Key used to register state and event handlers for FSM processes in the process context.
81
+ * This key is associated with a function that allows adding handler modules for a specific process.
82
+ * The function takes a process name and a variable number of handler modules, returning a cleanup function
83
+ * to remove the registered handlers.
84
+ * Example usage:
85
+ * ```typescript
86
+ * const context: Record<string, unknown> = {};
87
+ * initProcessManager(context); // Initialize the process manager in the context
88
+ * ...
89
+ * const registerHandlers = context["fsm:sys:registerHandlers"] as (name: string, ...modules: unknown[]) => () => void;
90
+ * const handlerModule1 = {
91
+ * Main: async (context) => { ... },
92
+ * SomeState: async (context) => { ... },
93
+ * }
94
+ * // Views layer can be defined in the same or separate modules
95
+ * const handlerModule3 = {
96
+ * MainView: async (context) => { ... },
97
+ * SomeStateView: async (context) => { ... },
98
+ * }
99
+ * // A common handler function to call for all states
100
+ * const handlerModule2 = (context) => { ... };
101
+ * const unregister = registerHandlers(
102
+ * "myProcess",
103
+ * handlerModule1,
104
+ * handlerModule2,
105
+ * handlerModule3
106
+ * );
107
+ * // To unregister the handlers later
108
+ * unregister();
109
+ * ```
110
+ */
111
+ export const KEY_REGISTER_HANDLERS = "fsm:sys:registerHandlers" as const;
112
+
113
+ /**
114
+ * Key used to launch an FSM process in the process context.
115
+ * This key is associated with a function that initiates the FSM process based on its name and context.
116
+ * The function takes the process name and context, returning either nothing, a cleanup function,
117
+ * or a Promise that resolves to either nothing or a cleanup function.
118
+ * Example usage:
119
+ * ```typescript
120
+ * const context: Record<string, unknown> = {};
121
+ * initProcessManager(context); // Initialize the process manager in the context
122
+ * // Register process configurations and handlers as needed
123
+ *
124
+ * const launchProcess = context["fsm:sys:launch"] as (name: string, context: Record<string, unknown>) => () => Promise<void>;
125
+ * const terminate = await launchProcess("myProcess", context);
126
+ * // To terminate the process later
127
+ * terminate?.();
128
+ * ```
129
+ */
130
+ export const KEY_LAUNCH_PROCESS = "fsm:sys:launch" as const;
@@ -0,0 +1,6 @@
1
+ export * from "./constants.ts";
2
+ export * from "./index.ts";
3
+ export * from "./launcher.ts";
4
+ export * from "./ProcessConfigManager.ts";
5
+ export * from "./startFsmProcess.ts";
6
+ export * from "./types.ts";
@@ -0,0 +1,12 @@
1
+ export function isGenerator(
2
+ value: unknown,
3
+ ): value is
4
+ | Generator<string, void, unknown>
5
+ | AsyncGenerator<string, void, unknown> {
6
+ return (
7
+ typeof value === "object" &&
8
+ value !== null &&
9
+ "next" in value &&
10
+ typeof (value as Generator<string, void, unknown>).next === "function"
11
+ );
12
+ }
@@ -0,0 +1,72 @@
1
+ import type { FsmStateConfig } from "../FsmStateConfig.ts";
2
+ import { ProcessConfigManager } from "./ProcessConfigManager.ts";
3
+ import { startFsmProcess } from "./startFsmProcess.ts";
4
+ import type { StageHandler } from "./types.ts";
5
+
6
+ export type ProcessConfig = {
7
+ name: string;
8
+ config?: FsmStateConfig;
9
+ handlers?: (
10
+ | StageHandler
11
+ | {
12
+ [state: string]: StageHandler | StageHandler[];
13
+ }
14
+ )[];
15
+ };
16
+ export type ProcessLauncherConfig = {
17
+ start: string[];
18
+ context?: Record<string, unknown>;
19
+ processes: ProcessConfig[];
20
+ };
21
+
22
+ export type LauncherRootContext = Record<string, unknown> & {
23
+ launch: (
24
+ processName: string,
25
+ context: Record<string, unknown>,
26
+ startEvent?: string,
27
+ ) => Promise<() => Promise<void>>;
28
+ };
29
+ export async function launcher(
30
+ init: (
31
+ root: LauncherRootContext,
32
+ ) => ProcessLauncherConfig | Promise<ProcessLauncherConfig>,
33
+ ) {
34
+ const configsManager = new ProcessConfigManager();
35
+
36
+ async function launch(
37
+ processName: string,
38
+ context: Record<string, unknown>,
39
+ startEvent: string = "start",
40
+ ) {
41
+ const config = configsManager.getProcessConfig(processName);
42
+ const load = configsManager.getHandlersLoader(processName);
43
+ return startFsmProcess(context, config, load, startEvent);
44
+ }
45
+
46
+ const rootContext: LauncherRootContext = {
47
+ launch,
48
+ };
49
+ const config = await init(rootContext);
50
+ for (const process of config.processes) {
51
+ if (process.config) {
52
+ configsManager.registerConfig(process.name, process.config);
53
+ }
54
+ if (process.handlers) {
55
+ configsManager.registerHandlers(process.name, ...process.handlers);
56
+ }
57
+ }
58
+
59
+ const shutdowns: (() => Promise<void>)[] = [];
60
+ for (const processName of config.start ?? []) {
61
+ const parent = config.context ?? rootContext;
62
+ const context = { parent };
63
+ const shutdown = await launch(processName, context);
64
+ shutdowns.push(shutdown);
65
+ }
66
+
67
+ return async () => {
68
+ for (const shutdown of shutdowns) {
69
+ await shutdown();
70
+ }
71
+ };
72
+ }
@@ -0,0 +1,73 @@
1
+ import { FsmProcess } from "../FsmProcess.ts";
2
+ import type { FsmStateConfig } from "../FsmStateConfig.ts";
3
+ import { isStateTransitionEnabled } from "../utils/index.ts";
4
+ import {
5
+ KEY_DISPATCH,
6
+ KEY_EVENT,
7
+ KEY_STATES,
8
+ KEY_TERMINATE,
9
+ } from "./constants.ts";
10
+ import { isGenerator } from "./isGenerator.ts";
11
+ import type { StageHandler } from "./types.ts";
12
+
13
+ export async function startFsmProcess<C = unknown>(
14
+ context: C,
15
+ config: FsmStateConfig,
16
+ load: (
17
+ state: string,
18
+ event: undefined | string,
19
+ ) => StageHandler<C>[] | Promise<StageHandler<C>[]>,
20
+ startEvent = "",
21
+ ): Promise<() => Promise<void>> {
22
+ let terminated = false;
23
+ const ctx = context as Record<string, unknown>;
24
+ const process = new FsmProcess(config);
25
+ const statesStack: string[] = [];
26
+ // ctx[KEY_PROCESS] = process;
27
+ process.onStateCreate((state) => {
28
+ state.onEnter(async () => {
29
+ statesStack.push(state.key);
30
+ ctx[KEY_STATES] = [...statesStack];
31
+ ctx[KEY_EVENT] = process.event;
32
+ const modules = (await load(state.key, process.event)) ?? [];
33
+ for (const module of Array.isArray(modules) ? modules : [modules]) {
34
+ const result = await module?.(context);
35
+ if (isGenerator(result)) {
36
+ let stateExited = false;
37
+ state.onExit(() => {
38
+ stateExited = true;
39
+ result.return?.(void 0);
40
+ });
41
+ (async () => {
42
+ for await (const event of result) {
43
+ if (stateExited || terminated) {
44
+ break;
45
+ }
46
+ await dispatch(event);
47
+ }
48
+ })();
49
+ } else if (typeof result === "function") {
50
+ state.onExit(result);
51
+ }
52
+ }
53
+ });
54
+ state.onExit(() => {
55
+ statesStack.pop();
56
+ ctx[KEY_STATES] = [...statesStack];
57
+ ctx[KEY_EVENT] = process.event;
58
+ });
59
+ });
60
+ async function terminate(): Promise<void> {
61
+ terminated = true;
62
+ await process.shutdown();
63
+ }
64
+ async function dispatch(event: string): Promise<void> {
65
+ if (event !== undefined && isStateTransitionEnabled(process, event)) {
66
+ await process.dispatch(event);
67
+ }
68
+ }
69
+ ctx[KEY_DISPATCH] = dispatch;
70
+ ctx[KEY_TERMINATE] = terminate;
71
+ await process.dispatch(startEvent);
72
+ return terminate;
73
+ }
@@ -0,0 +1,34 @@
1
+ export type StageHandler<C = Record<string, unknown>> = (
2
+ context: C,
3
+ ) =>
4
+ | void
5
+ | (() => void | Promise<void>)
6
+ | Promise<void | (() => void | Promise<void>)>
7
+ | AsyncGenerator<string, void, unknown>
8
+ | Generator<string, void, unknown>;
9
+
10
+ export function toStageHandlers<C>(
11
+ value: unknown,
12
+ accept: (key: string, value: unknown) => boolean,
13
+ ): StageHandler<C>[] {
14
+ return visit(value);
15
+
16
+ function visit(val: unknown): StageHandler<C>[] {
17
+ if (!val) {
18
+ return [];
19
+ }
20
+ if (typeof val === "function") {
21
+ return [val] as StageHandler<C>[];
22
+ }
23
+ if (typeof val !== "object") {
24
+ return [];
25
+ }
26
+ if (Array.isArray(val)) {
27
+ return val.flatMap(visit);
28
+ }
29
+ const modulesIndex = val as Record<string, unknown>;
30
+ return Object.entries(modulesIndex).flatMap(([key, value]) => {
31
+ return accept(key, value) ? visit(value) : [];
32
+ });
33
+ }
34
+ }
@@ -6,24 +6,29 @@ export function callStateHandlers(state: FsmState) {
6
6
  const key = state.key;
7
7
  for (
8
8
  let parent: FsmState | undefined = state.parent;
9
- !!parent;
9
+ parent;
10
10
  parent = parent?.parent
11
11
  ) {
12
12
  const handlers =
13
13
  parent.getData<Record<string, FsmStateHandler[]>>(KEY_HANDLERS)?.[key];
14
- handlers?.forEach((handler) => handler(state));
14
+ handlers?.forEach((handler) => {
15
+ handler(state);
16
+ });
15
17
  }
16
18
  }
17
19
 
18
20
  export function addSubstateHandlers(
19
21
  state: FsmState,
20
- handlers: Record<string, FsmStateHandler>
22
+ handlers: Record<string, FsmStateHandler>,
21
23
  ) {
22
24
  const oldIndex =
23
25
  state.getData<Record<string, FsmStateHandler[]>>(KEY_HANDLERS);
24
26
  const index = oldIndex ? { ...oldIndex } : {};
25
27
  for (const [key, handler] of Object.entries(handlers)) {
26
- const list = (index[key] = index[key] || []);
28
+ let list = index[key];
29
+ if (!list) {
30
+ list = index[key] = [];
31
+ }
27
32
  list.push(handler);
28
33
  }
29
34
  state.setData(KEY_HANDLERS, index);