@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.
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,71 +306,6 @@ var FsmProcess = class extends FsmBaseClass {
299
306
  }
300
307
  };
301
308
 
302
- // src/utils/printer.ts
303
- var KEY_PRINTER = "printer";
304
- function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
305
- let lineCounter = 0;
306
- const shift = () => {
307
- let prefix2 = "";
308
- for (let s = process.state?.parent; !!s; s = s.parent) {
309
- prefix2 += " ";
310
- }
311
- return prefix2;
312
- };
313
- const getPrefix = lineNumbers ? () => `[${++lineCounter}]${shift()}` : shift;
314
- const printer = (...args) => print(prefix, getPrefix(), ...args);
315
- return printer;
316
- }
317
- function setPrinter(state, config = {}) {
318
- const printer = preparePrinter(state.process, config);
319
- state.setData(KEY_PRINTER, printer);
320
- }
321
- function setProcessPrinter(process, config = {}) {
322
- const printer = preparePrinter(process, config);
323
- process.setData(KEY_PRINTER, printer);
324
- }
325
- function getProcessPrinter(process) {
326
- return process.getData(KEY_PRINTER) || console.log;
327
- }
328
- function getPrinter(state) {
329
- return state.getData(KEY_PRINTER, true) || getProcessPrinter(state.process);
330
- }
331
-
332
- // src/utils/tracer.ts
333
- function setProcessTracer(process, print) {
334
- return process.onStateCreate((state) => {
335
- setStateTracer(state, print);
336
- });
337
- }
338
- function setStateTracer(state, print) {
339
- state.onEnter(() => {
340
- const printLine = print || getPrinter(state);
341
- printLine(`<${state?.key} event="${state.process.event}">`);
342
- });
343
- state.onExit(async () => {
344
- await Promise.resolve().then(async () => {
345
- const printLine = print || getPrinter(state);
346
- printLine(`</${state.key}> <!-- event="${state.process.event}" -->`);
347
- });
348
- });
349
- }
350
-
351
- // src/utils/process.ts
352
- function newProcess(config, {
353
- prefix,
354
- print,
355
- lineNumbers = true
356
- }) {
357
- let process = new FsmProcess(config);
358
- setProcessPrinter(process, {
359
- prefix,
360
- print,
361
- lineNumbers
362
- });
363
- setProcessTracer(process);
364
- return process;
365
- }
366
-
367
309
  // src/utils/transitions.ts
368
310
  function isStateTransitionEnabled(process, event) {
369
311
  const transitions = getStateTransitions(process.state);
@@ -414,51 +356,231 @@ function newFsmProcess(context, config, load) {
414
356
  let started = false;
415
357
  let terminated = false;
416
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
+ }
417
362
  process.onStateCreate((state) => {
418
363
  started = true;
419
364
  state.onEnter(async () => {
420
365
  const modules = await load(state.key, process.event) ?? [];
421
366
  for (const module of Array.isArray(modules) ? modules : [modules]) {
422
- const handler = await module(context);
423
- if (typeof handler === "function") {
424
- state.onExit(handler);
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);
425
385
  }
426
386
  }
427
387
  });
428
388
  });
429
- async function dispatch(event) {
430
- return terminated || event !== void 0 && (!started || isStateTransitionEnabled(process, event)) ? process.dispatch(event) : false;
431
- }
432
389
  async function shutdown() {
433
390
  await process.shutdown();
434
391
  terminated = true;
435
392
  }
436
393
  return [dispatch, shutdown];
394
+ function isGenerator2(value) {
395
+ return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function";
396
+ }
437
397
  }
438
398
 
439
- // src/startFsmProcess.ts
440
- function startFsmProcess(context, config, load, startEvent = "") {
441
- let started = false;
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
+
488
+ // src/utils/printer.ts
489
+ var KEY_PRINTER = "printer";
490
+ function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
491
+ let lineCounter = 0;
492
+ const shift = () => {
493
+ let prefix2 = "";
494
+ for (let s = process.state?.parent; s; s = s.parent) {
495
+ prefix2 += " ";
496
+ }
497
+ return prefix2;
498
+ };
499
+ const getPrefix = lineNumbers ? () => `[${++lineCounter}]${shift()}` : shift;
500
+ const printer = (...args) => print(prefix, getPrefix(), ...args);
501
+ return printer;
502
+ }
503
+ function setPrinter(state, config = {}) {
504
+ const printer = preparePrinter(state.process, config);
505
+ state.setData(KEY_PRINTER, printer);
506
+ }
507
+ function setProcessPrinter(process, config = {}) {
508
+ const printer = preparePrinter(process, config);
509
+ process.setData(KEY_PRINTER, printer);
510
+ }
511
+ function getProcessPrinter(process) {
512
+ return process.getData(KEY_PRINTER) || console.log;
513
+ }
514
+ function getPrinter(state) {
515
+ return state.getData(KEY_PRINTER, true) || getProcessPrinter(state.process);
516
+ }
517
+
518
+ // src/utils/tracer.ts
519
+ function setProcessTracer(process, print) {
520
+ return process.onStateCreate((state) => {
521
+ setStateTracer(state, print);
522
+ });
523
+ }
524
+ function setStateTracer(state, print) {
525
+ state.onEnter(() => {
526
+ const printLine = print || getPrinter(state);
527
+ printLine(`<${state?.key} event="${state.process.event}">`);
528
+ });
529
+ state.onExit(async () => {
530
+ await Promise.resolve().then(async () => {
531
+ const printLine = print || getPrinter(state);
532
+ printLine(`</${state.key}> <!-- event="${state.process.event}" -->`);
533
+ });
534
+ });
535
+ }
536
+
537
+ // src/utils/process.ts
538
+ function newProcess(config, {
539
+ prefix,
540
+ print,
541
+ lineNumbers = true
542
+ }) {
543
+ let process = new FsmProcess(config);
544
+ setProcessPrinter(process, {
545
+ prefix,
546
+ print,
547
+ lineNumbers
548
+ });
549
+ setProcessTracer(process);
550
+ return process;
551
+ }
552
+
553
+ // src/orchestrator/isGenerator.ts
554
+ function isGenerator(value) {
555
+ return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function";
556
+ }
557
+
558
+ // src/orchestrator/startFsmProcess.ts
559
+ async function startFsmProcess(context, config, load, startEvent = "") {
442
560
  let terminated = false;
561
+ const ctx = context;
443
562
  const process = new FsmProcess(config);
563
+ const statesStack = [];
444
564
  process.onStateCreate((state) => {
445
- started = true;
446
565
  state.onEnter(async () => {
566
+ statesStack.push(state.key);
567
+ ctx[KEY_STATES] = [...statesStack];
568
+ ctx[KEY_EVENT] = process.event;
447
569
  const modules = await load(state.key, process.event) ?? [];
448
570
  for (const module of Array.isArray(modules) ? modules : [modules]) {
449
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.29.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"