@statewalker/fsm 0.36.0 → 0.38.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/README.md +257 -0
- package/dist/index.d.ts +297 -279
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +543 -761
- package/dist/index.js.map +1 -1
- package/package.json +10 -20
- package/src/core/fsm-base-class.ts +26 -26
- package/src/core/fsm-process.ts +69 -11
- package/src/core/fsm-state-config.ts +27 -0
- package/src/core/fsm-state-descriptor.ts +21 -0
- package/src/core/fsm-state.ts +22 -21
- package/src/core/fsm-transitions.ts +72 -0
- package/src/core/index.ts +2 -1
- package/src/index.ts +2 -5
- package/src/start-process.ts +185 -0
- package/src/trace/index.ts +2 -0
- package/src/trace/printer.ts +62 -0
- package/src/{utils → trace}/tracer.ts +12 -1
- package/CHANGELOG.md +0 -59
- package/bin/cli.js +0 -5
- package/src/core/new-fsm-process.ts +0 -83
- package/src/orchestrator/constants.ts +0 -130
- package/src/orchestrator/index.ts +0 -9
- package/src/orchestrator/is-generator.ts +0 -12
- package/src/orchestrator/launcher.ts +0 -172
- package/src/orchestrator/process-config-manager.ts +0 -70
- package/src/orchestrator/resolve-module-refs.ts +0 -52
- package/src/orchestrator/start-node-processes.ts +0 -19
- package/src/orchestrator/start-process.ts +0 -73
- package/src/orchestrator/start-processes.ts +0 -32
- package/src/orchestrator/types.ts +0 -34
- package/src/utils/handlers.ts +0 -35
- package/src/utils/index.ts +0 -4
- package/src/utils/printer.ts +0 -48
- package/src/utils/process.ts +0 -26
- package/src/utils/transitions.ts +0 -56
package/dist/index.d.ts
CHANGED
|
@@ -1,335 +1,353 @@
|
|
|
1
|
+
//#region src/core/fsm-base-class.d.ts
|
|
1
2
|
type Handler<P extends unknown[] = unknown[], R = unknown> = (...args: P) => R;
|
|
3
|
+
/**
|
|
4
|
+
* Shared handler-registry substrate under both `FsmProcess` and `FsmState`.
|
|
5
|
+
*
|
|
6
|
+
* Both the process and every state need the same primitive: keep named lists of
|
|
7
|
+
* callbacks (`onEnter`, `onExit`, `dump`, …) and run them with consistent ordering
|
|
8
|
+
* and error routing. Centralising it here keeps the two subclasses thin and their
|
|
9
|
+
* hook methods one-liners. `handlers` maps a hook name to its ordered callback list;
|
|
10
|
+
* the protected `_addHandler` / `_removeHandler` / `_runHandler` manage and invoke
|
|
11
|
+
* them. Handlers run **sequentially** (awaited in turn); a throwing handler is routed
|
|
12
|
+
* to `_handleError` rather than aborting the batch. Subclasses expose typed sugar
|
|
13
|
+
* (e.g. `state.onEnter(fn)` calls `_addHandler("onEnter", fn)`) and override
|
|
14
|
+
* `_handleError` to forward errors.
|
|
15
|
+
*/
|
|
2
16
|
declare class FsmBaseClass {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
handlers: Record<string, Handler[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Register `handler` under `type`; returns a disposer that removes it.
|
|
20
|
+
* `direct=false` prepends instead of appends — used so `onExit` handlers run in
|
|
21
|
+
* reverse (inner-to-outer) unwinding order.
|
|
22
|
+
*/
|
|
23
|
+
protected _addHandler<T>(type: string, handler: T, direct?: boolean): () => void;
|
|
24
|
+
protected _removeHandler<T>(type: string, handler: T): void;
|
|
25
|
+
/** Run every handler of `type` in order, awaiting each; route throws to `_handleError`. */
|
|
26
|
+
_runHandler(type: string, ...args: unknown[]): Promise<void>;
|
|
27
|
+
/** Default error sink — logs. `FsmState`/`FsmProcess` override it to fire `onStateError`. */
|
|
28
|
+
_handleError(error: Error | unknown): Promise<void>;
|
|
13
29
|
}
|
|
14
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Bind the named methods of `obj` to `obj` in place, so they can be passed as bare
|
|
32
|
+
* callbacks (e.g. `ctx[KEY_DISPATCH] = process.dispatch`) without losing `this`.
|
|
33
|
+
*/
|
|
34
|
+
declare function bindMethods<T>(obj: T, ...methods: (string | symbol)[]): T;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/core/fsm-state-config.d.ts
|
|
37
|
+
/** Wildcard `from`-state: a transition that applies in *any* state. */
|
|
15
38
|
declare const STATE_ANY = "*";
|
|
39
|
+
/** Empty `from`-state: the *initial* pseudo-state a parent enters into. */
|
|
16
40
|
declare const STATE_INITIAL = "";
|
|
41
|
+
/** Empty `to`-state: the *final* pseudo-state; entering it exits the parent. */
|
|
17
42
|
declare const STATE_FINAL = "";
|
|
43
|
+
/** Wildcard `event`: a transition triggered by *any* event. */
|
|
18
44
|
declare const EVENT_ANY = "*";
|
|
45
|
+
/** Empty event: the eventless/automatic transition (fires with no named event). */
|
|
19
46
|
declare const EVENT_EMPTY = "";
|
|
20
47
|
type FsmStateKey = string;
|
|
21
48
|
type FsmEventKey = string;
|
|
49
|
+
/**
|
|
50
|
+
* Declarative definition of a (possibly nested) state machine.
|
|
51
|
+
*
|
|
52
|
+
* One plain-object shape describes an entire HFSM, so machines stay serializable,
|
|
53
|
+
* diffable, and inspectable by tooling — the config is data, not code. A config has:
|
|
54
|
+
* - `key` — the state's name (unique among its siblings);
|
|
55
|
+
* - `transitions` — `[from, event, to]` tuples, where `from`/`to` name sibling
|
|
56
|
+
* states, `""` is the initial (`from`) or final (`to`) pseudo-state, and `"*"` is a
|
|
57
|
+
* wildcard (see the constants above);
|
|
58
|
+
* - `states` — nested child configs; entering this state descends into its initial
|
|
59
|
+
* child. The index signature lets consumers attach arbitrary metadata.
|
|
60
|
+
*
|
|
61
|
+
* Pass a config to `new FsmProcess(config)` or `startProcess(ctx, config, …)`; it is
|
|
62
|
+
* compiled once into an `FsmStateDescriptor` for fast lookup at runtime.
|
|
63
|
+
*/
|
|
22
64
|
type FsmStateConfig = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
65
|
+
key: FsmStateKey;
|
|
66
|
+
transitions?: [from: FsmStateKey, event: FsmEventKey, to: FsmStateKey][];
|
|
67
|
+
states?: FsmStateConfig[];
|
|
26
68
|
} & Record<string, unknown>;
|
|
27
|
-
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/core/fsm-state-descriptor.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* The *compiled* form of an `FsmStateConfig` subtree.
|
|
73
|
+
*
|
|
74
|
+
* Resolving a transition happens on every event, so the flat `[from, event, to]`
|
|
75
|
+
* tuple list is pre-indexed once into nested maps for O(1) lookup, and the
|
|
76
|
+
* wildcard-fallback order is applied here rather than re-derived on each dispatch.
|
|
77
|
+
* The shape is `transitions[from][event] = to` plus a `states` map of child
|
|
78
|
+
* descriptors. `FsmProcess` builds one from your config in its constructor; you
|
|
79
|
+
* rarely construct a descriptor directly.
|
|
80
|
+
*/
|
|
28
81
|
declare class FsmStateDescriptor {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
82
|
+
transitions: Record<string, Record<string, string>>;
|
|
83
|
+
states: Record<string, FsmStateDescriptor>;
|
|
84
|
+
/**
|
|
85
|
+
* Recursively compile a config (and its nested `states`) into descriptors,
|
|
86
|
+
* turning the tuple list into the indexed `transitions` map.
|
|
87
|
+
*/
|
|
88
|
+
static build(config: FsmStateConfig): FsmStateDescriptor;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the target state for a `(stateKey, eventKey)` pair. Tries the most
|
|
91
|
+
* specific match first, then falls back through the wildcards —
|
|
92
|
+
* `(state, event)` → `(*, event)` → `(state, *)` → `(*, *)` — and returns
|
|
93
|
+
* `STATE_FINAL` if nothing matches, so an unmatched event exits the state rather
|
|
94
|
+
* than silently doing nothing.
|
|
95
|
+
*/
|
|
96
|
+
getTargetStateKey(stateKey: string, eventKey: string): string;
|
|
33
97
|
}
|
|
34
|
-
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/core/fsm-state.d.ts
|
|
100
|
+
/** Serialized form of a single state: its `key` plus the `data` bag its `dump` hooks filled. */
|
|
35
101
|
type FsmStateDump = Record<string, unknown> & {
|
|
36
|
-
|
|
37
|
-
|
|
102
|
+
key: string;
|
|
103
|
+
data: Record<string, unknown>;
|
|
38
104
|
};
|
|
105
|
+
/** An `onEnter` / `onExit` callback; receives the state it fired on. */
|
|
39
106
|
type FsmStateHandler = (state: FsmState, ...args: unknown[]) => void | Promise<void>;
|
|
107
|
+
/** A `dump` / `restore` callback; reads or fills the mutable per-state `data` bag. */
|
|
40
108
|
type FsmStateDumpHandler = (state: FsmState, dump: FsmStateDump) => void | Promise<void>;
|
|
109
|
+
/** An `onStateError` callback; receives the error thrown by another handler on this state. */
|
|
41
110
|
type FsmStateErrorHandler = (state: FsmState, error: unknown) => void | Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* One live node in a running machine's active-state stack.
|
|
113
|
+
*
|
|
114
|
+
* A state needs a place to attach behaviour and record data while it is active.
|
|
115
|
+
* `FsmState` is that handle — created by the engine each time a state is entered,
|
|
116
|
+
* discarded when it exits — so handlers can capture per-activation closures instead
|
|
117
|
+
* of sharing mutable machine-wide state. It offers lifecycle hooks `onEnter` /
|
|
118
|
+
* `onExit` / `onStateError`, serialization hooks `dump` / `restore`, and the tree
|
|
119
|
+
* links `key` / `parent` / `descriptor`; every hook method returns a disposer.
|
|
120
|
+
* Attach hooks from within `FsmProcess.onStateCreate((state) => …)`, or let
|
|
121
|
+
* `startProcess` install them for you from a `load` callback.
|
|
122
|
+
*/
|
|
42
123
|
declare class FsmState extends FsmBaseClass {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
124
|
+
process: FsmProcess;
|
|
125
|
+
key: string;
|
|
126
|
+
parent?: FsmState;
|
|
127
|
+
descriptor?: FsmStateDescriptor;
|
|
128
|
+
constructor(process: FsmProcess, parent: FsmState | undefined, key: string, descriptor?: FsmStateDescriptor);
|
|
129
|
+
/** Run when this state is entered. */
|
|
130
|
+
onEnter(handler: FsmStateHandler): () => void;
|
|
131
|
+
/** Run when this state exits — registered inner-first so unwinding is inner-to-outer. */
|
|
132
|
+
onExit(handler: FsmStateHandler): () => void;
|
|
133
|
+
/** Handle an error thrown by another handler on this state (also bubbles to the process). */
|
|
134
|
+
onStateError(handler: FsmStateErrorHandler): () => void;
|
|
135
|
+
/** Contribute to this state's snapshot: fill `dump.data` when the process is dumped. */
|
|
136
|
+
dump(handler: FsmStateDumpHandler): () => void;
|
|
137
|
+
/** Rehydrate from this state's snapshot: read `dump.data` when the process is restored. */
|
|
138
|
+
restore(handler: FsmStateDumpHandler): () => void;
|
|
139
|
+
_handleError(error: Error | unknown): Promise<void>;
|
|
56
140
|
}
|
|
57
|
-
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/core/fsm-process.d.ts
|
|
143
|
+
/** Not started / no current transition. */
|
|
58
144
|
declare const STATUS_NONE = 0;
|
|
145
|
+
/** Entering: descended into a parent's first (initial) child. */
|
|
59
146
|
declare const STATUS_FIRST = 1;
|
|
147
|
+
/** Entering: advanced to a resolved target (sibling) state. */
|
|
60
148
|
declare const STATUS_NEXT = 2;
|
|
149
|
+
/** Rested on a leaf — dispatch returns control here (the default `mask`). */
|
|
61
150
|
declare const STATUS_LEAF = 4;
|
|
151
|
+
/** Exiting: popped back up to the parent state. */
|
|
62
152
|
declare const STATUS_LAST = 8;
|
|
153
|
+
/** Terminated: the machine has exited its root and cannot advance further. */
|
|
63
154
|
declare const STATUS_FINISHED = 16;
|
|
155
|
+
/** Composite: any "entering" phase. */
|
|
64
156
|
declare const STATUS_ENTER: number;
|
|
157
|
+
/** Composite: any "exiting" phase. */
|
|
65
158
|
declare const STATUS_EXIT: number;
|
|
159
|
+
/** A process-level handler (`onStateCreate` / `onStateError`). */
|
|
66
160
|
type FsmProcessHandler = (process: FsmProcess, ...args: unknown[]) => void | Promise<void>;
|
|
161
|
+
/** Serialized form of the whole machine: status, last event, and the root→leaf state stack. */
|
|
67
162
|
type FsmProcessDump = Record<string, unknown> & {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
163
|
+
status: number;
|
|
164
|
+
event?: string;
|
|
165
|
+
stack: FsmStateDump[];
|
|
71
166
|
};
|
|
72
167
|
type FsmProcessDumpHandler = (process: FsmProcess, dump: FsmProcessDump) => void | Promise<void>;
|
|
73
|
-
declare class FsmProcess extends FsmBaseClass {
|
|
74
|
-
state?: FsmState;
|
|
75
|
-
event?: string;
|
|
76
|
-
nextEvent?: string;
|
|
77
|
-
running: boolean;
|
|
78
|
-
mask: number;
|
|
79
|
-
status: number;
|
|
80
|
-
config: FsmStateConfig;
|
|
81
|
-
rootDescriptor: FsmStateDescriptor;
|
|
82
|
-
constructor(config: FsmStateConfig);
|
|
83
|
-
shutdown(event?: string): Promise<void>;
|
|
84
|
-
dispatch(event: string): Promise<boolean>;
|
|
85
|
-
dump(...args: unknown[]): Promise<FsmProcessDump>;
|
|
86
|
-
restore(dump: FsmProcessDump, ...args: unknown[]): Promise<this>;
|
|
87
|
-
onStateCreate(handler: FsmStateHandler): () => void;
|
|
88
|
-
onStateError(handler: (state: FsmState, error: unknown) => void | Promise<void>): () => void;
|
|
89
|
-
_handleStateError(state: FsmState, error: Error | unknown): Promise<void>;
|
|
90
|
-
_newState(parent: FsmState | undefined, key: string, descriptor: FsmStateDescriptor | undefined): FsmState;
|
|
91
|
-
_getSubstate(parent: FsmState | undefined, prevStateKey: string | undefined): FsmState | undefined;
|
|
92
|
-
_newSubstate(parent: FsmState | undefined, toState: string): FsmState;
|
|
93
|
-
_update(): boolean;
|
|
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
168
|
/**
|
|
128
|
-
*
|
|
129
|
-
* This provides a snapshot of the states the FSM process has entered.
|
|
130
|
-
* It is represented as an array of strings -- state keys.
|
|
169
|
+
* The running state machine — owner of the active-state stack and the traversal.
|
|
131
170
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
171
|
+
* This is the engine: given a compiled config it drives the enter/exit walk in
|
|
172
|
+
* response to events, so callers reason in terms of states and events, not manual
|
|
173
|
+
* stack bookkeeping. `dispatch(event)` advances the machine to the next resting leaf;
|
|
174
|
+
* `shutdown(event?)` unwinds every active state; `state` is the current leaf;
|
|
175
|
+
* `onStateCreate(handler)` is the primary extension point (fires once per created
|
|
176
|
+
* state — attach that state's hooks there); and `dump()` / `restore(dump)` snapshot
|
|
177
|
+
* and rehydrate the whole stack. Typical use: `new FsmProcess(config)`, register
|
|
178
|
+
* `onStateCreate`, then `dispatch("")` to enter the initial state and
|
|
179
|
+
* `dispatch(event)` for each subsequent event.
|
|
139
180
|
*/
|
|
140
|
-
declare
|
|
181
|
+
declare class FsmProcess extends FsmBaseClass {
|
|
182
|
+
state?: FsmState;
|
|
183
|
+
event?: string;
|
|
184
|
+
nextEvent?: string;
|
|
185
|
+
running: boolean;
|
|
186
|
+
mask: number;
|
|
187
|
+
status: number;
|
|
188
|
+
config: FsmStateConfig;
|
|
189
|
+
rootDescriptor: FsmStateDescriptor;
|
|
190
|
+
constructor(config: FsmStateConfig);
|
|
191
|
+
/** Force-exit the whole stack (root last), running each state's `onExit`; ends the machine. */
|
|
192
|
+
shutdown(event?: string): Promise<void>;
|
|
193
|
+
/**
|
|
194
|
+
* Feed an event to the machine and run the enter/exit cycle until it rests on a
|
|
195
|
+
* leaf (`status & mask`) or finishes.
|
|
196
|
+
*
|
|
197
|
+
* If a dispatch is already running (e.g. an `onEnter` handler dispatches
|
|
198
|
+
* synchronously), the event is queued in `nextEvent` and applied when the current
|
|
199
|
+
* run settles — runs never nest. Returns `false` once the machine has finished,
|
|
200
|
+
* `true` otherwise.
|
|
201
|
+
*/
|
|
202
|
+
dispatch(event: string): Promise<boolean>;
|
|
203
|
+
/**
|
|
204
|
+
* Snapshot the machine to a plain object: `{ status, event, stack }` where the
|
|
205
|
+
* stack is root→leaf, each entry carrying whatever its `dump` hooks recorded.
|
|
206
|
+
*/
|
|
207
|
+
dump(...args: unknown[]): Promise<FsmProcessDump>;
|
|
208
|
+
/**
|
|
209
|
+
* Rebuild the machine from a `dump`: recreate each state in the stack (firing
|
|
210
|
+
* `onStateCreate`) and replay its `restore` hooks, leaving the process resumable
|
|
211
|
+
* from where it was snapshotted.
|
|
212
|
+
*/
|
|
213
|
+
restore(dump: FsmProcessDump, ...args: unknown[]): Promise<this>;
|
|
214
|
+
/** Primary extension point: fires once for every state the machine creates. */
|
|
215
|
+
onStateCreate(handler: FsmStateHandler): () => void;
|
|
216
|
+
onStateError(handler: (state: FsmState, error: unknown) => void | Promise<void>): () => void;
|
|
217
|
+
_handleStateError(state: FsmState, error: Error | unknown): Promise<void>;
|
|
218
|
+
_newState(parent: FsmState | undefined, key: string, descriptor: FsmStateDescriptor | undefined): Promise<FsmState>;
|
|
219
|
+
_getSubstate(parent: FsmState | undefined, prevStateKey: string | undefined): Promise<FsmState | undefined>;
|
|
220
|
+
_newSubstate(parent: FsmState | undefined, toState: string): Promise<FsmState>;
|
|
221
|
+
/**
|
|
222
|
+
* One step of the traversal: compute the next state and update `status`.
|
|
223
|
+
*
|
|
224
|
+
* Either descends to a child / advances to a resolved target (setting an ENTER
|
|
225
|
+
* status), or — when no target resolves — settles on the current leaf
|
|
226
|
+
* (`STATUS_LEAF`) or pops to the parent (`STATUS_LAST`), reaching `STATUS_FINISHED`
|
|
227
|
+
* once the root is exited. Returns `false` when finished.
|
|
228
|
+
*/
|
|
229
|
+
_update(): Promise<boolean>;
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/core/fsm-transitions.d.ts
|
|
141
233
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
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
|
-
* ```
|
|
234
|
+
* List the transitions currently reachable from `state` — for a UI or viewer that
|
|
235
|
+
* needs to know which events can fire right now (e.g. to render only the enabled
|
|
236
|
+
* buttons). Collects `[from, event, to]` tuples by walking up the parent chain; the
|
|
237
|
+
* nearest state's rule for an event wins, so an outer fallback is masked by an inner
|
|
238
|
+
* override. The returned tuples are ordered outer→inner (root first).
|
|
152
239
|
*/
|
|
153
|
-
declare
|
|
240
|
+
declare function getStateTransitions(state?: FsmState): [from: string, event: string, to: string][];
|
|
154
241
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
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
|
-
* ```
|
|
242
|
+
* Guard: would `event` trigger any transition in the process's current state? Used
|
|
243
|
+
* to avoid dispatching dead events — the runner calls it before `dispatch`, and
|
|
244
|
+
* consumers use it to enable/disable controls. Returns `true` iff `event` appears
|
|
245
|
+
* among `getStateTransitions(process.state)`.
|
|
170
246
|
*/
|
|
171
|
-
declare
|
|
247
|
+
declare function isStateTransitionEnabled(process: FsmProcess, event: string): boolean;
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/start-process.d.ts
|
|
172
250
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
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
|
-
* ```
|
|
251
|
+
* Per-state behaviour contract used by `startProcess`. One function shape expresses
|
|
252
|
+
* what to do while in a state, and its *return value* wires up the rest, so a state's
|
|
253
|
+
* setup and teardown live together. The handler runs on entry with the shared
|
|
254
|
+
* `context` and may return:
|
|
255
|
+
* - nothing — no teardown;
|
|
256
|
+
* - a cleanup `function` — registered as this state's `onExit`;
|
|
257
|
+
* - an async/sync generator — run concurrently, each yielded string dispatched back
|
|
258
|
+
* into the machine (self-driving/reactive states), auto-`return()`ed on exit.
|
|
203
259
|
*/
|
|
204
|
-
|
|
260
|
+
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>;
|
|
205
261
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
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
|
-
* ```
|
|
262
|
+
* The caller's remote control returned by `startProcess`: stop the machine and
|
|
263
|
+
* snapshot/rehydrate it without holding a reference to the underlying `FsmProcess`.
|
|
221
264
|
*/
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
type ProcessConfig = {
|
|
228
|
-
name: string;
|
|
229
|
-
config?: FsmStateConfig;
|
|
230
|
-
} & ({
|
|
231
|
-
handler?: StageHandler;
|
|
232
|
-
} | {
|
|
233
|
-
handlers?: StageHandler | (StageHandler | {
|
|
234
|
-
[state: string]: StageHandler | StageHandler[];
|
|
235
|
-
})[];
|
|
236
|
-
} | {
|
|
237
|
-
default?: StageHandler | (StageHandler | {
|
|
238
|
-
[state: string]: StageHandler | StageHandler[];
|
|
239
|
-
})[];
|
|
240
|
-
});
|
|
241
|
-
declare function launcher(options: unknown): Promise<() => Promise<void>>;
|
|
242
|
-
|
|
243
|
-
interface IProcessConfigManager {
|
|
244
|
-
getProcessConfig(processName: string): FsmStateConfig;
|
|
245
|
-
getHandlersLoader<C = unknown>(processName: string): (state: string, event: undefined | string) => StageHandler<C>[];
|
|
246
|
-
registerConfig(name: string, config: FsmStateConfig): () => void;
|
|
247
|
-
registerHandlers(name: string, ...modules: unknown[]): () => void;
|
|
248
|
-
}
|
|
249
|
-
declare class ProcessConfigManager implements IProcessConfigManager {
|
|
250
|
-
protected configs: Record<string, FsmStateConfig>;
|
|
251
|
-
protected handlers: Record<string, unknown[]>;
|
|
252
|
-
getProcessConfig(processName: string): FsmStateConfig;
|
|
253
|
-
getHandlersLoader<C = unknown>(processName: string): (state: string, event: undefined | string) => StageHandler<C>[];
|
|
254
|
-
registerConfig(name: string, config: FsmStateConfig): () => void;
|
|
255
|
-
registerHandlers(name: string, ...modules: unknown[]): () => void;
|
|
265
|
+
interface ProcessHandle {
|
|
266
|
+
shutdown(): Promise<void>;
|
|
267
|
+
dump(...args: unknown[]): Promise<FsmProcessDump>;
|
|
268
|
+
restore(dump: FsmProcessDump, ...args: unknown[]): Promise<void>;
|
|
256
269
|
}
|
|
257
|
-
|
|
270
|
+
/** Context key: `(event) => Promise<void>` that dispatches an event (guarded). */
|
|
271
|
+
declare const KEY_DISPATCH = "fsm:dispatch";
|
|
272
|
+
/** Context key: `() => Promise<void>` that shuts the machine down. */
|
|
273
|
+
declare const KEY_TERMINATE = "fsm:terminate";
|
|
274
|
+
/** Context key: the current state-key stack (root→leaf), refreshed on every transition. */
|
|
275
|
+
declare const KEY_STATES = "fsm:states";
|
|
276
|
+
/** Context key: the last dispatched event. */
|
|
277
|
+
declare const KEY_EVENT = "fsm:event";
|
|
258
278
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
279
|
+
* Ergonomic runner: build a machine, attach behaviour via one `load` callback, and
|
|
280
|
+
* bind the machine into `context`.
|
|
281
|
+
*
|
|
282
|
+
* Doing this by hand (`new FsmProcess` + `onStateCreate` + `onEnter` + a loader +
|
|
283
|
+
* context wiring) is boilerplate every consumer repeats, so `startProcess` does it
|
|
284
|
+
* once — most callers use this instead of the raw engine. It creates the process; on
|
|
285
|
+
* each state entry calls `load(stateKey, event)` and installs the returned
|
|
286
|
+
* `StageHandler`s (their return values become `onExit` cleanups or event-yielding
|
|
287
|
+
* generators — see `StageHandler`); binds `KEY_DISPATCH` / `KEY_TERMINATE` /
|
|
288
|
+
* `KEY_STATES` / `KEY_EVENT` into `context`; dispatches `startEvent` to enter the
|
|
289
|
+
* initial state; and returns a `ProcessHandle`.
|
|
261
290
|
*
|
|
262
|
-
* @param
|
|
263
|
-
* @param
|
|
264
|
-
* @returns
|
|
265
|
-
* @
|
|
266
|
-
* ```ts
|
|
267
|
-
* const urls = resolveModulesUrls(
|
|
268
|
-
* 'file:///home/user/project/', // base URL
|
|
269
|
-
* 'https://example.com/js/my-module.js', // absolute URL
|
|
270
|
-
* './module1.js', // relative path
|
|
271
|
-
* '../module2.js' // relative path
|
|
272
|
-
* );
|
|
273
|
-
* console.log(urls);
|
|
274
|
-
* // Output: [
|
|
275
|
-
* // 'https://example.com/js/my-module.js',
|
|
276
|
-
* // 'file:///home/user/project/module1.js',
|
|
277
|
-
* // 'file:///home/user/module2.js'
|
|
278
|
-
* // ] *
|
|
279
|
-
* ```
|
|
291
|
+
* @param context shared object handlers read/write and the machine is bound into
|
|
292
|
+
* @param config the declarative machine definition
|
|
293
|
+
* @param load returns the handler(s) to run for a given `(stateKey, event)`
|
|
294
|
+
* @param startEvent initial event (default `""`, the eventless start)
|
|
280
295
|
*/
|
|
281
|
-
declare function
|
|
296
|
+
declare function startProcess<C = unknown>(context: C, config: FsmStateConfig, load: (state: string, event: string | undefined) => StageHandler<C>[] | Promise<StageHandler<C>[]>, startEvent?: string): Promise<ProcessHandle>;
|
|
297
|
+
/** Permanent equal alias of {@link startProcess} — the name existing consumers import. */
|
|
298
|
+
declare const startFsmProcess: typeof startProcess;
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/trace/printer.d.ts
|
|
301
|
+
/** A sink for trace output — anything shaped like `console.log`. */
|
|
302
|
+
type Printer = (...args: unknown[]) => void;
|
|
282
303
|
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* @returns - An array of resolved absolute module paths as strings
|
|
288
|
-
* @example
|
|
289
|
-
* ```ts
|
|
290
|
-
* const paths = resolveModulesPaths('file:///home/user/project/', './module1.js', '../module2.js');
|
|
291
|
-
* console.log(paths);
|
|
292
|
-
* // Output: [
|
|
293
|
-
* // '/home/user/project/module1.js',
|
|
294
|
-
* // '/home/user/module2.js'
|
|
295
|
-
* // ]
|
|
296
|
-
* ```
|
|
304
|
+
* Tuning for a `Printer`.
|
|
305
|
+
* - `prefix` — string prepended to every line (e.g. a process tag).
|
|
306
|
+
* - `print` — the underlying sink (defaults to `console.log`).
|
|
307
|
+
* - `lineNumbers` — prepend an incrementing `[n]` counter.
|
|
297
308
|
*/
|
|
298
|
-
declare function resolveModulesPaths(baseUrl: URL | string, ...refs: (URL | string)[]): string[];
|
|
299
|
-
|
|
300
|
-
declare function startNodeProcesses(modules?: string[]): Promise<void>;
|
|
301
|
-
|
|
302
|
-
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>>;
|
|
303
|
-
|
|
304
|
-
declare function startProcesses({ onExit, modules, init, }: {
|
|
305
|
-
onExit?: () => Promise<void> | void;
|
|
306
|
-
modules: unknown[];
|
|
307
|
-
init?: (context: Record<string, unknown>) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
308
|
-
}): Promise<() => Promise<void>>;
|
|
309
|
-
|
|
310
|
-
type Printer = (...args: unknown[]) => void;
|
|
311
309
|
type PrinterConfig = {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
310
|
+
prefix?: string;
|
|
311
|
+
print?: (...args: unknown[]) => void;
|
|
312
|
+
lineNumbers?: boolean;
|
|
315
313
|
};
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Build a `Printer` bound to `process` that indents each line by the current state
|
|
316
|
+
* nesting depth (two spaces per level), so log output visually mirrors the state
|
|
317
|
+
* tree — hierarchical indentation makes enter/exit traces readable at a glance.
|
|
318
|
+
*/
|
|
319
|
+
declare function preparePrinter(process: FsmProcess, {
|
|
320
|
+
prefix,
|
|
321
|
+
print,
|
|
322
|
+
lineNumbers
|
|
323
|
+
}: PrinterConfig): Printer;
|
|
324
|
+
/**
|
|
325
|
+
* Attach a printer to `process` (stored in a weak map so it is GC'd with the
|
|
326
|
+
* process). Handlers/tracers then reach it via {@link getProcessPrinter} /
|
|
327
|
+
* {@link getPrinter} rather than threading a logger through every call.
|
|
328
|
+
*/
|
|
319
329
|
declare function setProcessPrinter(process: FsmProcess, config?: PrinterConfig): void;
|
|
330
|
+
/** The printer attached to `process`, or `console.log` if none was set. */
|
|
320
331
|
declare function getProcessPrinter(process: FsmProcess): Printer;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
332
|
+
/** The printer for a state — its own if set, else its process's (see {@link getProcessPrinter}). */
|
|
333
|
+
declare function getPrinter(state: {
|
|
334
|
+
process: FsmProcess;
|
|
335
|
+
}): Printer;
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region src/trace/tracer.d.ts
|
|
338
|
+
/**
|
|
339
|
+
* Trace every state of `process`: as each state is created, attach a state tracer —
|
|
340
|
+
* so you can watch the machine move as a readable stream of enter/exit markers
|
|
341
|
+
* without editing any handler. Hooks `onStateCreate` and delegates to
|
|
342
|
+
* {@link setStateTracer}; pass a `print` sink to override the process printer.
|
|
343
|
+
*/
|
|
329
344
|
declare function setProcessTracer(process: FsmProcess, print?: Printer): () => void;
|
|
345
|
+
/**
|
|
346
|
+
* Trace one state's lifecycle: emit `<key event="…">` on enter and `</key>` on exit
|
|
347
|
+
* (XML-like, so nested states read as nested tags). Falls back to the state's
|
|
348
|
+
* printer ({@link getPrinter}) when no `print` sink is given.
|
|
349
|
+
*/
|
|
330
350
|
declare function setStateTracer(state: FsmState, print?: Printer): void;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
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 Module, type Printer, type PrinterConfig, type ProcessConfig, ProcessConfigManager, STATE_ANY, STATE_FINAL, STATE_INITIAL, STATUS_ENTER, STATUS_EXIT, STATUS_FINISHED, STATUS_FIRST, STATUS_LAST, STATUS_LEAF, STATUS_NEXT, STATUS_NONE, type StageHandler, startNodeProcesses as default, getPrinter, getProcessPrinter, getStateTransitions, isStateTransitionEnabled, launcher, newFsmProcess, newProcess, preparePrinter, resolveModulesPaths, resolveModulesUrls, setPrinter, setProcessPrinter, setProcessTracer, setStateTracer, startFsmProcess, startNodeProcesses, startProcesses, toStageHandlers };
|
|
351
|
+
//#endregion
|
|
352
|
+
export { EVENT_ANY, EVENT_EMPTY, FsmBaseClass, FsmEventKey, FsmProcess, FsmProcessDump, FsmProcessDumpHandler, FsmProcessHandler, FsmState, FsmStateConfig, FsmStateDescriptor, FsmStateDump, FsmStateDumpHandler, FsmStateErrorHandler, FsmStateHandler, FsmStateKey, KEY_DISPATCH, KEY_EVENT, KEY_STATES, KEY_TERMINATE, Printer, PrinterConfig, ProcessHandle, STATE_ANY, STATE_FINAL, STATE_INITIAL, STATUS_ENTER, STATUS_EXIT, STATUS_FINISHED, STATUS_FIRST, STATUS_LAST, STATUS_LEAF, STATUS_NEXT, STATUS_NONE, StageHandler, bindMethods, getPrinter, getProcessPrinter, getStateTransitions, isStateTransitionEnabled, preparePrinter, setProcessPrinter, setProcessTracer, setStateTracer, startFsmProcess, startProcess };
|
|
353
|
+
//# sourceMappingURL=index.d.ts.map
|