@statewalker/fsm 0.28.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.
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
+ type Handler<P extends unknown[] = unknown[], R = unknown> = (...args: P) => R;
1
2
  declare class FsmBaseClass {
2
- handlers: Record<string, Function[]>;
3
+ handlers: Record<string, Handler[]>;
3
4
  data: Record<string, unknown>;
4
5
  constructor();
5
6
  setData<T>(key: string, value: T): this;
6
7
  getData<T>(key: string): T | undefined;
7
- _addHandler(type: string, handler: Function, direct?: boolean): () => void;
8
- _removeHandler(type: string, handler: Function): void;
8
+ protected _addHandler<T>(type: string, handler: T, direct?: boolean): () => void;
9
+ protected _removeHandler<T>(type: string, handler: T): void;
9
10
  _runHandlerParallel(type: string, ...args: unknown[]): Promise<void>;
10
11
  _runHandler(type: string, ...args: unknown[]): Promise<void>;
11
12
  _handleError(error: Error | unknown): Promise<void>;
@@ -92,6 +93,171 @@ declare class FsmProcess extends FsmBaseClass {
92
93
  _update(): boolean;
93
94
  }
94
95
 
96
+ type Module<C = unknown> = (context: C) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)> | Generator<string> | AsyncGenerator<string>;
97
+ declare function newFsmProcess<C>(context: C, config: FsmStateConfig, load: (state: string, event: undefined | string) => undefined | Module<C> | Module<C>[] | Promise<undefined | Module<C> | Module<C>[]>): [
98
+ dispatch: (event: string) => Promise<boolean>,
99
+ shutdown: () => Promise<void>
100
+ ];
101
+
102
+ /**
103
+ * Key used to bind the dispatch function to the process context.
104
+ * The dispatch function allows triggering state transitions by dispatching events.
105
+ *
106
+ * Example usage:
107
+ * ```typescript
108
+ * const context: Record<string, unknown> = {};
109
+ * // Initialize the FSM process using `startFsmProcess`
110
+ * const dispatch = context["fsm:dispatch"] as (event: string) => Promise<void>;
111
+ * await dispatch("someEvent");
112
+ */
113
+ declare const KEY_DISPATCH: "fsm:dispatch";
114
+ /**
115
+ * Key used to bind the terminate function to the process context.
116
+ * The terminate function is used to gracefully shut down the FSM process.
117
+ * It returns a Promise that resolves when the process has been fully terminated.
118
+ * Example usage:
119
+ * ```typescript
120
+ * const context: Record<string, unknown> = {};
121
+ * // Initialize the FSM process using `startFsmProcess`
122
+ * const terminate = context["fsm:terminate"] as () => Promise<void>;
123
+ * await terminate();
124
+ * ```
125
+ */
126
+ declare const KEY_TERMINATE: "fsm:terminate";
127
+ /**
128
+ * Key used to bind the current stack of active states to the process context.
129
+ * This provides a snapshot of the states the FSM process has entered.
130
+ * It is represented as an array of strings -- state keys.
131
+ *
132
+ * Example usage:
133
+ * ```typescript
134
+ * const context: Record<string, unknown> = {};
135
+ * // Initialize the FSM process using `startFsmProcess`
136
+ * const states = context["fsm:states"] as string[];
137
+ * console.log("Current active states:", states);
138
+ * ```
139
+ */
140
+ declare const KEY_STATES: "fsm:states";
141
+ /**
142
+ * Key used to bind the current event being processed to the process context.
143
+ * This represents the event that triggered the current state transition.
144
+ * It is represented as a string.
145
+ * Example usage:
146
+ * ```typescript
147
+ * const context: Record<string, unknown> = {};
148
+ * // Initialize the FSM process using `startFsmProcess`
149
+ * const event = context["fsm:event"] as string;
150
+ * console.log("Current event:", event);
151
+ * ```
152
+ */
153
+ declare const KEY_EVENT: "fsm:event";
154
+ /**
155
+ * Key used to register a new FSM process configuration in the process context.
156
+ * This key is associated with a function that allows adding new FSM configurations.
157
+ * The function takes a process name and its configuration, and returns a cleanup function
158
+ * to remove the registered configuration.
159
+ * Example usage:
160
+ * ```typescript
161
+ * const context: Record<string, unknown> = {};
162
+ * initProcessManager(context); // Initialize the process manager in the context
163
+ * ...
164
+ * type StateConfig = { key: string; transitions?: string[][]; states?: StateConfig[] };
165
+ * const registerConfig = context["fsm:sys:registerConfig"] as (name: string, config: StateConfig) => () => void;
166
+ * const unregister = registerConfig("myProcess", { key: "Main", transitions: [...] });
167
+ * // To unregister the configuration later
168
+ * unregister();
169
+ * ```
170
+ */
171
+ declare const KEY_REGISTER_CONFIG: "fsm:sys:registerConfig";
172
+ /**
173
+ * Key used to register state and event handlers for FSM processes in the process context.
174
+ * This key is associated with a function that allows adding handler modules for a specific process.
175
+ * The function takes a process name and a variable number of handler modules, returning a cleanup function
176
+ * to remove the registered handlers.
177
+ * Example usage:
178
+ * ```typescript
179
+ * const context: Record<string, unknown> = {};
180
+ * initProcessManager(context); // Initialize the process manager in the context
181
+ * ...
182
+ * const registerHandlers = context["fsm:sys:registerHandlers"] as (name: string, ...modules: unknown[]) => () => void;
183
+ * const handlerModule1 = {
184
+ * Main: async (context) => { ... },
185
+ * SomeState: async (context) => { ... },
186
+ * }
187
+ * // Views layer can be defined in the same or separate modules
188
+ * const handlerModule3 = {
189
+ * MainView: async (context) => { ... },
190
+ * SomeStateView: async (context) => { ... },
191
+ * }
192
+ * // A common handler function to call for all states
193
+ * const handlerModule2 = (context) => { ... };
194
+ * const unregister = registerHandlers(
195
+ * "myProcess",
196
+ * handlerModule1,
197
+ * handlerModule2,
198
+ * handlerModule3
199
+ * );
200
+ * // To unregister the handlers later
201
+ * unregister();
202
+ * ```
203
+ */
204
+ declare const KEY_REGISTER_HANDLERS: "fsm:sys:registerHandlers";
205
+ /**
206
+ * Key used to launch an FSM process in the process context.
207
+ * This key is associated with a function that initiates the FSM process based on its name and context.
208
+ * The function takes the process name and context, returning either nothing, a cleanup function,
209
+ * or a Promise that resolves to either nothing or a cleanup function.
210
+ * Example usage:
211
+ * ```typescript
212
+ * const context: Record<string, unknown> = {};
213
+ * initProcessManager(context); // Initialize the process manager in the context
214
+ * // Register process configurations and handlers as needed
215
+ *
216
+ * const launchProcess = context["fsm:sys:launch"] as (name: string, context: Record<string, unknown>) => () => Promise<void>;
217
+ * const terminate = await launchProcess("myProcess", context);
218
+ * // To terminate the process later
219
+ * terminate?.();
220
+ * ```
221
+ */
222
+ declare const KEY_LAUNCH_PROCESS: "fsm:sys:launch";
223
+
224
+ type StageHandler<C = Record<string, unknown>> = (context: C) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)> | AsyncGenerator<string, void, unknown> | Generator<string, void, unknown>;
225
+ declare function toStageHandlers<C>(value: unknown, accept: (key: string, value: unknown) => boolean): StageHandler<C>[];
226
+
227
+ type ProcessConfig = {
228
+ name: string;
229
+ config?: FsmStateConfig;
230
+ handlers?: (StageHandler | {
231
+ [state: string]: StageHandler | StageHandler[];
232
+ })[];
233
+ };
234
+ type ProcessLauncherConfig = {
235
+ start: string[];
236
+ context?: Record<string, unknown>;
237
+ processes: ProcessConfig[];
238
+ };
239
+ type LauncherRootContext = Record<string, unknown> & {
240
+ launch: (processName: string, context: Record<string, unknown>, startEvent?: string) => Promise<() => Promise<void>>;
241
+ };
242
+ declare function launcher(init: (root: LauncherRootContext) => ProcessLauncherConfig | Promise<ProcessLauncherConfig>): Promise<() => Promise<void>>;
243
+
244
+ interface IProcessConfigManager {
245
+ getProcessConfig(processName: string): FsmStateConfig;
246
+ getHandlersLoader<C = unknown>(processName: string): (state: string, event: undefined | string) => StageHandler<C>[];
247
+ registerConfig(name: string, config: FsmStateConfig): () => void;
248
+ registerHandlers(name: string, ...modules: unknown[]): () => void;
249
+ }
250
+ declare class ProcessConfigManager implements IProcessConfigManager {
251
+ protected configs: Record<string, FsmStateConfig>;
252
+ protected handlers: Record<string, unknown[]>;
253
+ getProcessConfig(processName: string): FsmStateConfig;
254
+ getHandlersLoader<C = unknown>(processName: string): (state: string, event: undefined | string) => StageHandler<C>[];
255
+ registerConfig(name: string, config: FsmStateConfig): () => void;
256
+ registerHandlers(name: string, ...modules: unknown[]): () => void;
257
+ }
258
+
259
+ declare function startFsmProcess<C = unknown>(context: C, config: FsmStateConfig, load: (state: string, event: undefined | string) => StageHandler<C>[] | Promise<StageHandler<C>[]>, startEvent?: string): Promise<() => Promise<void>>;
260
+
95
261
  type Printer = (...args: unknown[]) => void;
