@statewalker/fsm 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +170 -13
- package/dist/index.js +268 -93
- 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,13 +306,192 @@ var FsmProcess = class extends FsmBaseClass {
|
|
|
299
306
|
}
|
|
300
307
|
};
|
|
301
308
|
|
|
309
|
+
// src/utils/transitions.ts
|
|
310
|
+
function isStateTransitionEnabled(process, event) {
|
|
311
|
+
const transitions = getStateTransitions(process.state);
|
|
312
|
+
let active = false;
|
|
313
|
+
for (const [from, ev, to] of transitions) {
|
|
314
|
+
if (ev === event) {
|
|
315
|
+
active = true;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return active;
|
|
320
|
+
}
|
|
321
|
+
function getStateTransitions(state) {
|
|
322
|
+
const result = [];
|
|
323
|
+
let index = {};
|
|
324
|
+
if (state) {
|
|
325
|
+
let prevStateKey = state.key;
|
|
326
|
+
for (let parent = state?.parent; parent; parent = parent.parent) {
|
|
327
|
+
if (!parent.descriptor) continue;
|
|
328
|
+
result.push(
|
|
329
|
+
...getTransitionsFromDescriptor(parent.descriptor, prevStateKey, index)
|
|
330
|
+
);
|
|
331
|
+
prevStateKey = parent.key;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return result.reverse();
|
|
335
|
+
}
|
|
336
|
+
function getTransitionsFromDescriptor(descriptor, prevStateKey, index = {}) {
|
|
337
|
+
const result = [];
|
|
338
|
+
const prevStateKeys = [prevStateKey, "*"];
|
|
339
|
+
for (const prevKey of prevStateKeys) {
|
|
340
|
+
const targets = descriptor.transitions[prevKey];
|
|
341
|
+
if (targets) {
|
|
342
|
+
for (const [event, target] of Object.entries(targets)) {
|
|
343
|
+
if (index[event]) continue;
|
|
344
|
+
if (target) {
|
|
345
|
+
index[event] = true;
|
|
346
|
+
}
|
|
347
|
+
result.push([prevStateKey, event, target]);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/newFsmProcess.ts
|
|
355
|
+
function newFsmProcess(context, config, load) {
|
|
356
|
+
let started = false;
|
|
357
|
+
let terminated = false;
|
|
358
|
+
const process = new FsmProcess(config);
|
|
359
|
+
async function dispatch(event) {
|
|
360
|
+
return terminated || event !== void 0 && (!started || isStateTransitionEnabled(process, event)) ? process.dispatch(event) : false;
|
|
361
|
+
}
|
|
362
|
+
process.onStateCreate((state) => {
|
|
363
|
+
started = true;
|
|
364
|
+
state.onEnter(async () => {
|
|
365
|
+
const modules = await load(state.key, process.event) ?? [];
|
|
366
|
+
for (const module of Array.isArray(modules) ? modules : [modules]) {
|
|
367
|
+
const result = await module?.(context);
|
|
368
|
+
if (isGenerator2(result)) {
|
|
369
|
+
state.onExit(() => {
|
|
370
|
+
result.return?.(void 0);
|
|
371
|
+
});
|
|
372
|
+
(async () => {
|
|
373
|
+
for await (const event of result) {
|
|
374
|
+
if (terminated) {
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
await dispatch(event);
|
|
378
|
+
if (terminated) {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
})();
|
|
383
|
+
} else if (typeof result === "function") {
|
|
384
|
+
state.onExit(result);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
async function shutdown() {
|
|
390
|
+
await process.shutdown();
|
|
391
|
+
terminated = true;
|
|
392
|
+
}
|
|
393
|
+
return [dispatch, shutdown];
|
|
394
|
+
function isGenerator2(value) {
|
|
395
|
+
return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function";
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/orchestrator/constants.ts
|
|
400
|
+
var KEY_DISPATCH = "fsm:dispatch";
|
|
401
|
+
var KEY_TERMINATE = "fsm:terminate";
|
|
402
|
+
var KEY_STATES = "fsm:states";
|
|
403
|
+
var KEY_EVENT = "fsm:event";
|
|
404
|
+
var KEY_REGISTER_CONFIG = "fsm:sys:registerConfig";
|
|
405
|
+
var KEY_REGISTER_HANDLERS = "fsm:sys:registerHandlers";
|
|
406
|
+
var KEY_LAUNCH_PROCESS = "fsm:sys:launch";
|
|
407
|
+
|
|
408
|
+
// src/orchestrator/types.ts
|
|
409
|
+
function toStageHandlers(value, accept) {
|
|
410
|
+
return visit(value);
|
|
411
|
+
function visit(val) {
|
|
412
|
+
if (!val) {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
if (typeof val === "function") {
|
|
416
|
+
return [val];
|
|
417
|
+
}
|
|
418
|
+
if (typeof val !== "object") {
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
if (Array.isArray(val)) {
|
|
422
|
+
return val.flatMap(visit);
|
|
423
|
+
}
|
|
424
|
+
const modulesIndex = val;
|
|
425
|
+
return Object.entries(modulesIndex).flatMap(([key, value2]) => {
|
|
426
|
+
return accept(key, value2) ? visit(value2) : [];
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/orchestrator/ProcessConfigManager.ts
|
|
432
|
+
var ProcessConfigManager = class {
|
|
433
|
+
constructor() {
|
|
434
|
+
this.configs = {};
|
|
435
|
+
this.handlers = {};
|
|
436
|
+
}
|
|
437
|
+
getProcessConfig(processName) {
|
|
438
|
+
return this.configs[processName] ?? { key: "Main" };
|
|
439
|
+
}
|
|
440
|
+
getHandlersLoader(processName) {
|
|
441
|
+
const config = this.getProcessConfig(processName);
|
|
442
|
+
return (stateKey) => {
|
|
443
|
+
const modulesKeys = [];
|
|
444
|
+
if (stateKey === config.key) {
|
|
445
|
+
modulesKeys.push("default");
|
|
446
|
+
}
|
|
447
|
+
modulesKeys.push(
|
|
448
|
+
stateKey,
|
|
449
|
+
`${stateKey}Controller`,
|
|
450
|
+
`${stateKey}StateController`,
|
|
451
|
+
`${stateKey}View`,
|
|
452
|
+
`${stateKey}StateView`,
|
|
453
|
+
`${stateKey}Trigger`,
|
|
454
|
+
`${stateKey}StateTrigger`,
|
|
455
|
+
`${stateKey}Test`,
|
|
456
|
+
`${stateKey}StateTest`
|
|
457
|
+
);
|
|
458
|
+
const keysSet = new Set(modulesKeys);
|
|
459
|
+
const processHandlers = this.handlers[processName] ?? [];
|
|
460
|
+
const handlers = toStageHandlers(
|
|
461
|
+
processHandlers,
|
|
462
|
+
(key) => keysSet.has(key)
|
|
463
|
+
);
|
|
464
|
+
return handlers;
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
registerConfig(name, config) {
|
|
468
|
+
this.configs[name] = config;
|
|
469
|
+
return () => {
|
|
470
|
+
delete this.configs[name];
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
registerHandlers(name, ...modules) {
|
|
474
|
+
let list = this.handlers[name];
|
|
475
|
+
if (!list) {
|
|
476
|
+
list = this.handlers[name] = [];
|
|
477
|
+
}
|
|
478
|
+
list.push(modules);
|
|
479
|
+
return () => {
|
|
480
|
+
list = list.filter((v) => v !== modules);
|
|
481
|
+
if (list.length === 0) {
|
|
482
|
+
delete this.handlers[name];
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
302
488
|
// src/utils/printer.ts
|
|
303
489
|
var KEY_PRINTER = "printer";
|
|
304
490
|
function preparePrinter(process, { prefix = "", print = console.log, lineNumbers = false }) {
|
|
305
491
|
let lineCounter = 0;
|
|
306
492
|
const shift = () => {
|
|
307
493
|
let prefix2 = "";
|
|
308
|
-
for (let s = process.state?.parent;
|
|
494
|
+
for (let s = process.state?.parent; s; s = s.parent) {
|
|
309
495
|
prefix2 += " ";
|
|
310
496
|
}
|
|
311
497
|
return prefix2;
|
|
@@ -364,101 +550,37 @@ function newProcess(config, {
|
|
|
364
550
|
return process;
|
|
365
551
|
}
|
|
366
552
|
|
|
367
|
-
// src/
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
let active = false;
|
|
371
|
-
for (const [from, ev, to] of transitions) {
|
|
372
|
-
if (ev === event) {
|
|
373
|
-
active = true;
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return active;
|
|
378
|
-
}
|
|
379
|
-
function getStateTransitions(state) {
|
|
380
|
-
const result = [];
|
|
381
|
-
let index = {};
|
|
382
|
-
if (state) {
|
|
383
|
-
let prevStateKey = state.key;
|
|
384
|
-
for (let parent = state?.parent; parent; parent = parent.parent) {
|
|
385
|
-
if (!parent.descriptor) continue;
|
|
386
|
-
result.push(
|
|
387
|
-
...getTransitionsFromDescriptor(parent.descriptor, prevStateKey, index)
|
|
388
|
-
);
|
|
389
|
-
prevStateKey = parent.key;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return result.reverse();
|
|
393
|
-
}
|
|
394
|
-
function getTransitionsFromDescriptor(descriptor, prevStateKey, index = {}) {
|
|
395
|
-
const result = [];
|
|
396
|
-
const prevStateKeys = [prevStateKey, "*"];
|
|
397
|
-
for (const prevKey of prevStateKeys) {
|
|
398
|
-
const targets = descriptor.transitions[prevKey];
|
|
399
|
-
if (targets) {
|
|
400
|
-
for (const [event, target] of Object.entries(targets)) {
|
|
401
|
-
if (index[event]) continue;
|
|
402
|
-
if (target) {
|
|
403
|
-
index[event] = true;
|
|
404
|
-
}
|
|
405
|
-
result.push([prevStateKey, event, target]);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return result;
|
|
553
|
+
// src/orchestrator/isGenerator.ts
|
|
554
|
+
function isGenerator(value) {
|
|
555
|
+
return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function";
|
|
410
556
|
}
|
|
411
557
|
|
|
412
|
-
// src/
|
|
413
|
-
function
|
|
414
|
-
let started = false;
|
|
558
|
+
// src/orchestrator/startFsmProcess.ts
|
|
559
|
+
async function startFsmProcess(context, config, load, startEvent = "") {
|
|
415
560
|
let terminated = false;
|
|
561
|
+
const ctx = context;
|
|
416
562
|
const process = new FsmProcess(config);
|
|
563
|
+
const statesStack = [];
|
|
417
564
|
process.onStateCreate((state) => {
|
|
418
|
-
started = true;
|
|
419
565
|
state.onEnter(async () => {
|
|
566
|
+
statesStack.push(state.key);
|
|
567
|
+
ctx[KEY_STATES] = [...statesStack];
|
|
568
|
+
ctx[KEY_EVENT] = process.event;
|
|
420
569
|
const modules = await load(state.key, process.event) ?? [];
|
|
421
570
|
for (const module of Array.isArray(modules) ? modules : [modules]) {
|
|
422
|
-
const
|
|
423
|
-
if (typeof handler === "function") {
|
|
424
|
-
state.onExit(handler);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
async function dispatch(event) {
|
|
430
|
-
return terminated || event !== void 0 && (!started || isStateTransitionEnabled(process, event)) ? process.dispatch(event) : false;
|
|
431
|
-
}
|
|
432
|
-
async function shutdown() {
|
|
433
|
-
await process.shutdown();
|
|
434
|
-
terminated = true;
|
|
435
|
-
}
|
|
436
|
-
return [dispatch, shutdown];
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// src/startFsmProcess.ts
|
|
440
|
-
function startFsmProcess(context, config, load, startEvent = "") {
|
|
441
|
-
let started = false;
|
|
442
|
-
let terminated = false;
|
|
443
|
-
const process = new FsmProcess(config);
|
|
444
|
-
process.onStateCreate((state) => {
|
|
445
|
-
started = true;
|
|
446
|
-
state.onEnter(async () => {
|
|
447
|
-
const ModuleOrTriggers = await load(state.key, process.event) ?? [];
|
|
448
|
-
for (const ModuleOrTrigger of Array.isArray(ModuleOrTriggers) ? ModuleOrTriggers : [ModuleOrTriggers]) {
|
|
449
|
-
const result = await ModuleOrTrigger?.(context);
|
|
571
|
+
const result = await module?.(context);
|
|
450
572
|
if (isGenerator(result)) {
|
|
573
|
+
let stateExited = false;
|
|
451
574
|
state.onExit(() => {
|
|
575
|
+
stateExited = true;
|
|
452
576
|
result.return?.(void 0);
|
|
453
577
|
});
|
|
454
578
|
(async () => {
|
|
455
579
|
for await (const event of result) {
|
|
456
|
-
if (terminated) {
|
|
580
|
+
if (stateExited || terminated) {
|
|
457
581
|
break;
|
|
458
582
|
}
|
|
459
|
-
|
|
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"
|