@statewalker/fsm 0.22.1 → 0.23.1

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
@@ -6,7 +6,6 @@ declare class FsmBaseClass {
6
6
  getData<T>(key: string): T | undefined;
7
7
  _addHandler(type: string, handler: Function, direct?: boolean): () => void;
8
8
  _removeHandler(type: string, handler: Function): void;
9
- _runHandlerSync(type: string, ...args: unknown[]): any[];
10
9
  _runHandler(type: string, ...args: unknown[]): Promise<void>;
11
10
  _handleError(error: Error | unknown): Promise<void>;
12
11
  }
@@ -22,7 +21,7 @@ type FsmStateConfig = {
22
21
  key: FsmStateKey;
23
22
  transitions?: [from: FsmStateKey, event: FsmEventKey, to: FsmStateKey][];
24
23
  states?: FsmStateConfig[];
25
- } & Record<string, any>;
24
+ } & Record<string, unknown>;
26
25
 
27
26
  declare class FsmStateDescriptor {
28
27
  transitions: Record<string, Record<string, string>>;
@@ -31,14 +30,13 @@ declare class FsmStateDescriptor {
31
30
  getTargetStateKey(stateKey: string, eventKey: string): string;
32
31
  }
33
32
 
34
- type FsmStateDump = Record<string, any> & {
33
+ type FsmStateDump = Record<string, unknown> & {
35
34
  key: string;
36
35
  data: Record<string, unknown>;
37
36
  };
38
37
  type FsmStateHandler = (state: FsmState, ...args: unknown[]) => void | Promise<void>;
39
- type FsmStateSyncHandler = (state: FsmState, ...args: unknown[]) => void;
40
38
  type FsmStateDumpHandler = (state: FsmState, dump: FsmStateDump) => void | Promise<void>;
41
- type FsmStateErrorHandler = (state: FsmState, error: any) => void | Promise<void>;
39
+ type FsmStateErrorHandler = (state: FsmState, error: unknown) => void | Promise<void>;
42
40
  declare class FsmState extends FsmBaseClass {
43
41
  process: FsmProcess;
44
42
  key: string;
@@ -63,8 +61,8 @@ declare const STATUS_LAST = 8;
63
61
  declare const STATUS_FINISHED = 16;
64
62
  declare const STATUS_ENTER: number;
65
63
  declare const STATUS_EXIT: number;
66
- type FsmProcessHandler = (process: FsmProcess, ...args: any[]) => void | Promise<void>;
67
- type FsmProcessDump = Record<string, any> & {
64
+ type FsmProcessHandler = (process: FsmProcess, ...args: unknown[]) => void | Promise<void>;
65
+ type FsmProcessDump = Record<string, unknown> & {
68
66
  status: number;
69
67
  event?: string;
70
68
  stack: FsmStateDump[];
@@ -81,8 +79,8 @@ declare class FsmProcess extends FsmBaseClass {
81
79
  dispatch(event: string, mask?: number): Promise<boolean>;
82
80
  dump(...args: unknown[]): Promise<FsmProcessDump>;
83
81
  restore(dump: FsmProcessDump, ...args: unknown[]): Promise<this>;
84
- onStateCreate(handler: FsmStateSyncHandler): () => void;
85
- onStateError(handler: (state: FsmState, error: any) => void | Promise<void>): () => void;
82
+ onStateCreate(handler: FsmStateHandler): () => void;
83
+ onStateError(handler: (state: FsmState, error: unknown) => void | Promise<void>): () => void;
86
84
  _handleStateError(state: FsmState, error: Error | unknown): Promise<void>;
87
85
  _newState(parent: FsmState | undefined, key: string, descriptor: FsmStateDescriptor | undefined): FsmState;
88
86
  _getSubstate(parent: FsmState | undefined, prevStateKey: string | undefined): FsmState | undefined;
@@ -90,4 +88,29 @@ declare class FsmProcess extends FsmBaseClass {
90
88
  _update(): boolean;
91
89
  }
92
90
 
93
- 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 FsmStateSyncHandler, STATE_ANY, STATE_FINAL, STATE_INITIAL, STATUS_ENTER, STATUS_EXIT, STATUS_FINISHED, STATUS_FIRST, STATUS_LAST, STATUS_LEAF, STATUS_NEXT, STATUS_NONE };
91
+ type Printer = (...args: unknown[]) => void;
92
+ type PrinterConfig = {
93
+ prefix?: string;
94
+ print?: (...args: unknown[]) => void;
95
+ lineNumbers?: boolean;
96
+ };
97
+ declare const KEY_PRINTER = "printer";
98
+ declare function preparePrinter(process: FsmProcess, { prefix, print, lineNumbers }: PrinterConfig): Printer;
99
+ declare function setPrinter(state: FsmState, config?: PrinterConfig): void;
100
+ declare function setProcessPrinter(process: FsmProcess, config?: PrinterConfig): void;
101
+ declare function getProcessPrinter(process: FsmProcess): Printer;
102
+ declare function getPrinter(state: FsmState): Printer;
103
+
104
+ declare function newProcess(config: FsmStateConfig, { prefix, print, lineNumbers, }: {
105
+ prefix?: string;
106
+ print?: (...args: any[]) => void;
107
+ lineNumbers?: boolean;
108
+ }): FsmProcess;
109
+
110
+ declare function setProcessTracer(process: FsmProcess, print?: Printer): () => void;
111
+ declare function setStateTracer(state: FsmState, print?: Printer): void;
112
+
113
+ declare function isStateTransitionEnabled(process: FsmProcess, event: string): boolean;
114
+ declare function getStateTransitions(state?: FsmState): [from: string, event: string, to: string][];
115
+
116
+ 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 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, newProcess, preparePrinter, setPrinter, setProcessPrinter, setProcessTracer, setStateTracer };
package/dist/index.js CHANGED
@@ -29,12 +29,9 @@ var FsmBaseClass = class {
29
29
  delete this.handlers[type];
30
30
  }
31
31
  }
32
- _runHandlerSync(type, ...args) {
33
- const list = this.handlers[type] || [];
34
- return list.map((handler) => handler(...args));
35
- }
36
32
  async _runHandler(type, ...args) {
37
- const promises = this._runHandlerSync(type, ...args);
33
+ const list = this.handlers[type] || [];
34
+ const promises = list.map((handler) => handler(...args));
38
35
  for (const promise of promises) {
39
36
  try {
40
37
  await promise;
@@ -49,9 +46,11 @@ var FsmBaseClass = class {
49
46
  };
50
47
  function bindMethods(obj, ...methods) {
51
48
  const o = obj;
52
- methods.forEach((methodName) => {
53
- o[methodName] = o[methodName].bind(o);
54
- });
49
+ for (const methodName of methods) {
50
+ const method = o[methodName];
51
+ if (typeof method !== "function") continue;
52
+ o[methodName] = method.bind(o);
53
+ }
55
54
  return obj;
56
55
  }
57
56
 
@@ -235,7 +234,7 @@ var FsmProcess = class extends FsmBaseClass {
235
234
  }
236
235
  _newState(parent, key, descriptor) {
237
236
  const state = new FsmState(this, parent, key, descriptor);
238
- this._runHandlerSync("onStateCreate", state);
237
+ this._runHandler("onStateCreate", state);
239
238
  return state;
240
239
  }
241
240
  _getSubstate(parent, prevStateKey) {
@@ -272,12 +271,121 @@ var FsmProcess = class extends FsmBaseClass {
272
271
  return !(this.status & STATUS_FINISHED);
273
272
  }
274
273
  };
274
+
275
+ // src/utils/printer.ts
276
+ var KEY_PRINTER = "printer";
277
+ function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
278
+ let lineCounter = 0;
279
+ const shift = () => {
280
+ let prefix2 = "";
281
+ for (let s = process.state?.parent; !!s; s = s.parent) {
282
+ prefix2 += " ";
283
+ }
284
+ return prefix2;
285
+ };
286
+ const getPrefix = lineNumbers ? () => `[${++lineCounter}]${shift()}` : shift;
287
+ const printer = (...args) => print(prefix, getPrefix(), ...args);
288
+ return printer;
289
+ }
290
+ function setPrinter(state, config = {}) {
291
+ const printer = preparePrinter(state.process, config);
292
+ state.setData(KEY_PRINTER, printer);
293
+ }
294
+ function setProcessPrinter(process, config = {}) {
295
+ const printer = preparePrinter(process, config);
296
+ process.setData(KEY_PRINTER, printer);
297
+ }
298
+ function getProcessPrinter(process) {
299
+ return process.getData(KEY_PRINTER) || console.log;
300
+ }
301
+ function getPrinter(state) {
302
+ return state.getData(KEY_PRINTER, true) || getProcessPrinter(state.process);
303
+ }
304
+
305
+ // src/utils/tracer.ts
306
+ function setProcessTracer(process, print) {
307
+ return process.onStateCreate((state) => {
308
+ setStateTracer(state, print);
309
+ });
310
+ }
311
+ function setStateTracer(state, print) {
312
+ state.onEnter(() => {
313
+ const printLine = print || getPrinter(state);
314
+ printLine(`<${state?.key} event="${state.process.event}">`);
315
+ });
316
+ state.onExit(() => {
317
+ const printLine = print || getPrinter(state);
318
+ printLine(`</${state.key}> <!-- event="${state.process.event}" -->`);
319
+ });
320
+ }
321
+
322
+ // src/utils/process.ts
323
+ function newProcess(config, {
324
+ prefix,
325
+ print,
326
+ lineNumbers = true
327
+ }) {
328
+ let process = new FsmProcess(config);
329
+ setProcessPrinter(process, {
330
+ prefix,
331
+ print,
332
+ lineNumbers
333
+ });
334
+ setProcessTracer(process);
335
+ return process;
336
+ }
337
+
338
+ // src/utils/transitions.ts
339
+ function isStateTransitionEnabled(process, event) {
340
+ const transitions = getStateTransitions(process.state);
341
+ let active = false;
342
+ for (const [from, ev, to] of transitions) {
343
+ if (ev === event) {
344
+ active = true;
345
+ break;
346
+ }
347
+ }
348
+ return active;
349
+ }
350
+ function getStateTransitions(state) {
351
+ const result = [];
352
+ let index = {};
353
+ if (state) {
354
+ let prevStateKey = state.key;
355
+ for (let parent = state?.parent; parent; parent = parent.parent) {
356
+ if (!parent.descriptor) continue;
357
+ result.push(
358
+ ...getTransitionsFromDescriptor(parent.descriptor, prevStateKey, index)
359
+ );
360
+ prevStateKey = parent.key;
361
+ }
362
+ }
363
+ return result.reverse();
364
+ }
365
+ function getTransitionsFromDescriptor(descriptor, prevStateKey, index = {}) {
366
+ const result = [];
367
+ const prevStateKeys = [prevStateKey, "*"];
368
+ for (const prevKey of prevStateKeys) {
369
+ const targets = descriptor.transitions[prevKey];
370
+ if (targets) {
371
+ for (const [event, target] of Object.entries(targets)) {
372
+ if (index[event]) continue;
373
+ if (target) {
374
+ index[event] = true;
375
+ }
376
+ result.push([prevStateKey, event, target]);
377
+ }
378
+ }
379
+ }
380
+ return result;
381
+ }
275
382
  export {
276
383
  EVENT_ANY,
277
384
  EVENT_EMPTY,
278
385
  FsmProcess,
279
386
  FsmState,
280
387
  FsmStateDescriptor,
388
+ KEY_PRINTER,
281
389
  STATE_ANY,
282
390
  STATE_FINAL,
283
391
  STATE_INITIAL,
@@ -288,5 +396,15 @@ export {
288
396
  STATUS_LAST,
289
397
  STATUS_LEAF,
290
398
  STATUS_NEXT,
291
- STATUS_NONE
399
+ STATUS_NONE,
400
+ getPrinter,
401
+ getProcessPrinter,
402
+ getStateTransitions,
403
+ isStateTransitionEnabled,
404
+ newProcess,
405
+ preparePrinter,
406
+ setPrinter,
407
+ setProcessPrinter,
408
+ setProcessTracer,
409
+ setStateTracer
292
410
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/fsm",
3
- "version": "0.22.1",
3
+ "version": "0.23.1",
4
4
  "description": "HFSM Implementation",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/statewalker/statewalker-fsm",
@@ -14,14 +14,17 @@
14
14
  "dist",
15
15
  "src"
16
16
  ],
17
+ "source": "./src/index.ts",
17
18
  "module": "./dist/index.js",
18
19
  "main": "./dist/index.js",
19
20
  "types": "./dist/index.d.ts",
20
21
  "exports": {
21
- ".": "./dist/index.js",
22
- "./utils": "./dist/utils/index.js",
23
- "./*.ts": "./src/*.ts",
24
- "./utils/*.ts": "./src/utils/*.ts"
22
+ ".": {
23
+ "source": "./src/index.ts",
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "default": "./dist/index.js"
27
+ }
25
28
  },
26
29
  "devDependencies": {
27
30
  "@changesets/cli": "^2.27.7",
@@ -43,7 +46,8 @@
43
46
  "clean": "rm -rf dist",
44
47
  "lint": "eslint \"**/*.(js|ts)\"",
45
48
  "test": "vitest --run",
46
- "test:watch": "vitest"
49
+ "test:watch": "vitest",
50
+ "prepublish": "yarn dist"
47
51
  },
48
52
  "sideEffects": false,
49
53
  "publishConfig": {
@@ -30,12 +30,10 @@ export class FsmBaseClass {
30
30
  delete this.handlers[type];
31
31
  }
32
32
  }
33
- _runHandlerSync(type: string, ...args: unknown[]) {
34
- const list = this.handlers[type] || [];
35
- return list.map((handler) => handler(...args));
36
- }
33
+
37
34
  async _runHandler(type: string, ...args: unknown[]) {
38
- const promises = this._runHandlerSync(type, ...args);
35
+ const list = this.handlers[type] || [];
36
+ const promises = list.map((handler) => handler(...args));
39
37
  for (const promise of promises) {
40
38
  try {
41
39
  await promise;
@@ -49,10 +47,12 @@ export class FsmBaseClass {
49
47
  }
50
48
  }
51
49
 
52
- export function bindMethods<T>(obj: T, ...methods: string[]) {
53
- const o = obj as any;
54
- methods.forEach((methodName) => {
55
- o[methodName] = (o[methodName] as Function).bind(o);
56
- });
50
+ export function bindMethods<T>(obj: T, ...methods: (string | symbol)[]) {
51
+ const o = obj as Record<string | symbol, unknown>;
52
+ for (const methodName of methods) {
53
+ const method = o[methodName];
54
+ if (typeof method !== "function") continue;
55
+ o[methodName] = method.bind(o);
56
+ }
57
57
  return obj;
58
58
  }
package/src/FsmProcess.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FsmBaseClass, bindMethods } from "./FsmBaseClass.ts";
2
- import { FsmState, FsmStateDump, FsmStateSyncHandler } from "./FsmState.ts";
2
+ import { FsmState, FsmStateDump, FsmStateHandler } from "./FsmState.ts";
3
3
  import {
4
4
  EVENT_EMPTY,
5
5
  FsmStateConfig,
@@ -21,10 +21,10 @@ export const STATUS_EXIT = STATUS_LEAF | STATUS_LAST;
21
21
 
22
22
  export type FsmProcessHandler = (
23
23
  process: FsmProcess,
24
- ...args: any[]
24
+ ...args: unknown[]
25
25
  ) => void | Promise<void>;
26
26
 
27
- export type FsmProcessDump = Record<string, any> & {
27
+ export type FsmProcessDump = Record<string, unknown> & {
28
28
  status: number;
29
29
  event?: string;
30
30
  stack: FsmStateDump[];
@@ -32,7 +32,7 @@ export type FsmProcessDump = Record<string, any> & {
32
32
 
33
33
  export type FsmProcessDumpHandler = (
34
34
  process: FsmProcess,
35
- dump: FsmProcessDump
35
+ dump: FsmProcessDump,
36
36
  ) => void | Promise<void>;
37
37
 
38
38
  export class FsmProcess extends FsmBaseClass {
@@ -83,7 +83,7 @@ export class FsmProcess extends FsmBaseClass {
83
83
  };
84
84
  const dumpStates = async (
85
85
  state: FsmState | undefined,
86
- stack: FsmStateDump[] = []
86
+ stack: FsmStateDump[] = [],
87
87
  ) => {
88
88
  if (!state) return stack;
89
89
  state.parent && (await dumpStates(state.parent, stack));
@@ -111,17 +111,19 @@ export class FsmProcess extends FsmBaseClass {
111
111
  "restore",
112
112
  this.state,
113
113
  stateDump.data,
114
- ...args
114
+ ...args,
115
115
  );
116
116
  }
117
117
  return this;
118
118
  }
119
119
 
120
- onStateCreate(handler: FsmStateSyncHandler) {
120
+ onStateCreate(handler: FsmStateHandler) {
121
121
  return this._addHandler("onStateCreate", handler, true);
122
122
  }
123
123
 
124
- onStateError(handler: (state: FsmState, error: any) => void | Promise<void>) {
124
+ onStateError(
125
+ handler: (state: FsmState, error: unknown) => void | Promise<void>,
126
+ ) {
125
127
  return this._addHandler("onStateError", handler);
126
128
  }
127
129
 
@@ -133,10 +135,10 @@ export class FsmProcess extends FsmBaseClass {
133
135
  _newState(
134
136
  parent: FsmState | undefined,
135
137
  key: string,
136
- descriptor: FsmStateDescriptor | undefined
138
+ descriptor: FsmStateDescriptor | undefined,
137
139
  ) {
138
140
  const state = new FsmState(this, parent, key, descriptor);
139
- this._runHandlerSync("onStateCreate", state);
141
+ this._runHandler("onStateCreate", state);
140
142
  return state;
141
143
  }
142
144
 
@@ -145,7 +147,7 @@ export class FsmProcess extends FsmBaseClass {
145
147
  const toState =
146
148
  parent.descriptor?.getTargetStateKey(
147
149
  prevStateKey || STATE_INITIAL,
148
- this.event || EVENT_EMPTY
150
+ this.event || EVENT_EMPTY,
149
151
  ) || STATE_FINAL;
150
152
  if (!toState) return;
151
153
  return this._newSubstate(parent, toState);
package/src/FsmState.ts CHANGED
@@ -2,7 +2,7 @@ import { FsmBaseClass, bindMethods } from "./FsmBaseClass.ts";
2
2
  import { FsmProcess } from "./FsmProcess.ts";
3
3
  import { FsmStateDescriptor } from "./FsmStateDescriptor.ts";
4
4
 
5
- export type FsmStateDump = Record<string, any> & {
5
+ export type FsmStateDump = Record<string, unknown> & {
6
6
  key: string;
7
7
  data: Record<string, unknown>;
8
8
  };
@@ -11,8 +11,6 @@ export type FsmStateHandler = (
11
11
  ...args: unknown[]
12
12
  ) => void | Promise<void>;
13
13
 
14
- export type FsmStateSyncHandler = (state: FsmState, ...args: unknown[]) => void;
15
-
16
14
  export type FsmStateDumpHandler = (
17
15
  state: FsmState,
18
16
  dump: FsmStateDump
@@ -20,7 +18,7 @@ export type FsmStateDumpHandler = (
20
18
 
21
19
  export type FsmStateErrorHandler = (
22
20
  state: FsmState,
23
- error: any
21
+ error: unknown
24
22
  ) => void | Promise<void>;
25
23
 
26
24
  export class FsmState extends FsmBaseClass {
@@ -10,4 +10,4 @@ export type FsmStateConfig = {
10
10
  key: FsmStateKey;
11
11
  transitions?: [from: FsmStateKey, event: FsmEventKey, to: FsmStateKey][];
12
12
  states?: FsmStateConfig[];
13
- } & Record<string, any>;
13
+ } & Record<string, unknown>;
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ 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 "./utils/index.ts";
@@ -1,3 +1,4 @@
1
- export * from "./handlers.ts";
2
1
  export * from "./printer.ts";
2
+ export * from "./process.ts";
3
3
  export * from "./tracer.ts";
4
+ export * from "./transitions.ts";
@@ -0,0 +1,155 @@
1
+ import { FsmState } from "../FsmState";
2
+
3
+ export function newModuleLoader<T>(baseUrl: string) {
4
+ const cache: Record<string, Promise<T>> = {};
5
+ return async (path: string) => {
6
+ return (cache[path] =
7
+ cache[path] ||
8
+ (async () => {
9
+ const suffixes = ["/", "", ".js", "/index.js"];
10
+ for (const suffix of suffixes) {
11
+ const url = new URL(path + suffix, baseUrl).pathname;
12
+ try {
13
+ return (await import(url)) as T;
14
+ } catch (error) {
15
+ continue;
16
+ }
17
+ }
18
+ })());
19
+ };
20
+ }
21
+
22
+ export async function newHandlerLoader(
23
+ baseUrl: string,
24
+ loader: (
25
+ path: string,
26
+ ) => Promise<undefined | FsmStateHandler> = newModuleLoader(baseUrl),
27
+ ): Promise<(state: FsmState) => Promise<FsmStateHandler[]>> {
28
+ return async (state: FsmState) => {
29
+ const stack: string[] = [];
30
+ for (let s: FsmState | undefined = state; s; s = s.parent) {
31
+ stack.unshift(s.key);
32
+ }
33
+
34
+ const handlers: FsmStateHandler[] = [];
35
+ let topName: undefined | string;
36
+ while (stack.length > 0) {
37
+ const lastSegment = stack.pop();
38
+ if (!topName) {
39
+ topName = lastSegment;
40
+ if (topName === undefined) break;
41
+ }
42
+ const path = [...stack, topName].join("/");
43
+ const handler = await loader(path);
44
+ handler && handlers.push(handler);
45
+ }
46
+
47
+ return handlers;
48
+ };
49
+ }
50
+
51
+ /*
52
+ * StateContext:
53
+ - Each context is a function or a class
54
+ - It recieves the stack of state objects
55
+ - The returned value is used to pass to the handler function
56
+ * Trigger function
57
+ - recieves the context instance
58
+ - notifies about changes using the given callback
59
+ * Handler
60
+ - recieves the context
61
+ - starts activities on call
62
+ - optionally returns a cleanup function
63
+ - could return a promise with optional cleanup function; the state is not
64
+ activated until all handlers return the control
65
+ */
66
+ export type FsmStateHandler<
67
+ FsmStateContext extends Record<string, unknown> = Record<string, unknown>,
68
+ FsmStateStore = Record<string, unknown>,
69
+ > = {
70
+ context?: (...stack: FsmStateStore[]) => FsmStateContext;
71
+ trigger?: (
72
+ context: FsmStateContext,
73
+ listener: (event: string) => void,
74
+ ) => void | (() => void);
75
+ default?: (context: FsmStateContext) => void | (() => void);
76
+ handler?: (context: FsmStateContext) => void | (() => void);
77
+ };
78
+
79
+ export function newStateHandlers<
80
+ FsmStateContext extends Record<string, unknown> = Record<string, unknown>,
81
+ FsmStateStore = Record<string, unknown>,
82
+ >(
83
+ loader: (state: FsmState) => Promise<FsmStateHandler[]>,
84
+ newContext: (state: FsmState, ...stack: FsmStateStore[]) => FsmStateStore = (
85
+ state,
86
+ ) =>
87
+ ({
88
+ state: state.key,
89
+ event: state.process.event,
90
+ }) as unknown as FsmStateStore,
91
+ ) {
92
+ return (state: FsmState) => {
93
+ const stack: FsmStateStore[] = [];
94
+ for (let s: FsmState | undefined = state.parent; !!s; s = s.parent) {
95
+ const store = s.getData<FsmStateStore>("context");
96
+ store && stack.push(store);
97
+ }
98
+ const store = newContext(state, ...stack);
99
+ stack.unshift(store);
100
+ state.setData("context", store);
101
+
102
+ // const process = state.process;
103
+ // let processContext = process.getData<FsmProcessContext>("context");
104
+ // if (!processContext) {
105
+ // processContext = newContext(process);
106
+ // process.setData("context", processContext);
107
+ // }
108
+ let registrations: (void | (() => void))[] = [];
109
+ state.onExit(() => {
110
+ for (const registration of registrations) {
111
+ registration?.();
112
+ }
113
+ });
114
+ state.onEnter(async () => {
115
+ const modules = await loader(state);
116
+ if (!modules?.length) return;
117
+ for (const module of modules) {
118
+ // Update state context
119
+ // TODO: use a stack of context objects
120
+ const ctx = isClass(module.context)
121
+ ? new module.context(...stack)
122
+ : isFunction(module.context)
123
+ ? module.context(...stack) || store
124
+ : store;
125
+ const stateContext = ctx as FsmStateContext;
126
+ // Set trigger
127
+ let notifyTrigger = (event: string) => {
128
+ if (event) {
129
+ // TODO: Check that the event is available in this state;
130
+ state.process.dispatch(event);
131
+ }
132
+ };
133
+ const removeTrigger = module?.trigger?.(stateContext, (event) =>
134
+ notifyTrigger(event),
135
+ );
136
+ registrations.push(() => (notifyTrigger = () => {}));
137
+ removeTrigger && registrations.push(removeTrigger);
138
+
139
+ const handler = module.default || module.handler;
140
+ registrations.push(handler?.(stateContext));
141
+ }
142
+ function isFunction<T extends Array<unknown>, R = unknown>(
143
+ value: unknown,
144
+ ): value is (...args: T) => R {
145
+ return typeof value === "function";
146
+ }
147
+ function isClass<T extends Array<unknown>, R = unknown>(
148
+ value: unknown,
149
+ ): value is new (...args: T) => R {
150
+ if (!isFunction(value)) return false;
151
+ return value.prototype.constructor.toString().match(/^class/);
152
+ }
153
+ });
154
+ };
155
+ }
@@ -1,9 +1,9 @@
1
1
  import type { FsmProcess } from "../FsmProcess.ts";
2
2
  import type { FsmState } from "../FsmState.ts";
3
- export type Printer = (...args: any[]) => void;
3
+ export type Printer = (...args: unknown[]) => void;
4
4
  export type PrinterConfig = {
5
5
  prefix?: string;
6
- print?: (...args: any[]) => void;
6
+ print?: (...args: unknown[]) => void;
7
7
  lineNumbers?: boolean;
8
8
  };
9
9
 
@@ -11,7 +11,7 @@ export const KEY_PRINTER = "printer";
11
11
 
12
12
  export function preparePrinter(
13
13
  process: FsmProcess,
14
- { prefix = "", print = console.log, lineNumbers = false }: PrinterConfig
14
+ { prefix = "", print = console.log, lineNumbers = false }: PrinterConfig,
15
15
  ): Printer {
16
16
  let lineCounter = 0;
17
17
  const shift = () => {
@@ -22,7 +22,7 @@ export function preparePrinter(
22
22
  return prefix;
23
23
  };
24
24
  const getPrefix = lineNumbers ? () => `[${++lineCounter}]${shift()}` : shift;
25
- const printer = (...args: string[]) => print(prefix, getPrefix(), ...args);
25
+ const printer = (...args: unknown[]) => print(prefix, getPrefix(), ...args);
26
26
  return printer;
27
27
  }
28
28
 
@@ -33,7 +33,7 @@ export function setPrinter(state: FsmState, config: PrinterConfig = {}) {
33
33
 
34
34
  export function setProcessPrinter(
35
35
  process: FsmProcess,
36
- config: PrinterConfig = {}
36
+ config: PrinterConfig = {},
37
37
  ) {
38
38
  const printer = preparePrinter(process, config);
39
39
  process.setData(KEY_PRINTER, printer);
@@ -0,0 +1,26 @@
1
+ import { FsmProcess } from "../FsmProcess.ts";
2
+ import type { FsmStateConfig } from "../FsmStateConfig.ts";
3
+ import { setProcessPrinter } from "./printer.ts";
4
+ import { setProcessTracer } from "./tracer.ts";
5
+
6
+ export function newProcess(
7
+ config: FsmStateConfig,
8
+ {
9
+ prefix,
10
+ print,
11
+ lineNumbers = true,
12
+ }: {
13
+ prefix?: string;
14
+ print?: (...args: any[]) => void;
15
+ lineNumbers?: boolean;
16
+ },
17
+ ): FsmProcess {
18
+ let process = new FsmProcess(config);
19
+ setProcessPrinter(process, {
20
+ prefix,
21
+ print,
22
+ lineNumbers,
23
+ });
24
+ setProcessTracer(process);
25
+ return process;
26
+ }
@@ -0,0 +1,56 @@
1
+ import { FsmProcess } from "../FsmProcess.ts";
2
+ import type { FsmState } from "../FsmState.ts";
3
+ import type { FsmStateDescriptor } from "../FsmStateDescriptor.ts";
4
+
5
+ export function isStateTransitionEnabled(process: FsmProcess, event: string) {
6
+ const transitions = getStateTransitions(process.state);
7
+ let active = false;
8
+ for (const [from, ev, to] of transitions) {
9
+ if (ev === event) {
10
+ active = true;
11
+ break;
12
+ }
13
+ }
14
+ return active;
15
+ }
16
+
17
+ export function getStateTransitions(
18
+ state?: FsmState,
19
+ ): [from: string, event: string, to: string][] {
20
+ const result: [from: string, event: string, to: string][] = [];
21
+ let index = {};
22
+ if (state) {
23
+ let prevStateKey = state.key;
24
+ for (let parent = state?.parent; parent; parent = parent.parent) {
25
+ if (!parent.descriptor) continue;
26
+ result.push(
27
+ ...getTransitionsFromDescriptor(parent.descriptor, prevStateKey, index),
28
+ );
29
+ prevStateKey = parent.key;
30
+ }
31
+ }
32
+ return result.reverse();
33
+ }
34
+
35
+ function getTransitionsFromDescriptor(
36
+ descriptor: FsmStateDescriptor,
37
+ prevStateKey: string,
38
+ index: Record<string, boolean> = {},
39
+ ) {
40
+ const result: [from: string, event: string, to: string][] = [];
41
+ const prevStateKeys = [prevStateKey, "*"];
42
+ for (const prevKey of prevStateKeys) {
43
+ const targets: undefined | Record<string, string> =
44
+ descriptor.transitions[prevKey];
45
+ if (targets) {
46
+ for (const [event, target] of Object.entries(targets)) {
47
+ if (index[event]) continue;
48
+ if (target) {
49
+ index[event] = true;
50
+ }
51
+ result.push([prevStateKey, event, target]);
52
+ }
53
+ }
54
+ }
55
+ return result;
56
+ }
@@ -1,23 +0,0 @@
1
- import { FsmState, FsmStateHandler, FsmProcess } from '../index.js';
2
-
3
- declare const KEY_HANDLERS = "handlers";
4
- declare function callStateHandlers(state: FsmState): void;
5
- declare function addSubstateHandlers(state: FsmState, handlers: Record<string, FsmStateHandler>): void;
6
-
7
- type Printer = (...args: any[]) => void;
8
- type PrinterConfig = {
9
- prefix?: string;
10
- print?: (...args: any[]) => void;
11
- lineNumbers?: boolean;
12
- };
13
- declare const KEY_PRINTER = "printer";
14
- declare function preparePrinter(process: FsmProcess, { prefix, print, lineNumbers }: PrinterConfig): Printer;
15
- declare function setPrinter(state: FsmState, config?: PrinterConfig): void;
16
- declare function setProcessPrinter(process: FsmProcess, config?: PrinterConfig): void;
17
- declare function getProcessPrinter(process: FsmProcess): Printer;
18
- declare function getPrinter(state: FsmState): Printer;
19
-
20
- declare function setProcessTracer(process: FsmProcess, print?: Printer): () => void;
21
- declare function setStateTracer(state: FsmState, print?: Printer): void;
22
-
23
- export { KEY_HANDLERS, KEY_PRINTER, type Printer, type PrinterConfig, addSubstateHandlers, callStateHandlers, getPrinter, getProcessPrinter, preparePrinter, setPrinter, setProcessPrinter, setProcessTracer, setStateTracer };
@@ -1,78 +0,0 @@
1
- // src/utils/handlers.ts
2
- var KEY_HANDLERS = "handlers";
3
- function callStateHandlers(state) {
4
- const key = state.key;
5
- for (let parent = state.parent; !!parent; parent = parent?.parent) {
6
- const handlers = parent.getData(KEY_HANDLERS)?.[key];
7
- handlers?.forEach((handler) => handler(state));
8
- }
9
- }
10
- function addSubstateHandlers(state, handlers) {
11
- const oldIndex = state.getData(KEY_HANDLERS);
12
- const index = oldIndex ? { ...oldIndex } : {};
13
- for (const [key, handler] of Object.entries(handlers)) {
14
- const list = index[key] = index[key] || [];
15
- list.push(handler);
16
- }
17
- state.setData(KEY_HANDLERS, index);
18
- }
19
-
20
- // src/utils/printer.ts
21
- var KEY_PRINTER = "printer";
22
- function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
23
- let lineCounter = 0;
24
- const shift = () => {
25
- let prefix2 = "";
26
- for (let s = process.state?.parent; !!s; s = s.parent) {
27
- prefix2 += " ";
28
- }
29
- return prefix2;
30
- };
31
- const getPrefix = lineNumbers ? () => `[${++lineCounter}]${shift()}` : shift;
32
- const printer = (...args) => print(prefix, getPrefix(), ...args);
33
- return printer;
34
- }
35
- function setPrinter(state, config = {}) {
36
- const printer = preparePrinter(state.process, config);
37
- state.setData(KEY_PRINTER, printer);
38
- }
39
- function setProcessPrinter(process, config = {}) {
40
- const printer = preparePrinter(process, config);
41
- process.setData(KEY_PRINTER, printer);
42
- }
43
- function getProcessPrinter(process) {
44
- return process.getData(KEY_PRINTER) || console.log;
45
- }
46
- function getPrinter(state) {
47
- return state.getData(KEY_PRINTER, true) || getProcessPrinter(state.process);
48
- }
49
-
50
- // src/utils/tracer.ts
51
- function setProcessTracer(process, print) {
52
- return process.onStateCreate((state) => {
53
- setStateTracer(state, print);
54
- });
55
- }
56
- function setStateTracer(state, print) {
57
- state.onEnter(() => {
58
- const printLine = print || getPrinter(state);
59
- printLine(`<${state?.key} event="${state.process.event}">`);
60
- });
61
- state.onExit(() => {
62
- const printLine = print || getPrinter(state);
63
- printLine(`</${state.key}> <!-- event="${state.process.event}" -->`);
64
- });
65
- }
66
- export {
67
- KEY_HANDLERS,
68
- KEY_PRINTER,
69
- addSubstateHandlers,
70
- callStateHandlers,
71
- getPrinter,
72
- getProcessPrinter,
73
- preparePrinter,
74
- setPrinter,
75
- setProcessPrinter,
76
- setProcessTracer,
77
- setStateTracer
78
- };