96
262
  type PrinterConfig = {
97
263
  prefix?: string;
@@ -117,13 +283,4 @@ declare function setStateTracer(state: FsmState, print?: Printer): void;
117
283
  declare function isStateTransitionEnabled(process: FsmProcess, event: string): boolean;
118
284
  declare function getStateTransitions(state?: FsmState): [from: string, event: string, to: string][];
119
285
 
120
- type Module<C = unknown> = (context: C) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>;
121
- declare function newFsmProcess<C>(context: C, config: FsmStateConfig, load: (state: string, event: undefined | string) => undefined | Module<C> | Module<C>[] | Promise<undefined | Module<C> | Module<C>[]>): [
122
- dispatch: (event: string) => Promise<boolean>,
123
- shutdown: () => Promise<void>
124
- ];
125
-
126
- type ModuleOrTrigger<C = unknown> = (context: C) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)> | AsyncGenerator<string, void, unknown> | Generator<string, void, unknown>;
127
- declare function startFsmProcess<C>(context: C, config: FsmStateConfig, load: (state: string, event: undefined | string) => undefined | ModuleOrTrigger<C> | ModuleOrTrigger<C>[] | Promise<undefined | ModuleOrTrigger<C> | ModuleOrTrigger<C>[]>, startEvent?: string): () => Promise<void>;
128
-
129
- export { EVENT_ANY, EVENT_EMPTY, type FsmEventKey, FsmProcess, type FsmProcessDump, type FsmProcessDumpHandler, type FsmProcessHandler, FsmState, type FsmStateConfig, FsmStateDescriptor, type FsmStateDump, type FsmStateDumpHandler, type FsmStateErrorHandler, type FsmStateHandler, type FsmStateKey, KEY_PRINTER, type Module, type ModuleOrTrigger, type Printer, type PrinterConfig, STATE_ANY, STATE_FINAL, STATE_INITIAL, STATUS_ENTER, STATUS_EXIT, STATUS_FINISHED, STATUS_FIRST, STATUS_LAST, STATUS_LEAF, STATUS_NEXT, STATUS_NONE, getPrinter, getProcessPrinter, getStateTransitions, isStateTransitionEnabled, newFsmProcess, newProcess, preparePrinter, setPrinter, setProcessPrinter, setProcessTracer, setStateTracer, startFsmProcess };
286
+ export { EVENT_ANY, EVENT_EMPTY, type FsmEventKey, FsmProcess, type FsmProcessDump, type FsmProcessDumpHandler, type FsmProcessHandler, FsmState, type FsmStateConfig, FsmStateDescriptor, type FsmStateDump, type FsmStateDumpHandler, type FsmStateErrorHandler, type FsmStateHandler, type FsmStateKey, type IProcessConfigManager, KEY_DISPATCH, KEY_EVENT, KEY_LAUNCH_PROCESS, KEY_PRINTER, KEY_REGISTER_CONFIG, KEY_REGISTER_HANDLERS, KEY_STATES, KEY_TERMINATE, type LauncherRootContext, type Module, type Printer, type PrinterConfig, type ProcessConfig, ProcessConfigManager, type ProcessLauncherConfig, STATE_ANY, STATE_FINAL, STATE_INITIAL, STATUS_ENTER, STATUS_EXIT, STATUS_FINISHED, STATUS_FIRST, STATUS_LAST, STATUS_LEAF, STATUS_NEXT, STATUS_NONE, type StageHandler, getPrinter, getProcessPrinter, getStateTransitions, isStateTransitionEnabled, launcher, newFsmProcess, newProcess, preparePrinter, setPrinter, setProcessPrinter, setProcessTracer, setStateTracer, startFsmProcess, toStageHandlers };
package/dist/index.js CHANGED
@@ -15,9 +15,13 @@ var FsmBaseClass = class {
15
15
  // ----------------------------------------------
16
16
  // internal methods
17
17
  _addHandler(type, handler, direct = true) {
18
- const list = this.handlers[type] = this.handlers[type] || [];
19
- direct ? list.push(handler) : list.unshift(handler);
20
- return () => this._removeHandler(type, handler);
18
+ let list = this.handlers[type];
19
+ if (!list) {
20
+ list = this.handlers[type] = [];
21
+ }
22
+ const h = handler;
23
+ direct ? list.push(h) : list.unshift(h);
24
+ return () => this._removeHandler(type, h);
21
25
  }
22
26
  _removeHandler(type, handler) {
23
27
  let list = this.handlers[type];
@@ -129,12 +133,15 @@ var FsmStateDescriptor = class _FsmStateDescriptor {
129
133
  static build(config) {
130
134
  const descriptor = new _FsmStateDescriptor();
131
135
  for (const [from, event, to] of config.transitions || []) {
132
- const index = descriptor.transitions[from] = descriptor.transitions[from] || {};
136
+ let index = descriptor.transitions[from];
137
+ if (!index) {
138
+ index = descriptor.transitions[from] = {};
139
+ }
133
140
  index[event] = to;
134
141
  }
135
142
  if (config.states) {
136
143
  for (const substateConfig of config.states) {
137
- descriptor.states[substateConfig.key] = this.build(substateConfig);
144
+ descriptor.states[substateConfig.key] = _FsmStateDescriptor.build(substateConfig);
138
145
  }
139
146
  }
140
147
  return descriptor;
@@ -299,13 +306,192 @@ var FsmProcess = class extends FsmBaseClass {
299
306
  }
300
307
  };
301
308
 
309
+ // src/utils/transitions.ts
310
+ function isStateTransitionEnabled(process, event) {
311
+ const transitions = getStateTransitions(process.state);
312
+ let active = false;
313
+ for (const [from, ev, to] of transitions) {
314
+ if (ev === event) {
315
+ active = true;
316
+ break;
317
+ }
318
+ }
319
+ return active;
320
+ }
321
+ function getStateTransitions(state) {
322
+ const result = [];
323
+ let index = {};
324
+ if (state) {
325
+ let prevStateKey = state.key;
326
+ for (let parent = state?.parent; parent; parent = parent.parent) {
327
+ if (!parent.descriptor) continue;
328
+ result.push(
329
+ ...getTransitionsFromDescriptor(parent.descriptor, prevStateKey, index)
330
+ );
331
+ prevStateKey = parent.key;
332
+ }
333
+ }
334
+ return result.reverse();
335
+ }
336
+ function getTransitionsFromDescriptor(descriptor, prevStateKey, index = {}) {
337
+ const result = [];
338
+ const prevStateKeys = [prevStateKey, "*"];
339
+ for (const prevKey of prevStateKeys) {
340
+ const targets = descriptor.transitions[prevKey];
341
+ if (targets) {
342
+ for (const [event, target] of Object.entries(targets)) {
343
+ if (index[event]) continue;
344
+ if (target) {
345
+ index[event] = true;
346
+ }
347
+ result.push([prevStateKey, event, target]);
348
+ }
349
+ }
350
+ }
351
+ return result;
352
+ }
353
+
354
+ // src/newFsmProcess.ts
355
+ function newFsmProcess(context, config, load) {
356
+ let started = false;
357
+ let terminated = false;
358
+ const process = new FsmProcess(config);
359
+ async function dispatch(event) {
360
+ return terminated || event !== void 0 && (!started || isStateTransitionEnabled(process, event)) ? process.dispatch(event) : false;
361
+ }
362
+ process.onStateCreate((state) => {
363
+ started = true;
364
+ state.onEnter(async () => {
365
+ const modules = await load(state.key, process.event) ?? [];
366
+ for (const module of Array.isArray(modules) ? modules : [modules]) {
367
+ const result = await module?.(context);
368
+ if (isGenerator2(result)) {
369
+ state.onExit(() => {
370
+ result.return?.(void 0);
371
+ });
372
+ (async () => {
373
+ for await (const event of result) {
374
+ if (terminated) {
375
+ break;
376
+ }
377
+ await dispatch(event);
378
+ if (terminated) {
379
+ break;
380
+ }
381
+ }
382
+ })();
383
+ } else if (typeof result === "function") {
384
+ state.onExit(result);
385
+ }
386
+ }
387
+ });
388
+ });
389
+ async function shutdown() {
390
+ await process.shutdown();
391
+ terminated = true;
392
+ }
393
+ return [dispatch, shutdown];
394
+ function isGenerator2(value) {
395
+ return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function";
396
+ }
397
+ }
398
+
399
+ // src/orchestrator/constants.ts
400
+ var KEY_DISPATCH = "fsm:dispatch";
401
+ var KEY_TERMINATE = "fsm:terminate";
402
+ var KEY_STATES = "fsm:states";
403
+ var KEY_EVENT = "fsm:event";
404
+ var KEY_REGISTER_CONFIG = "fsm:sys:registerConfig";
405
+ var KEY_REGISTER_HANDLERS = "fsm:sys:registerHandlers";
406
+ var KEY_LAUNCH_PROCESS = "fsm:sys:launch";
407
+
408
+ // src/orchestrator/types.ts
409
+ function toStageHandlers(value, accept) {
410
+ return visit(value);
411
+ function visit(val) {
412
+ if (!val) {
413
+ return [];
414
+ }
415
+ if (typeof val === "function") {
416
+ return [val];
417
+ }
418
+ if (typeof val !== "object") {
419
+ return [];
420
+ }
421
+ if (Array.isArray(val)) {
422
+ return val.flatMap(visit);
423
+ }
424
+ const modulesIndex = val;
425
+ return Object.entries(modulesIndex).flatMap(([key, value2]) => {
426
+ return accept(key, value2) ? visit(value2) : [];
427
+ });
428
+ }
429
+ }
430
+
431
+ // src/orchestrator/ProcessConfigManager.ts
432
+ var ProcessConfigManager = class {
433
+ constructor() {
434
+ this.configs = {};
435
+ this.handlers = {};
436
+ }
437
+ getProcessConfig(processName) {
438
+ return this.configs[processName] ?? { key: "Main" };
439
+ }
440
+ getHandlersLoader(processName) {
441
+ const config = this.getProcessConfig(processName);
442
+ return (stateKey) => {
443
+ const modulesKeys = [];
444
+ if (stateKey === config.key) {
445
+ modulesKeys.push("default");
446
+ }
447
+ modulesKeys.push(
448
+ stateKey,
449
+ `${stateKey}Controller`,
450
+ `${stateKey}StateController`,
451
+ `${stateKey}View`,
452
+ `${stateKey}StateView`,
453
+ `${stateKey}Trigger`,
454
+ `${stateKey}StateTrigger`,
455
+ `${stateKey}Test`,
456
+ `${stateKey}StateTest`
457
+ );
458
+ const keysSet = new Set(modulesKeys);
459
+ const processHandlers = this.handlers[processName] ?? [];
460
+ const handlers = toStageHandlers(
461
+ processHandlers,
462
+ (key) => keysSet.has(key)
463
+ );
464
+ return handlers;
465
+ };
466
+ }
467
+ registerConfig(name, config) {
468
+ this.configs[name] = config;
469
+ return () => {
470
+ delete this.configs[name];
471
+ };
472
+ }
473
+ registerHandlers(name, ...modules) {
474
+ let list = this.handlers[name];
475
+ if (!list) {
476
+ list = this.handlers[name] = [];
477
+ }
478
+ list.push(modules);
479
+ return () => {
480
+ list = list.filter((v) => v !== modules);
481
+ if (list.length === 0) {
482
+ delete this.handlers[name];
483
+ }
484
+ };
485
+ }
486
+ };
487
+
302
488
  // src/utils/printer.ts
303
489
  var KEY_PRINTER = "printer";
304
490
  function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
305
491
  let lineCounter = 0;
306
492
  const shift = () => {
307
493
  let prefix2 = "";
308
- for (let s = process.state?.parent; !!s; s = s.parent) {
494
+ for (let s = process.state?.parent; s; s = s.parent) {
309
495
  prefix2 += " ";
310
496
  }
311
497
  return prefix2;
@@ -364,101 +550,37 @@ function newProcess(config, {
364
550
  return process;
365
551
  }
366
552
 
367
- // src/utils/transitions.ts
368
- function isStateTransitionEnabled(process, event) {
369
- const transitions = getStateTransitions(process.state);
370
- let active = false;
371
- for (const [from, ev, to] of transitions) {
372
- if (ev === event) {
373
- active = true;
374
- break;
375
- }
376
- }
377
- return active;
378
- }
379
- function getStateTransitions(state) {
380
- const result = [];
381
- let index = {};
382
- if (state) {
383
- let prevStateKey = state.key;
384
- for (let parent = state?.parent; parent; parent = parent.parent) {
385
- if (!parent.descriptor) continue;
386
- result.push(
387
- ...getTransitionsFromDescriptor(parent.descriptor, prevStateKey, index)
388
- );
389
- prevStateKey = parent.key;
390
- }
391
- }
392
- return result.reverse();
393
- }
394
- function getTransitionsFromDescriptor(descriptor, prevStateKey, index = {}) {
395
- const result = [];
396
- const prevStateKeys = [prevStateKey, "*"];
397
- for (const prevKey of prevStateKeys) {
398
- const targets = descriptor.transitions[prevKey];
399
- if (targets) {
400
- for (const [event, target] of Object.entries(targets)) {
401
- if (index[event]) continue;
402
- if (target) {
403
- index[event] = true;
404
- }
405
- result.push([prevStateKey, event, target]);
406
- }
407
- }
408
- }
409
- return result;
553
+ // src/orchestrator/isGenerator.ts
554
+ function isGenerator(value) {
555
+ return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function";
410
556
  }
411
557
 
412
- // src/newFsmProcess.ts
413
- function newFsmProcess(context, config, load) {
414
- let started = false;
558
+ // src/orchestrator/startFsmProcess.ts
559
+ async function startFsmProcess(context, config, load, startEvent = "") {
415
560
  let terminated = false;
561
+ const ctx = context;
416
562
  const process = new FsmProcess(config);
563
+ const statesStack = [];
417
564
  process.onStateCreate((state) => {
418
- started = true;
419
565
  state.onEnter(async () => {
566
+ statesStack.push(state.key);
567
+ ctx[KEY_STATES] = [...statesStack];
568
+ ctx[KEY_EVENT] = process.event;
420
569
  const modules = await load(state.key, process.event) ?? [];
421
570
  for (const module of Array.isArray(modules) ? modules : [modules]) {
422
- const handler = await module(context);
423
- if (typeof handler === "function") {
424
- state.onExit(handler);
425
- }
426
- }
427
- });
428
- });
429
- async function dispatch(event) {
430
- return terminated || event !== void 0 && (!started || isStateTransitionEnabled(process, event)) ? process.dispatch(event) : false;
431
- }
432
- async function shutdown() {
433
- await process.shutdown();
434
- terminated = true;
435
- }
436
- return [dispatch, shutdown];
437
- }
438
-
439
- // src/startFsmProcess.ts
440
- function startFsmProcess(context, config, load, startEvent = "") {
441
- let started = false;
442
- let terminated = false;
443
- const process = new FsmProcess(config);
444
- process.onStateCreate((state) => {
445
- started = true;
446
- state.onEnter(async () => {
447
- const ModuleOrTriggers = await load(state.key, process.event) ?? [];
448
- for (const ModuleOrTrigger of Array.isArray(ModuleOrTriggers) ? ModuleOrTriggers : [ModuleOrTriggers]) {
449
- const result = await ModuleOrTrigger?.(context);
571
+ const result = await module?.(context);
450
572
  if (isGenerator(result)) {
573
+ let stateExited = false;
451
574
  state.onExit(() => {
575
+ stateExited = true;
452
576
  result.return?.(void 0);
453
577
  });
454
578
  (async () => {
455
579
  for await (const event of result) {
456
- if (terminated) {
580
+ if (stateExited || terminated) {
457
581
  break;
458
582
  }
459
- if (event !== void 0 && isStateTransitionEnabled(process, event)) {
460
- await process.dispatch(event);
461
- }
583
+ await dispatch(event);
462
584
  }
463
585
  })();
464
586
  } else if (typeof result === "function") {
@@ -466,16 +588,59 @@ function startFsmProcess(context, config, load, startEvent = "") {
466
588
  }
467
589
  }
468
590
  });
591
+ state.onExit(() => {
592
+ statesStack.pop();
593
+ ctx[KEY_STATES] = [...statesStack];
594
+ ctx[KEY_EVENT] = process.event;
595
+ });
469
596
  });
470
- function isGenerator(value) {
471
- return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function";
472
- }
473
- async function shutdown() {
474
- await process.shutdown();
597
+ async function terminate() {
475
598
  terminated = true;
599
+ await process.shutdown();
476
600
  }
477
- process.dispatch(startEvent);
478
- return shutdown;
601
+ async function dispatch(event) {
602
+ if (event !== void 0 && isStateTransitionEnabled(process, event)) {
603
+ await process.dispatch(event);
604
+ }
605
+ }
606
+ ctx[KEY_DISPATCH] = dispatch;
607
+ ctx[KEY_TERMINATE] = terminate;
608
+ await process.dispatch(startEvent);
609
+ return terminate;
610
+ }
611
+
612
+ // src/orchestrator/launcher.ts
613
+ async function launcher(init) {
614
+ const configsManager = new ProcessConfigManager();
615
+ async function launch(processName, context, startEvent = "start") {
616
+ const config2 = configsManager.getProcessConfig(processName);
617
+ const load = configsManager.getHandlersLoader(processName);
618
+ return startFsmProcess(context, config2, load, startEvent);
619
+ }
620
+ const rootContext = {
621
+ launch
622
+ };
623
+ const config = await init(rootContext);
624
+ for (const process of config.processes) {
625
+ if (process.config) {
626
+ configsManager.registerConfig(process.name, process.config);
627
+ }
628
+ if (process.handlers) {
629
+ configsManager.registerHandlers(process.name, ...process.handlers);
630
+ }
631
+ }
632
+ const shutdowns = [];
633
+ for (const processName of config.start ?? []) {
634
+ const parent = config.context ?? rootContext;
635
+ const context = { parent };
636
+ const shutdown = await launch(processName, context);
637
+ shutdowns.push(shutdown);
638
+ }
639
+ return async () => {
640
+ for (const shutdown of shutdowns) {
641
+ await shutdown();
642
+ }
643
+ };
479
644
  }
480
645
  export {
481
646
  EVENT_ANY,
@@ -483,7 +648,15 @@ export {
483
648
  FsmProcess,
484
649
  FsmState,
485
650
  FsmStateDescriptor,
651
+ KEY_DISPATCH,
652
+ KEY_EVENT,
653
+ KEY_LAUNCH_PROCESS,
486
654
  KEY_PRINTER,
655
+ KEY_REGISTER_CONFIG,
656
+ KEY_REGISTER_HANDLERS,
657
+ KEY_STATES,
658
+ KEY_TERMINATE,
659
+ ProcessConfigManager,
487
660
  STATE_ANY,
488
661
  STATE_FINAL,
489
662
  STATE_INITIAL,
@@ -499,6 +672,7 @@ export {
499
672
  getProcessPrinter,
500
673
  getStateTransitions,
501
674
  isStateTransitionEnabled,
675
+ launcher,
502
676
  newFsmProcess,
503
677
  newProcess,
504
678
  preparePrinter,
@@ -506,5 +680,6 @@ export {
506
680
  setProcessPrinter,
507
681
  setProcessTracer,
508
682
  setStateTracer,
509
- startFsmProcess
683
+ startFsmProcess,
684
+ toStageHandlers
510
685
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/fsm",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "description": "HFSM Implementation",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/statewalker/statewalker-fsm",
@@ -27,13 +27,10 @@
27
27
  }
28
28
  },
29
29
  "devDependencies": {
30
- "@changesets/cli": "^2.27.7",
31
- "@statewalker/eslint-config": "*",
32
- "@statewalker/typescript-config": "*",
33
- "eslint": "^9.0.0",
34
- "tsup": "^8.0.2",
35
- "typescript": "^5.4.4",
36
- "vitest": "^1.4.0"
30
+ "@biomejs/biome": "^2.2.2",
31
+ "tsup": "^8.5.0",
32
+ "typescript": "^5.9.2",
33
+ "vitest": "^3.2.4"
37
34
  },
38
35
  "repository": {
39
36
  "type": "git",
@@ -44,7 +41,9 @@
44
41
  "build": "yarn dist",
45
42
  "watch": "tsup --watch",
46
43
  "clean": "rm -rf dist",
47
- "lint": "eslint \"**/*.(js|ts)\"",
44
+ "format": "biome format . --write",
45
+ "check": "biome check .",
46
+ "fix": "yarn format && biome check . --fix --unsafe",
48
47
  "test": "vitest --run",
49
48
  "test:watch": "vitest",
50
49
  "prepublish": "yarn dist"