machina 6.0.0 → 6.1.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.
@@ -0,0 +1,654 @@
1
+ //#region src/emitter.d.ts
2
+ /**
3
+ * Returned by `on()`. Call `off()` to remove the subscription.
4
+ * Safe to call multiple times — subsequent calls are no-ops because
5
+ * the underlying Set deduplicates membership.
6
+ */
7
+ type Subscription = {
8
+ off(): void;
9
+ };
10
+ //#endregion
11
+ //#region src/types.d.ts
12
+ /**
13
+ * Keys on a state object that have special meaning and are NOT input names.
14
+ * Used by InputNamesOf to filter these out when collecting input names.
15
+ */
16
+ type SpecialStateKeys = "_onEnter" | "_onExit" | "_child" | "*";
17
+ /**
18
+ * Extracts state names as a string literal union from a states config object.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * type S = StateNamesOf<{ green: {...}, yellow: {...}, red: {...} }>;
23
+ * // => "green" | "yellow" | "red"
24
+ * ```
25
+ */
26
+ type StateNamesOf<TStates> = keyof TStates & string;
27
+ /**
28
+ * Extracts input names as a string literal union from a states config object.
29
+ * Collects all handler keys across ALL states, then strips out lifecycle hooks
30
+ * and special keys (_onEnter, _onExit, _child, *).
31
+ *
32
+ * This is what flows into `handle(inputName)` to provide compile-time
33
+ * validation of input names.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * type I = InputNamesOf<{
38
+ * idle: { start: "running", reset: fn };
39
+ * running: { pause: "paused", stop: "idle" };
40
+ * }>;
41
+ * // => "start" | "reset" | "pause" | "stop"
42
+ * ```
43
+ *
44
+ * How it works:
45
+ * 1. `{ [S in keyof TStates]: keyof TStates[S] & string }` — maps each state
46
+ * to the union of its handler key names
47
+ * 2. `[keyof TStates]` — collapses the mapped type into a flat union of ALL
48
+ * handler keys across every state
49
+ * 3. `Exclude<..., SpecialStateKeys>` — strips lifecycle/special keys
50
+ */
51
+ type InputNamesOf<TStates> = Exclude<{ [S in keyof TStates]: keyof TStates[S] & string }[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>>> = { [S in keyof TStates]: { [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> } };
219
+ /**
220
+ * Configuration object for creating an FSM.
221
+ *
222
+ * @typeParam TCtx - The context type (Fsm) or client type (BehavioralFsm).
223
+ * For Fsm, this is inferred from the `context` property. For BehavioralFsm,
224
+ * it's the client object type provided explicitly or as a generic parameter.
225
+ *
226
+ * @typeParam TStates - The literal states object type. Captured by the factory
227
+ * function's generic parameter (ideally with `const` to preserve string
228
+ * literal types). Defaults to a loose record for unconstrained usage.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * // TCtx inferred as { tickCount: number }, TStates inferred from states object:
233
+ * createFsm({
234
+ * id: "traffic-light",
235
+ * initialState: "green", // validated against state keys
236
+ * context: { tickCount: 0 }, // inference site for TCtx
237
+ * states: {
238
+ * green: { timeout: "yellow" }, // "yellow" validated against state keys
239
+ * yellow: { timeout: "red" },
240
+ * red: { timeout: "green" },
241
+ * },
242
+ * });
243
+ * ```
244
+ */
245
+ interface FsmConfig<TCtx, TStates extends Record<string, Record<string, unknown>> = Record<string, Record<string, unknown>>> {
246
+ /** Unique identifier for this FSM */
247
+ id: string;
248
+ /**
249
+ * The state to start in. Must be a key of `states`.
250
+ *
251
+ * Wrapped in NoInfer to prevent TypeScript from using this value as an
252
+ * inference site for TStates. Without it, `initialState: "green"` could
253
+ * narrow TStates to only have a "green" key. We want inference to come
254
+ * exclusively from the `states` property.
255
+ */
256
+ initialState: NoInfer<keyof TStates & string>;
257
+ /**
258
+ * Initial context data. The type is inferred from this value and flows
259
+ * into every handler's `ctx` parameter.
260
+ *
261
+ * For BehavioralFsm, this property is optional and serves only as a
262
+ * type constraint — the client object IS the context.
263
+ */
264
+ context?: TCtx;
265
+ /** State definitions. Keys become the state name union. */
266
+ states: ValidateStates<TCtx, TStates>;
267
+ }
268
+ /**
269
+ * Symbol used as a property key to identify machina FSM instances at runtime.
270
+ * Each class stamps itself with a MachinaType value so the ChildLink adapter
271
+ * can dispatch handle()/canHandle()/reset() correctly without circular imports.
272
+ */
273
+ declare const MACHINA_TYPE: unique symbol;
274
+ /**
275
+ * Discriminant values stamped onto FSM instances via `MACHINA_TYPE`.
276
+ * Used by the `ChildLink` adapter to dispatch calls correctly without
277
+ * importing either class directly (which would create circular dependencies).
278
+ */
279
+ type MachinaType = "Fsm" | "BehavioralFsm";
280
+ /** Structural type matching any machina FSM instance (Fsm or BehavioralFsm) */
281
+ type MachinaInstance = {
282
+ readonly [MACHINA_TYPE]: MachinaType;
283
+ };
284
+ /**
285
+ * Internal adapter that wraps either an Fsm or BehavioralFsm child,
286
+ * presenting a uniform API for parent-initiated delegation.
287
+ */
288
+ interface ChildLink {
289
+ /** Check if the child's current state can handle this input */
290
+ canHandle(client: object, inputName: string): boolean;
291
+ /** Dispatch the input to the child */
292
+ handle(client: object, inputName: string, ...args: unknown[]): void;
293
+ /** Reset the child to its initialState */
294
+ reset(client: object): void;
295
+ /** Subscribe to all child events (wildcard). Returns unsubscribe fn. */
296
+ onAny(callback: (eventName: string, data: unknown) => void): {
297
+ off(): void;
298
+ };
299
+ /** The child FSM's compositeState for the given client */
300
+ compositeState(client: object): string;
301
+ /** Dispose the child FSM */
302
+ dispose(): void;
303
+ /**
304
+ * The raw Fsm or BehavioralFsm instance this ChildLink wraps.
305
+ * Exposed for inspection tooling (machina-inspect) — allows external
306
+ * tools to introspect child graph structure without reaching through
307
+ * private fields.
308
+ */
309
+ instance: MachinaInstance;
310
+ }
311
+ /**
312
+ * Options for FSM disposal.
313
+ */
314
+ interface DisposeOptions {
315
+ /**
316
+ * When true, child FSMs declared via _child are NOT disposed.
317
+ * Default: false (children ARE disposed along with the parent).
318
+ */
319
+ preserveChildren?: boolean;
320
+ }
321
+ /**
322
+ * Built-in event map for Fsm instances.
323
+ * Payloads do NOT include a client reference (Fsm is its own client).
324
+ *
325
+ * @typeParam TStateNames - The state name union, flows into transition
326
+ * event payloads so fromState/toState are narrowed to actual state names.
327
+ */
328
+ interface FsmEventMap<TStateNames extends string = string> {
329
+ /** Fired just before a state transition occurs */
330
+ transitioning: {
331
+ fromState: TStateNames;
332
+ toState: TStateNames;
333
+ };
334
+ /** Fired just after a state transition completes */
335
+ transitioned: {
336
+ fromState: TStateNames;
337
+ toState: TStateNames;
338
+ };
339
+ /** Fired when an input is about to be dispatched to a handler */
340
+ handling: {
341
+ inputName: string;
342
+ };
343
+ /** Fired after an input has been successfully handled */
344
+ handled: {
345
+ inputName: string;
346
+ };
347
+ /** Fired when an input has no matching handler in the current state */
348
+ nohandler: {
349
+ inputName: string;
350
+ args: unknown[];
351
+ };
352
+ /** Fired when a transition targets a state that doesn't exist */
353
+ invalidstate: {
354
+ stateName: string;
355
+ };
356
+ /** Fired when an input is deferred for later replay */
357
+ deferred: {
358
+ inputName: string;
359
+ };
360
+ }
361
+ /**
362
+ * Built-in event map for BehavioralFsm instances.
363
+ * Every payload is intersected with `{ client: TClient }` so subscribers
364
+ * can identify which client the event pertains to.
365
+ *
366
+ * @typeParam TClient - The client object type
367
+ * @typeParam TStateNames - The state name union
368
+ */
369
+ type BehavioralFsmEventMap<TClient, TStateNames extends string = string> = { [K in keyof FsmEventMap<TStateNames>]: FsmEventMap<TStateNames>[K] & {
370
+ client: TClient;
371
+ } };
372
+ //#endregion
373
+ //#region src/fsm.d.ts
374
+ /**
375
+ * Single-client FSM. Wraps a BehavioralFsm and uses the config's `context`
376
+ * object as the implicit client, so callers never pass a client argument.
377
+ *
378
+ * Prefer `createFsm()` over constructing this directly — the factory infers
379
+ * all generic parameters from the config object.
380
+ *
381
+ * All public methods silently no-op after `dispose()` is called.
382
+ *
383
+ * @typeParam TCtx - The context type, inferred from `config.context`.
384
+ * @typeParam TStateNames - String literal union of valid state names.
385
+ * @typeParam TInputNames - String literal union of valid input names.
386
+ */
387
+ declare class Fsm<TCtx extends object, TStateNames extends string, TInputNames extends string> {
388
+ readonly id: string;
389
+ readonly initialState: TStateNames;
390
+ readonly [MACHINA_TYPE]: "Fsm";
391
+ readonly states: Record<string, Record<string, unknown>>;
392
+ private readonly bfsm;
393
+ private readonly context;
394
+ private readonly emitter;
395
+ private disposed;
396
+ constructor(config: FsmConfig<TCtx, Record<string, Record<string, unknown>>>);
397
+ /**
398
+ * Dispatch an input to the current state's handler.
399
+ * If a `_child` FSM in the current state can handle it, delegation occurs
400
+ * there first; unhandled inputs bubble up to the parent.
401
+ * No-ops silently when disposed.
402
+ */
403
+ handle(inputName: TInputNames, ...args: unknown[]): void;
404
+ /**
405
+ * Returns true if the current state has a handler for `inputName`
406
+ * (or a catch-all `"*"` handler). Does not trigger initialization
407
+ * or any side effects. Returns false when disposed.
408
+ */
409
+ canHandle(inputName: string): boolean;
410
+ /**
411
+ * Transition back to `initialState`, firing `_onEnter` and lifecycle
412
+ * events as if entering it fresh. No-ops silently when disposed.
413
+ */
414
+ reset(): void;
415
+ /**
416
+ * Returns the current state name. Always defined — Fsm eagerly
417
+ * initializes into `initialState` during construction.
418
+ */
419
+ currentState(): TStateNames;
420
+ /**
421
+ * Directly transition to `toState`, firing `_onExit`, `_onEnter`, and
422
+ * lifecycle events. Same-state transitions are silently ignored.
423
+ * No-ops when disposed.
424
+ */
425
+ transition(toState: TStateNames): void;
426
+ /**
427
+ * Returns the current state as a dot-delimited path that includes
428
+ * any active child FSM states (e.g. `"active.connecting.retrying"`).
429
+ * Returns just the current state name when no child is active.
430
+ */
431
+ compositeState(): string;
432
+ /**
433
+ * Subscribe to a built-in lifecycle event or the wildcard.
434
+ *
435
+ * Named overload: typed payload, no event name in callback.
436
+ * Wildcard (`"*"`): receives `(eventName, data)` for every event.
437
+ * Returns a no-op `Subscription` when disposed.
438
+ */
439
+ on<K extends keyof FsmEventMap<TStateNames> & string>(eventName: K, callback: (data: FsmEventMap<TStateNames>[K]) => void): Subscription;
440
+ on(eventName: "*", callback: (eventName: string, data: unknown) => void): Subscription;
441
+ /**
442
+ * Emit a custom event through the FSM. Built-in lifecycle events are
443
+ * emitted automatically — this is for user-defined events from handlers.
444
+ * Routes through the BehavioralFsm so all relay paths are consistent.
445
+ * No-ops when disposed.
446
+ */
447
+ emit(eventName: string, data?: unknown): void;
448
+ /**
449
+ * Permanently shut down this FSM. Irreversible — all subsequent method
450
+ * calls become silent no-ops. Clears all listeners and cascades disposal
451
+ * to child FSMs (unless `preserveChildren` is set).
452
+ */
453
+ dispose(options?: DisposeOptions): void;
454
+ }
455
+ /**
456
+ * Create a single-client FSM from a config object.
457
+ *
458
+ * Generic parameters are inferred automatically:
459
+ * - `TCtx` comes from `config.context` (defaults to `{}` if omitted).
460
+ * - `TStates` is captured with `const` inference to preserve string literal
461
+ * types, enabling compile-time validation of transition targets and `handle()`
462
+ * input names.
463
+ *
464
+ * State names, input names, and all handler signatures derive from `TStates`.
465
+ *
466
+ * @example
467
+ * ```ts
468
+ * const light = createFsm({
469
+ * id: "traffic-light",
470
+ * initialState: "green",
471
+ * context: { tickCount: 0 },
472
+ * states: {
473
+ * green: { timeout: "yellow" },
474
+ * yellow: { timeout: "red" },
475
+ * red: { timeout: "green" },
476
+ * },
477
+ * });
478
+ *
479
+ * light.handle("timeout"); // transitions green → yellow
480
+ * ```
481
+ */
482
+ 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>>;
483
+ //#endregion
484
+ //#region src/behavioral-fsm.d.ts
485
+ /**
486
+ * Defines FSM behavior (states + transitions) while tracking per-client state
487
+ * in a `WeakMap`. A single `BehavioralFsm` instance can drive any number of
488
+ * independent client objects simultaneously — each gets its own state,
489
+ * deferred queue, and lifecycle.
490
+ *
491
+ * Prefer `createBehavioralFsm()` over constructing this directly — the factory
492
+ * infers all generic parameters from the config object.
493
+ *
494
+ * All public methods silently no-op after `dispose()` is called.
495
+ *
496
+ * @typeParam TClient - The client object type. Must be an object (non-primitive)
497
+ * so it can serve as a WeakMap key.
498
+ * @typeParam TStateNames - String literal union of valid state names.
499
+ * @typeParam TInputNames - String literal union of valid input names.
500
+ */
501
+ declare class BehavioralFsm<TClient extends object, TStateNames extends string, TInputNames extends string> {
502
+ readonly id: string;
503
+ readonly initialState: TStateNames;
504
+ readonly [MACHINA_TYPE]: "BehavioralFsm";
505
+ readonly states: Record<string, Record<string, unknown>>;
506
+ private readonly emitter;
507
+ private readonly clients;
508
+ private readonly knownClients;
509
+ private readonly childSubscriptions;
510
+ private disposed;
511
+ private transitionDepth;
512
+ constructor(config: FsmConfig<TClient, Record<string, Record<string, unknown>>>);
513
+ /**
514
+ * Dispatch an input to the given client's current state handler.
515
+ *
516
+ * Delegation order: if the current state has a `_child` FSM that can
517
+ * handle the input, it is dispatched there. If the child emits `nohandler`,
518
+ * the input bubbles up to this FSM's local handler. If no handler exists
519
+ * here either, `nohandler` is emitted on this FSM's emitter.
520
+ *
521
+ * No-ops silently when disposed.
522
+ */
523
+ handle(client: TClient, inputName: TInputNames, ...args: unknown[]): void;
524
+ /**
525
+ * Returns true if the client's current state has a handler for `inputName`
526
+ * (or a catch-all `"*"` handler). Does NOT initialize the client — no
527
+ * `_onEnter`, no events, no side effects. Unseen clients are treated as
528
+ * if they were already in `initialState`. Returns false when disposed.
529
+ */
530
+ canHandle(client: TClient, inputName: string): boolean;
531
+ /**
532
+ * Transition the client back to `initialState`, firing `_onEnter` and
533
+ * lifecycle events as if entering it fresh. No-ops when disposed.
534
+ */
535
+ reset(client: TClient): void;
536
+ /**
537
+ * Returns the client's current state, or `undefined` if the client has
538
+ * never been initialized (i.e. `handle()`, `transition()`, or `reset()`
539
+ * have never been called for it). Does NOT trigger initialization.
540
+ */
541
+ currentState(client: TClient): TStateNames | undefined;
542
+ /**
543
+ * Directly transition `client` to `toState`, running the full lifecycle:
544
+ * `_onExit` for the current state → `transitioning` event → update state →
545
+ * `_onEnter` for new state → `transitioned` event → child reset → deferred
546
+ * queue replay → bounce (if `_onEnter` returned a state name).
547
+ *
548
+ * Same-state transitions are silently ignored. Transitions to unknown state
549
+ * names emit `invalidstate` instead of throwing. Throws if the transition
550
+ * depth exceeds `MAX_TRANSITION_DEPTH` (likely an `_onEnter` → transition loop).
551
+ *
552
+ * No-ops when disposed.
553
+ */
554
+ transition(client: TClient, toState: TStateNames): void;
555
+ /**
556
+ * Returns the client's state as a dot-delimited path including any active
557
+ * child FSM states (e.g. `"active.connecting.retrying"`). Returns just the
558
+ * current state name when no child is active. Returns `""` for clients that
559
+ * have never been initialized (unlike `currentState()` which returns `undefined`).
560
+ */
561
+ compositeState(client: TClient): string;
562
+ /**
563
+ * Subscribe to a built-in lifecycle event or the wildcard.
564
+ *
565
+ * Named overload: typed payload includes `{ client: TClient }` so you can
566
+ * identify which client the event pertains to. Wildcard (`"*"`) receives
567
+ * `(eventName, data)` for every event. Returns a no-op `Subscription`
568
+ * when disposed.
569
+ */
570
+ on<K extends keyof BehavioralFsmEventMap<TClient, TStateNames> & string>(eventName: K, callback: (data: BehavioralFsmEventMap<TClient, TStateNames>[K]) => void): Subscription;
571
+ on(eventName: "*", callback: (eventName: string, data: unknown) => void): Subscription;
572
+ /**
573
+ * Emit a custom event through the FSM. Built-in lifecycle events are
574
+ * emitted automatically — this is for user-defined events from handlers.
575
+ * No-ops when disposed.
576
+ */
577
+ emit(eventName: string, data?: unknown): void;
578
+ /**
579
+ * Permanently shut down this FSM. Irreversible — all subsequent method
580
+ * calls become silent no-ops. Tears down child subscriptions, clears all
581
+ * listeners, and cascades disposal to child FSMs (unless `preserveChildren`
582
+ * is set). The same child appearing in multiple states is disposed once.
583
+ */
584
+ dispose(options?: DisposeOptions): void;
585
+ /**
586
+ * Walks all states at construction time, detects raw FSM instances assigned
587
+ * to _child, and wraps them into ChildLink adapters via createChildLink().
588
+ * Must run BEFORE setupChildSubscriptions() so the subscriptions see
589
+ * ChildLink objects, not raw FSM instances.
590
+ */
591
+ private wrapChildLinks;
592
+ /**
593
+ * Walks all states at construction time, finds states with _child, and
594
+ * subscribes once to each unique child's wildcard events. Subscriptions are
595
+ * stored for cleanup in dispose(). We deduplicate by child reference to
596
+ * avoid double-subscribing when the same child appears in multiple states.
597
+ */
598
+ private setupChildSubscriptions;
599
+ /**
600
+ * Bubbles a child nohandler to the parent for the given client.
601
+ * Only fires if the client is currently in a state that has this childLink.
602
+ * Extracted from the lambda in setupChildSubscriptions to keep it readable.
603
+ */
604
+ private bubbleNohandler;
605
+ /**
606
+ * Returns true if the given client is currently in a parent state whose
607
+ * _child is the specified childLink. Returns false if the client has no
608
+ * meta (never initialized) or is in a state with a different (or no) child.
609
+ */
610
+ private isChildActiveForClient;
611
+ /**
612
+ * The inner handler dispatch — no delegation, no initialization side effects
613
+ * beyond what getOrCreateClientMeta already did. Called by handle() after
614
+ * the delegation check, and by the nohandler child listener for bubbling.
615
+ */
616
+ private handleLocally;
617
+ private getOrCreateClientMeta;
618
+ private buildHandlerArgs;
619
+ private processQueue;
620
+ }
621
+ /**
622
+ * Create a behavioral FSM (one definition, many clients) from a config object.
623
+ *
624
+ * Generic parameters are inferred automatically:
625
+ * - `TClient` must be provided explicitly as a type parameter (it can't be
626
+ * inferred from the config since no `context` property exists at the FSM level).
627
+ * - `TStates` is captured with `const` inference to preserve string literal
628
+ * types, enabling compile-time validation of transition targets and `handle()`
629
+ * input names.
630
+ *
631
+ * State names, input names, and all handler signatures derive from `TStates`.
632
+ *
633
+ * @example
634
+ * ```ts
635
+ * interface Connection { url: string; retries: number; }
636
+ *
637
+ * const connFsm = createBehavioralFsm<Connection>({
638
+ * id: "connectivity",
639
+ * initialState: "disconnected",
640
+ * states: {
641
+ * disconnected: { connect: "connecting" },
642
+ * connecting: { connected: "online", failed: "disconnected" },
643
+ * online: { disconnect: "disconnected" },
644
+ * },
645
+ * });
646
+ *
647
+ * const conn = { url: "wss://example.com", retries: 0 };
648
+ * connFsm.handle(conn, "connect");
649
+ * ```
650
+ */
651
+ declare function createBehavioralFsm<TClient extends object, const TStates extends Record<string, Record<string, unknown>>>(config: FsmConfig<TClient, TStates>): BehavioralFsm<TClient, StateNamesOf<TStates>, InputNamesOf<TStates>>;
652
+ //#endregion
653
+ export { BehavioralFsm, type BehavioralFsmEventMap, type ChildLink, type DisposeOptions, Fsm, type FsmConfig, type FsmEventMap, type HandlerArgs, type HandlerDef, type HandlerFn, type InputNamesOf, MACHINA_TYPE, type MachinaInstance, type StateNamesOf, type Subscription, createBehavioralFsm, createFsm };
654
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/emitter.ts","../src/types.ts","../src/fsm.ts","../src/behavioral-fsm.ts"],"mappings":"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"}