machina 4.0.2 → 6.0.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/LICENSE +1 -1
- package/README.md +198 -415
- package/dist/index.d.mts +628 -0
- package/dist/index.d.ts +628 -0
- package/dist/index.js +656 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +651 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +28 -149
- package/changelog.md +0 -190
- package/lib/machina.js +0 -744
- package/lib/machina.min.js +0 -2
- package/lib/machina.min.js.map +0 -1
- package/lib/machina_OLD.js +0 -612
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returned by `on()`. Call `off()` to remove the subscription.
|
|
3
|
+
* Safe to call multiple times — subsequent calls are no-ops because
|
|
4
|
+
* the underlying Set deduplicates membership.
|
|
5
|
+
*/
|
|
6
|
+
type Subscription = {
|
|
7
|
+
off(): void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Keys on a state object that have special meaning and are NOT input names.
|
|
12
|
+
* Used by InputNamesOf to filter these out when collecting input names.
|
|
13
|
+
*/
|
|
14
|
+
type SpecialStateKeys = "_onEnter" | "_onExit" | "_child" | "*";
|
|
15
|
+
/**
|
|
16
|
+
* Extracts state names as a string literal union from a states config object.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* type S = StateNamesOf<{ green: {...}, yellow: {...}, red: {...} }>;
|
|
21
|
+
* // => "green" | "yellow" | "red"
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
type StateNamesOf<TStates> = keyof TStates & string;
|
|
25
|
+
/**
|
|
26
|
+
* Extracts input names as a string literal union from a states config object.
|
|
27
|
+
* Collects all handler keys across ALL states, then strips out lifecycle hooks
|
|
28
|
+
* and special keys (_onEnter, _onExit, _child, *).
|
|
29
|
+
*
|
|
30
|
+
* This is what flows into `handle(inputName)` to provide compile-time
|
|
31
|
+
* validation of input names.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* type I = InputNamesOf<{
|
|
36
|
+
* idle: { start: "running", reset: fn };
|
|
37
|
+
* running: { pause: "paused", stop: "idle" };
|
|
38
|
+
* }>;
|
|
39
|
+
* // => "start" | "reset" | "pause" | "stop"
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* How it works:
|
|
43
|
+
* 1. `{ [S in keyof TStates]: keyof TStates[S] & string }` — maps each state
|
|
44
|
+
* to the union of its handler key names
|
|
45
|
+
* 2. `[keyof TStates]` — collapses the mapped type into a flat union of ALL
|
|
46
|
+
* handler keys across every state
|
|
47
|
+
* 3. `Exclude<..., SpecialStateKeys>` — strips lifecycle/special keys
|
|
48
|
+
*/
|
|
49
|
+
type InputNamesOf<TStates> = Exclude<{
|
|
50
|
+
[S in keyof TStates]: keyof TStates[S] & string;
|
|
51
|
+
}[keyof TStates], SpecialStateKeys>;
|
|
52
|
+
/**
|
|
53
|
+
* The single combined object passed to every handler.
|
|
54
|
+
*
|
|
55
|
+
* Handlers return a state name to transition, or void to stay put.
|
|
56
|
+
* This replaces imperative `transition()` calls — closer to gen_fsm's
|
|
57
|
+
* return-based model and symmetrical with string shorthand handlers.
|
|
58
|
+
*
|
|
59
|
+
* @typeParam TCtx - The context type. For Fsm this is the config-defined
|
|
60
|
+
* context object. For BehavioralFsm this is the client object itself.
|
|
61
|
+
* @typeParam TStateNames - String literal union of valid state names.
|
|
62
|
+
* Defaults to `string` for loose usage; the factory functions narrow this
|
|
63
|
+
* to the actual state names inferred from the config.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* // Conditional transition — return the target state:
|
|
68
|
+
* timeout({ ctx }) {
|
|
69
|
+
* if (ctx.tickCount >= 3) return "yellow";
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* // Side effects without transition — return nothing:
|
|
73
|
+
* tick({ ctx }) {
|
|
74
|
+
* ctx.tickCount++;
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* // In a catch-all — inputName tells you what was received:
|
|
78
|
+
* "*"({ inputName }) {
|
|
79
|
+
* console.log(`unhandled input: ${inputName}`);
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
interface HandlerArgs<TCtx, TStateNames extends string = string> {
|
|
84
|
+
/** The context (Fsm) or client object (BehavioralFsm) */
|
|
85
|
+
ctx: TCtx;
|
|
86
|
+
/**
|
|
87
|
+
* The name of the input currently being handled.
|
|
88
|
+
*
|
|
89
|
+
* Typed as `string` rather than the inferred input union because:
|
|
90
|
+
* 1. Inside a named handler you already know the input name
|
|
91
|
+
* 2. In a catch-all (*) handler it could be anything
|
|
92
|
+
* 3. Narrowing to the literal per-handler would require complex
|
|
93
|
+
* mapped types for zero practical benefit
|
|
94
|
+
*/
|
|
95
|
+
inputName: string;
|
|
96
|
+
/**
|
|
97
|
+
* Defer the current input for replay after a future transition.
|
|
98
|
+
* Erlang's selective receive, in JS form.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* // Replay on the next transition to any state
|
|
103
|
+
* defer();
|
|
104
|
+
*
|
|
105
|
+
* // Replay only when entering "yellow"
|
|
106
|
+
* defer({ until: "yellow" });
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
defer(opts?: {
|
|
110
|
+
until: TStateNames;
|
|
111
|
+
}): void;
|
|
112
|
+
/**
|
|
113
|
+
* Emit a custom event through the FSM's emitter.
|
|
114
|
+
* Built-in events (transitioning, transitioned, etc.) are emitted
|
|
115
|
+
* automatically by the FSM engine — this is for user-defined events.
|
|
116
|
+
*/
|
|
117
|
+
emit(eventName: string, data?: unknown): void;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A function handler for state inputs, lifecycle hooks (_onEnter, _onExit),
|
|
121
|
+
* and catch-all (*) handlers.
|
|
122
|
+
*
|
|
123
|
+
* **Return value determines transition:**
|
|
124
|
+
* - Return a valid state name → FSM transitions to that state
|
|
125
|
+
* - Return void/undefined → FSM stays in the current state
|
|
126
|
+
*
|
|
127
|
+
* This mirrors gen_fsm's `{next_state, StateName, NewStateData}` return.
|
|
128
|
+
* Guards are just `if` statements. Actions are just code before the return.
|
|
129
|
+
*
|
|
130
|
+
* The `...extra` rest parameter captures additional arguments passed through
|
|
131
|
+
* `handle(inputName, ...extraArgs)`. These are untyped (`unknown[]`) because
|
|
132
|
+
* correlating per-input arg types with handle() call sites would require
|
|
133
|
+
* prohibitively complex mapped types for minimal benefit.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // Side effects only, no transition:
|
|
138
|
+
* tick({ ctx }) { ctx.tickCount++; }
|
|
139
|
+
*
|
|
140
|
+
* // Conditional transition (replaces guard + target):
|
|
141
|
+
* timeout({ ctx }) {
|
|
142
|
+
* if (ctx.tickCount >= 3) return "yellow";
|
|
143
|
+
* }
|
|
144
|
+
*
|
|
145
|
+
* // Unconditional transition with side effect (replaces action + target):
|
|
146
|
+
* timeout({ ctx }) {
|
|
147
|
+
* console.log("transitioning after", ctx.tickCount, "ticks");
|
|
148
|
+
* return "yellow";
|
|
149
|
+
* }
|
|
150
|
+
*
|
|
151
|
+
* // Handler with extra args passed via handle("success", responseData):
|
|
152
|
+
* success({ ctx }, data) { ctx.result = data; }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
type HandlerFn<TCtx, TStateNames extends string = string> = (args: HandlerArgs<TCtx, TStateNames>, ...extra: unknown[]) => TStateNames | void;
|
|
156
|
+
/**
|
|
157
|
+
* The union of valid handler definition forms for a state input.
|
|
158
|
+
*
|
|
159
|
+
* - `TStateNames` — string shorthand, auto-transitions to that state
|
|
160
|
+
* - `HandlerFn` — function that returns a state name (transition) or void (stay)
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* states: {
|
|
165
|
+
* green: {
|
|
166
|
+
* timeout: "yellow", // string shorthand
|
|
167
|
+
* tick({ ctx }) { ctx.tickCount++; }, // function, no transition
|
|
168
|
+
* emergency({ ctx }) { // function, conditional transition
|
|
169
|
+
* if (ctx.severity > 5) return "red";
|
|
170
|
+
* },
|
|
171
|
+
* },
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
/**
|
|
176
|
+
* The union of valid handler definition forms for a state input.
|
|
177
|
+
*
|
|
178
|
+
* - `TStateNames` — string shorthand, auto-transitions to that state
|
|
179
|
+
* - `HandlerFn` — function that returns a state name (transition) or void (stay)
|
|
180
|
+
* - `MachinaInstance` — included to satisfy TypeScript's structural widening
|
|
181
|
+
* when `ValidateStates` falls back to its constraint type. The per-key
|
|
182
|
+
* restriction on `_child` is still enforced by `ValidateStates`; this
|
|
183
|
+
* union member just prevents the inference engine from rejecting
|
|
184
|
+
* `_child: childFsm` before the conditional mapping can evaluate it.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* states: {
|
|
189
|
+
* green: {
|
|
190
|
+
* timeout: "yellow", // string shorthand
|
|
191
|
+
* tick({ ctx }) { ctx.tickCount++; }, // function, no transition
|
|
192
|
+
* emergency({ ctx }) { // function, conditional transition
|
|
193
|
+
* if (ctx.severity > 5) return "red";
|
|
194
|
+
* },
|
|
195
|
+
* },
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
type HandlerDef<TCtx, TStateNames extends string = string> = TStateNames | HandlerFn<TCtx, TStateNames> | MachinaInstance;
|
|
200
|
+
/**
|
|
201
|
+
* Validates and constrains the states object at the type level.
|
|
202
|
+
*
|
|
203
|
+
* This is a mapped type that walks every state and every property within
|
|
204
|
+
* each state, assigning the correct expected type based on the property key:
|
|
205
|
+
*
|
|
206
|
+
* | Key | Expected type |
|
|
207
|
+
* |------------------|-----------------------------------------|
|
|
208
|
+
* | `_onEnter` | HandlerFn (lifecycle hook) |
|
|
209
|
+
* | `_onExit` | HandlerFn (lifecycle hook) |
|
|
210
|
+
* | `_child` | MachinaInstance (Fsm or BehavioralFsm) |
|
|
211
|
+
* | `*` | HandlerFn (catch-all) |
|
|
212
|
+
* | anything else | HandlerDef (string or fn) |
|
|
213
|
+
*
|
|
214
|
+
* @typeParam TCtx - Context/client type, flows into handler signatures
|
|
215
|
+
* @typeParam TStates - The literal states object type captured by the
|
|
216
|
+
* factory function's generic parameter
|
|
217
|
+
*/
|
|
218
|
+
type ValidateStates<TCtx, TStates extends Record<string, Record<string, unknown>>> = {
|
|
219
|
+
[S in keyof TStates]: {
|
|
220
|
+
[K in keyof TStates[S]]: K extends "_onEnter" | "_onExit" ? HandlerFn<TCtx, keyof TStates & string> : K extends "_child" ? MachinaInstance : K extends "*" ? HandlerFn<TCtx, keyof TStates & string> : HandlerDef<TCtx, keyof TStates & string>;
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Configuration object for creating an FSM.
|
|
225
|
+
*
|
|
226
|
+
* @typeParam TCtx - The context type (Fsm) or client type (BehavioralFsm).
|
|
227
|
+
* For Fsm, this is inferred from the `context` property. For BehavioralFsm,
|
|
228
|
+
* it's the client object type provided explicitly or as a generic parameter.
|
|
229
|
+
*
|
|
230
|
+
* @typeParam TStates - The literal states object type. Captured by the factory
|
|
231
|
+
* function's generic parameter (ideally with `const` to preserve string
|
|
232
|
+
* literal types). Defaults to a loose record for unconstrained usage.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* // TCtx inferred as { tickCount: number }, TStates inferred from states object:
|
|
237
|
+
* createFsm({
|
|
238
|
+
* id: "traffic-light",
|
|
239
|
+
* initialState: "green", // validated against state keys
|
|
240
|
+
* context: { tickCount: 0 }, // inference site for TCtx
|
|
241
|
+
* states: {
|
|
242
|
+
* green: { timeout: "yellow" }, // "yellow" validated against state keys
|
|
243
|
+
* yellow: { timeout: "red" },
|
|
244
|
+
* red: { timeout: "green" },
|
|
245
|
+
* },
|
|
246
|
+
* });
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
interface FsmConfig<TCtx, TStates extends Record<string, Record<string, unknown>> = Record<string, Record<string, unknown>>> {
|
|
250
|
+
/** Unique identifier for this FSM */
|
|
251
|
+
id: string;
|
|
252
|
+
/**
|
|
253
|
+
* The state to start in. Must be a key of `states`.
|
|
254
|
+
*
|
|
255
|
+
* Wrapped in NoInfer to prevent TypeScript from using this value as an
|
|
256
|
+
* inference site for TStates. Without it, `initialState: "green"` could
|
|
257
|
+
* narrow TStates to only have a "green" key. We want inference to come
|
|
258
|
+
* exclusively from the `states` property.
|
|
259
|
+
*/
|
|
260
|
+
initialState: NoInfer<keyof TStates & string>;
|
|
261
|
+
/**
|
|
262
|
+
* Initial context data. The type is inferred from this value and flows
|
|
263
|
+
* into every handler's `ctx` parameter.
|
|
264
|
+
*
|
|
265
|
+
* For BehavioralFsm, this property is optional and serves only as a
|
|
266
|
+
* type constraint — the client object IS the context.
|
|
267
|
+
*/
|
|
268
|
+
context?: TCtx;
|
|
269
|
+
/** State definitions. Keys become the state name union. */
|
|
270
|
+
states: ValidateStates<TCtx, TStates>;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Symbol used as a property key to identify machina FSM instances at runtime.
|
|
274
|
+
* Each class stamps itself with a MachinaType value so the ChildLink adapter
|
|
275
|
+
* can dispatch handle()/canHandle()/reset() correctly without circular imports.
|
|
276
|
+
*/
|
|
277
|
+
declare const MACHINA_TYPE: unique symbol;
|
|
278
|
+
/**
|
|
279
|
+
* Discriminant values stamped onto FSM instances via `MACHINA_TYPE`.
|
|
280
|
+
* Used by the `ChildLink` adapter to dispatch calls correctly without
|
|
281
|
+
* importing either class directly (which would create circular dependencies).
|
|
282
|
+
*/
|
|
283
|
+
type MachinaType = "Fsm" | "BehavioralFsm";
|
|
284
|
+
/** Structural type matching any machina FSM instance (Fsm or BehavioralFsm) */
|
|
285
|
+
type MachinaInstance = {
|
|
286
|
+
readonly [MACHINA_TYPE]: MachinaType;
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* Options for FSM disposal.
|
|
290
|
+
*/
|
|
291
|
+
interface DisposeOptions {
|
|
292
|
+
/**
|
|
293
|
+
* When true, child FSMs declared via _child are NOT disposed.
|
|
294
|
+
* Default: false (children ARE disposed along with the parent).
|
|
295
|
+
*/
|
|
296
|
+
preserveChildren?: boolean;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Built-in event map for Fsm instances.
|
|
300
|
+
* Payloads do NOT include a client reference (Fsm is its own client).
|
|
301
|
+
*
|
|
302
|
+
* @typeParam TStateNames - The state name union, flows into transition
|
|
303
|
+
* event payloads so fromState/toState are narrowed to actual state names.
|
|
304
|
+
*/
|
|
305
|
+
interface FsmEventMap<TStateNames extends string = string> {
|
|
306
|
+
/** Fired just before a state transition occurs */
|
|
307
|
+
transitioning: {
|
|
308
|
+
fromState: TStateNames;
|
|
309
|
+
toState: TStateNames;
|
|
310
|
+
};
|
|
311
|
+
/** Fired just after a state transition completes */
|
|
312
|
+
transitioned: {
|
|
313
|
+
fromState: TStateNames;
|
|
314
|
+
toState: TStateNames;
|
|
315
|
+
};
|
|
316
|
+
/** Fired when an input is about to be dispatched to a handler */
|
|
317
|
+
handling: {
|
|
318
|
+
inputName: string;
|
|
319
|
+
};
|
|
320
|
+
/** Fired after an input has been successfully handled */
|
|
321
|
+
handled: {
|
|
322
|
+
inputName: string;
|
|
323
|
+
};
|
|
324
|
+
/** Fired when an input has no matching handler in the current state */
|
|
325
|
+
nohandler: {
|
|
326
|
+
inputName: string;
|
|
327
|
+
args: unknown[];
|
|
328
|
+
};
|
|
329
|
+
/** Fired when a transition targets a state that doesn't exist */
|
|
330
|
+
invalidstate: {
|
|
331
|
+
stateName: string;
|
|
332
|
+
};
|
|
333
|
+
/** Fired when an input is deferred for later replay */
|
|
334
|
+
deferred: {
|
|
335
|
+
inputName: string;
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Built-in event map for BehavioralFsm instances.
|
|
340
|
+
* Every payload is intersected with `{ client: TClient }` so subscribers
|
|
341
|
+
* can identify which client the event pertains to.
|
|
342
|
+
*
|
|
343
|
+
* @typeParam TClient - The client object type
|
|
344
|
+
* @typeParam TStateNames - The state name union
|
|
345
|
+
*/
|
|
346
|
+
type BehavioralFsmEventMap<TClient, TStateNames extends string = string> = {
|
|
347
|
+
[K in keyof FsmEventMap<TStateNames>]: FsmEventMap<TStateNames>[K] & {
|
|
348
|
+
client: TClient;
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Single-client FSM. Wraps a BehavioralFsm and uses the config's `context`
|
|
354
|
+
* object as the implicit client, so callers never pass a client argument.
|
|
355
|
+
*
|
|
356
|
+
* Prefer `createFsm()` over constructing this directly — the factory infers
|
|
357
|
+
* all generic parameters from the config object.
|
|
358
|
+
*
|
|
359
|
+
* All public methods silently no-op after `dispose()` is called.
|
|
360
|
+
*
|
|
361
|
+
* @typeParam TCtx - The context type, inferred from `config.context`.
|
|
362
|
+
* @typeParam TStateNames - String literal union of valid state names.
|
|
363
|
+
* @typeParam TInputNames - String literal union of valid input names.
|
|
364
|
+
*/
|
|
365
|
+
declare class Fsm<TCtx extends object, TStateNames extends string, TInputNames extends string> {
|
|
366
|
+
readonly id: string;
|
|
367
|
+
readonly [MACHINA_TYPE]: "Fsm";
|
|
368
|
+
private readonly bfsm;
|
|
369
|
+
private readonly context;
|
|
370
|
+
private readonly emitter;
|
|
371
|
+
private disposed;
|
|
372
|
+
constructor(config: FsmConfig<TCtx, Record<string, Record<string, unknown>>>);
|
|
373
|
+
/**
|
|
374
|
+
* Dispatch an input to the current state's handler.
|
|
375
|
+
* If a `_child` FSM in the current state can handle it, delegation occurs
|
|
376
|
+
* there first; unhandled inputs bubble up to the parent.
|
|
377
|
+
* No-ops silently when disposed.
|
|
378
|
+
*/
|
|
379
|
+
handle(inputName: TInputNames, ...args: unknown[]): void;
|
|
380
|
+
/**
|
|
381
|
+
* Returns true if the current state has a handler for `inputName`
|
|
382
|
+
* (or a catch-all `"*"` handler). Does not trigger initialization
|
|
383
|
+
* or any side effects. Returns false when disposed.
|
|
384
|
+
*/
|
|
385
|
+
canHandle(inputName: string): boolean;
|
|
386
|
+
/**
|
|
387
|
+
* Transition back to `initialState`, firing `_onEnter` and lifecycle
|
|
388
|
+
* events as if entering it fresh. No-ops silently when disposed.
|
|
389
|
+
*/
|
|
390
|
+
reset(): void;
|
|
391
|
+
/**
|
|
392
|
+
* Returns the current state name. Always defined — Fsm eagerly
|
|
393
|
+
* initializes into `initialState` during construction.
|
|
394
|
+
*/
|
|
395
|
+
currentState(): TStateNames;
|
|
396
|
+
/**
|
|
397
|
+
* Directly transition to `toState`, firing `_onExit`, `_onEnter`, and
|
|
398
|
+
* lifecycle events. Same-state transitions are silently ignored.
|
|
399
|
+
* No-ops when disposed.
|
|
400
|
+
*/
|
|
401
|
+
transition(toState: TStateNames): void;
|
|
402
|
+
/**
|
|
403
|
+
* Returns the current state as a dot-delimited path that includes
|
|
404
|
+
* any active child FSM states (e.g. `"active.connecting.retrying"`).
|
|
405
|
+
* Returns just the current state name when no child is active.
|
|
406
|
+
*/
|
|
407
|
+
compositeState(): string;
|
|
408
|
+
/**
|
|
409
|
+
* Subscribe to a built-in lifecycle event or the wildcard.
|
|
410
|
+
*
|
|
411
|
+
* Named overload: typed payload, no event name in callback.
|
|
412
|
+
* Wildcard (`"*"`): receives `(eventName, data)` for every event.
|
|
413
|
+
* Returns a no-op `Subscription` when disposed.
|
|
414
|
+
*/
|
|
415
|
+
on<K extends keyof FsmEventMap<TStateNames> & string>(eventName: K, callback: (data: FsmEventMap<TStateNames>[K]) => void): Subscription;
|
|
416
|
+
on(eventName: "*", callback: (eventName: string, data: unknown) => void): Subscription;
|
|
417
|
+
/**
|
|
418
|
+
* Emit a custom event through the FSM. Built-in lifecycle events are
|
|
419
|
+
* emitted automatically — this is for user-defined events from handlers.
|
|
420
|
+
* Routes through the BehavioralFsm so all relay paths are consistent.
|
|
421
|
+
* No-ops when disposed.
|
|
422
|
+
*/
|
|
423
|
+
emit(eventName: string, data?: unknown): void;
|
|
424
|
+
/**
|
|
425
|
+
* Permanently shut down this FSM. Irreversible — all subsequent method
|
|
426
|
+
* calls become silent no-ops. Clears all listeners and cascades disposal
|
|
427
|
+
* to child FSMs (unless `preserveChildren` is set).
|
|
428
|
+
*/
|
|
429
|
+
dispose(options?: DisposeOptions): void;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Create a single-client FSM from a config object.
|
|
433
|
+
*
|
|
434
|
+
* Generic parameters are inferred automatically:
|
|
435
|
+
* - `TCtx` comes from `config.context` (defaults to `{}` if omitted).
|
|
436
|
+
* - `TStates` is captured with `const` inference to preserve string literal
|
|
437
|
+
* types, enabling compile-time validation of transition targets and `handle()`
|
|
438
|
+
* input names.
|
|
439
|
+
*
|
|
440
|
+
* State names, input names, and all handler signatures derive from `TStates`.
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* ```ts
|
|
444
|
+
* const light = createFsm({
|
|
445
|
+
* id: "traffic-light",
|
|
446
|
+
* initialState: "green",
|
|
447
|
+
* context: { tickCount: 0 },
|
|
448
|
+
* states: {
|
|
449
|
+
* green: { timeout: "yellow" },
|
|
450
|
+
* yellow: { timeout: "red" },
|
|
451
|
+
* red: { timeout: "green" },
|
|
452
|
+
* },
|
|
453
|
+
* });
|
|
454
|
+
*
|
|
455
|
+
* light.handle("timeout"); // transitions green → yellow
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
declare function createFsm<TCtx extends object = Record<string, never>, const TStates extends Record<string, Record<string, unknown>> = Record<string, Record<string, unknown>>>(config: FsmConfig<TCtx, TStates>): Fsm<TCtx, StateNamesOf<TStates>, InputNamesOf<TStates>>;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Defines FSM behavior (states + transitions) while tracking per-client state
|
|
462
|
+
* in a `WeakMap`. A single `BehavioralFsm` instance can drive any number of
|
|
463
|
+
* independent client objects simultaneously — each gets its own state,
|
|
464
|
+
* deferred queue, and lifecycle.
|
|
465
|
+
*
|
|
466
|
+
* Prefer `createBehavioralFsm()` over constructing this directly — the factory
|
|
467
|
+
* infers all generic parameters from the config object.
|
|
468
|
+
*
|
|
469
|
+
* All public methods silently no-op after `dispose()` is called.
|
|
470
|
+
*
|
|
471
|
+
* @typeParam TClient - The client object type. Must be an object (non-primitive)
|
|
472
|
+
* so it can serve as a WeakMap key.
|
|
473
|
+
* @typeParam TStateNames - String literal union of valid state names.
|
|
474
|
+
* @typeParam TInputNames - String literal union of valid input names.
|
|
475
|
+
*/
|
|
476
|
+
declare class BehavioralFsm<TClient extends object, TStateNames extends string, TInputNames extends string> {
|
|
477
|
+
readonly id: string;
|
|
478
|
+
readonly initialState: TStateNames;
|
|
479
|
+
readonly [MACHINA_TYPE]: "BehavioralFsm";
|
|
480
|
+
private readonly states;
|
|
481
|
+
private readonly emitter;
|
|
482
|
+
private readonly clients;
|
|
483
|
+
private readonly knownClients;
|
|
484
|
+
private readonly childSubscriptions;
|
|
485
|
+
private disposed;
|
|
486
|
+
private transitionDepth;
|
|
487
|
+
constructor(config: FsmConfig<TClient, Record<string, Record<string, unknown>>>);
|
|
488
|
+
/**
|
|
489
|
+
* Dispatch an input to the given client's current state handler.
|
|
490
|
+
*
|
|
491
|
+
* Delegation order: if the current state has a `_child` FSM that can
|
|
492
|
+
* handle the input, it is dispatched there. If the child emits `nohandler`,
|
|
493
|
+
* the input bubbles up to this FSM's local handler. If no handler exists
|
|
494
|
+
* here either, `nohandler` is emitted on this FSM's emitter.
|
|
495
|
+
*
|
|
496
|
+
* No-ops silently when disposed.
|
|
497
|
+
*/
|
|
498
|
+
handle(client: TClient, inputName: TInputNames, ...args: unknown[]): void;
|
|
499
|
+
/**
|
|
500
|
+
* Returns true if the client's current state has a handler for `inputName`
|
|
501
|
+
* (or a catch-all `"*"` handler). Does NOT initialize the client — no
|
|
502
|
+
* `_onEnter`, no events, no side effects. Unseen clients are treated as
|
|
503
|
+
* if they were already in `initialState`. Returns false when disposed.
|
|
504
|
+
*/
|
|
505
|
+
canHandle(client: TClient, inputName: string): boolean;
|
|
506
|
+
/**
|
|
507
|
+
* Transition the client back to `initialState`, firing `_onEnter` and
|
|
508
|
+
* lifecycle events as if entering it fresh. No-ops when disposed.
|
|
509
|
+
*/
|
|
510
|
+
reset(client: TClient): void;
|
|
511
|
+
/**
|
|
512
|
+
* Returns the client's current state, or `undefined` if the client has
|
|
513
|
+
* never been initialized (i.e. `handle()`, `transition()`, or `reset()`
|
|
514
|
+
* have never been called for it). Does NOT trigger initialization.
|
|
515
|
+
*/
|
|
516
|
+
currentState(client: TClient): TStateNames | undefined;
|
|
517
|
+
/**
|
|
518
|
+
* Directly transition `client` to `toState`, running the full lifecycle:
|
|
519
|
+
* `_onExit` for the current state → `transitioning` event → update state →
|
|
520
|
+
* `_onEnter` for new state → `transitioned` event → child reset → deferred
|
|
521
|
+
* queue replay → bounce (if `_onEnter` returned a state name).
|
|
522
|
+
*
|
|
523
|
+
* Same-state transitions are silently ignored. Transitions to unknown state
|
|
524
|
+
* names emit `invalidstate` instead of throwing. Throws if the transition
|
|
525
|
+
* depth exceeds `MAX_TRANSITION_DEPTH` (likely an `_onEnter` → transition loop).
|
|
526
|
+
*
|
|
527
|
+
* No-ops when disposed.
|
|
528
|
+
*/
|
|
529
|
+
transition(client: TClient, toState: TStateNames): void;
|
|
530
|
+
/**
|
|
531
|
+
* Returns the client's state as a dot-delimited path including any active
|
|
532
|
+
* child FSM states (e.g. `"active.connecting.retrying"`). Returns just the
|
|
533
|
+
* current state name when no child is active. Returns `""` for clients that
|
|
534
|
+
* have never been initialized (unlike `currentState()` which returns `undefined`).
|
|
535
|
+
*/
|
|
536
|
+
compositeState(client: TClient): string;
|
|
537
|
+
/**
|
|
538
|
+
* Subscribe to a built-in lifecycle event or the wildcard.
|
|
539
|
+
*
|
|
540
|
+
* Named overload: typed payload includes `{ client: TClient }` so you can
|
|
541
|
+
* identify which client the event pertains to. Wildcard (`"*"`) receives
|
|
542
|
+
* `(eventName, data)` for every event. Returns a no-op `Subscription`
|
|
543
|
+
* when disposed.
|
|
544
|
+
*/
|
|
545
|
+
on<K extends keyof BehavioralFsmEventMap<TClient, TStateNames> & string>(eventName: K, callback: (data: BehavioralFsmEventMap<TClient, TStateNames>[K]) => void): Subscription;
|
|
546
|
+
on(eventName: "*", callback: (eventName: string, data: unknown) => void): Subscription;
|
|
547
|
+
/**
|
|
548
|
+
* Emit a custom event through the FSM. Built-in lifecycle events are
|
|
549
|
+
* emitted automatically — this is for user-defined events from handlers.
|
|
550
|
+
* No-ops when disposed.
|
|
551
|
+
*/
|
|
552
|
+
emit(eventName: string, data?: unknown): void;
|
|
553
|
+
/**
|
|
554
|
+
* Permanently shut down this FSM. Irreversible — all subsequent method
|
|
555
|
+
* calls become silent no-ops. Tears down child subscriptions, clears all
|
|
556
|
+
* listeners, and cascades disposal to child FSMs (unless `preserveChildren`
|
|
557
|
+
* is set). The same child appearing in multiple states is disposed once.
|
|
558
|
+
*/
|
|
559
|
+
dispose(options?: DisposeOptions): void;
|
|
560
|
+
/**
|
|
561
|
+
* Walks all states at construction time, detects raw FSM instances assigned
|
|
562
|
+
* to _child, and wraps them into ChildLink adapters via createChildLink().
|
|
563
|
+
* Must run BEFORE setupChildSubscriptions() so the subscriptions see
|
|
564
|
+
* ChildLink objects, not raw FSM instances.
|
|
565
|
+
*/
|
|
566
|
+
private wrapChildLinks;
|
|
567
|
+
/**
|
|
568
|
+
* Walks all states at construction time, finds states with _child, and
|
|
569
|
+
* subscribes once to each unique child's wildcard events. Subscriptions are
|
|
570
|
+
* stored for cleanup in dispose(). We deduplicate by child reference to
|
|
571
|
+
* avoid double-subscribing when the same child appears in multiple states.
|
|
572
|
+
*/
|
|
573
|
+
private setupChildSubscriptions;
|
|
574
|
+
/**
|
|
575
|
+
* Bubbles a child nohandler to the parent for the given client.
|
|
576
|
+
* Only fires if the client is currently in a state that has this childLink.
|
|
577
|
+
* Extracted from the lambda in setupChildSubscriptions to keep it readable.
|
|
578
|
+
*/
|
|
579
|
+
private bubbleNohandler;
|
|
580
|
+
/**
|
|
581
|
+
* Returns true if the given client is currently in a parent state whose
|
|
582
|
+
* _child is the specified childLink. Returns false if the client has no
|
|
583
|
+
* meta (never initialized) or is in a state with a different (or no) child.
|
|
584
|
+
*/
|
|
585
|
+
private isChildActiveForClient;
|
|
586
|
+
/**
|
|
587
|
+
* The inner handler dispatch — no delegation, no initialization side effects
|
|
588
|
+
* beyond what getOrCreateClientMeta already did. Called by handle() after
|
|
589
|
+
* the delegation check, and by the nohandler child listener for bubbling.
|
|
590
|
+
*/
|
|
591
|
+
private handleLocally;
|
|
592
|
+
private getOrCreateClientMeta;
|
|
593
|
+
private buildHandlerArgs;
|
|
594
|
+
private processQueue;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Create a behavioral FSM (one definition, many clients) from a config object.
|
|
598
|
+
*
|
|
599
|
+
* Generic parameters are inferred automatically:
|
|
600
|
+
* - `TClient` must be provided explicitly as a type parameter (it can't be
|
|
601
|
+
* inferred from the config since no `context` property exists at the FSM level).
|
|
602
|
+
* - `TStates` is captured with `const` inference to preserve string literal
|
|
603
|
+
* types, enabling compile-time validation of transition targets and `handle()`
|
|
604
|
+
* input names.
|
|
605
|
+
*
|
|
606
|
+
* State names, input names, and all handler signatures derive from `TStates`.
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* interface Connection { url: string; retries: number; }
|
|
611
|
+
*
|
|
612
|
+
* const connFsm = createBehavioralFsm<Connection>({
|
|
613
|
+
* id: "connectivity",
|
|
614
|
+
* initialState: "disconnected",
|
|
615
|
+
* states: {
|
|
616
|
+
* disconnected: { connect: "connecting" },
|
|
617
|
+
* connecting: { connected: "online", failed: "disconnected" },
|
|
618
|
+
* online: { disconnect: "disconnected" },
|
|
619
|
+
* },
|
|
620
|
+
* });
|
|
621
|
+
*
|
|
622
|
+
* const conn = { url: "wss://example.com", retries: 0 };
|
|
623
|
+
* connFsm.handle(conn, "connect");
|
|
624
|
+
* ```
|
|
625
|
+
*/
|
|
626
|
+
declare function createBehavioralFsm<TClient extends object, const TStates extends Record<string, Record<string, unknown>>>(config: FsmConfig<TClient, TStates>): BehavioralFsm<TClient, StateNamesOf<TStates>, InputNamesOf<TStates>>;
|
|
627
|
+
|
|
628
|
+
export { BehavioralFsm, type BehavioralFsmEventMap, type DisposeOptions, Fsm, type FsmConfig, type FsmEventMap, type HandlerArgs, type HandlerDef, type HandlerFn, type InputNamesOf, type MachinaInstance, type StateNamesOf, type Subscription, createBehavioralFsm, createFsm };
|