@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 +170 -13
- package/dist/index.js +267 -92
- package/package.json +8 -9
- package/src/FsmBaseClass.ts +13 -6
- package/src/FsmProcess.ts +7 -3
- package/src/FsmState.ts +7 -7
- package/src/FsmStateDescriptor.ts +8 -5
- package/src/index.ts +2 -2
- package/src/newFsmProcess.ts +46 -17
- package/src/orchestrator/ProcessConfigManager.ts +70 -0
- package/src/orchestrator/constants.ts +130 -0
- package/src/orchestrator/index.ts +6 -0
- package/src/orchestrator/isGenerator.ts +12 -0
- package/src/orchestrator/launcher.ts +72 -0
- package/src/orchestrator/startFsmProcess.ts +73 -0
- package/src/orchestrator/types.ts +34 -0
- package/src/utils/handlers.ts +9 -4
- package/src/utils/newFsmStateHandler.ts +7 -3
- package/src/utils/printer.ts +1 -1
- package/src/startFsmProcess copy 2.ts +0 -74
- package/src/startFsmProcess copy.ts +0 -77
- package/src/startFsmProcess.ts +0 -78
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,
|
|
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:
|
|
8
|
-
_removeHandler(type: string, handler:
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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] =
|
|
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
|
|
423
|
-
if (
|
|
424
|
-
state.onExit(
|
|
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/
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
478
|
-
|
|
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.
|
|
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
|
-
"@
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
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
|
-
"
|
|
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"
|