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.
package/dist/index.d.mts CHANGED
@@ -1,12 +1,14 @@
1
+ //#region src/emitter.d.ts
1
2
  /**
2
3
  * Returned by `on()`. Call `off()` to remove the subscription.
3
4
  * Safe to call multiple times — subsequent calls are no-ops because
4
5
  * the underlying Set deduplicates membership.
5
6
  */
6
7
  type Subscription = {
7
- off(): void;
8
+ off(): void;
8
9
  };
9
-
10
+ //#endregion
11
+ //#region src/types.d.ts
10
12
  /**
11
13
  * Keys on a state object that have special meaning and are NOT input names.
12
14
  * Used by InputNamesOf to filter these out when collecting input names.
@@ -46,9 +48,7 @@ type StateNamesOf<TStates> = keyof TStates & string;
46
48
  * handler keys across every state
47
49
  * 3. `Exclude<..., SpecialStateKeys>` — strips lifecycle/special keys
48
50
  */
49
- type InputNamesOf<TStates> = Exclude<{
50
- [S in keyof TStates]: keyof TStates[S] & string;
51
- }[keyof TStates], SpecialStateKeys>;
51
+ type InputNamesOf<TStates> = Exclude<{ [S in keyof TStates]: keyof TStates[S] & string }[keyof TStates], SpecialStateKeys>;
52
52
  /**
53
53
  * The single combined object passed to every handler.
54
54
  *
@@ -81,40 +81,40 @@ type InputNamesOf<TStates> = Exclude<{
81
81
  * ```
82
82
  */
83
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;
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
118
  }
119
119
  /**
120
120
  * A function handler for state inputs, lifecycle hooks (_onEnter, _onExit),
@@ -215,11 +215,7 @@ type HandlerDef<TCtx, TStateNames extends string = string> = TStateNames | Handl
215
215
  * @typeParam TStates - The literal states object type captured by the
216
216
  * factory function's generic parameter
217
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
- };
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> } };
223
219
  /**
224
220
  * Configuration object for creating an FSM.
225
221
  *
@@ -247,27 +243,27 @@ type ValidateStates<TCtx, TStates extends Record<string, Record<string, unknown>
247
243
  * ```
248
244
  */
249
245
  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>;
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>;
271
267
  }
272
268
  /**
273
269
  * Symbol used as a property key to identify machina FSM instances at runtime.
@@ -283,17 +279,44 @@ declare const MACHINA_TYPE: unique symbol;
283
279
  type MachinaType = "Fsm" | "BehavioralFsm";
284
280
  /** Structural type matching any machina FSM instance (Fsm or BehavioralFsm) */
285
281
  type MachinaInstance = {
286
- readonly [MACHINA_TYPE]: MachinaType;
282
+ readonly [MACHINA_TYPE]: MachinaType;
287
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
+ }
288
311
  /**
289
312
  * Options for FSM disposal.
290
313
  */
291
314
  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;
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;
297
320
  }
298
321
  /**
299
322
  * Built-in event map for Fsm instances.
@@ -303,37 +326,37 @@ interface DisposeOptions {
303
326
  * event payloads so fromState/toState are narrowed to actual state names.
304
327
  */
305
328
  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
- };
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
+ };
337
360
  }
338
361
  /**
339
362
  * Built-in event map for BehavioralFsm instances.
@@ -343,12 +366,11 @@ interface FsmEventMap<TStateNames extends string = string> {
343
366
  * @typeParam TClient - The client object type
344
367
  * @typeParam TStateNames - The state name union
345
368
  */
346
- type BehavioralFsmEventMap<TClient, TStateNames extends string = string> = {
347
- [K in keyof FsmEventMap<TStateNames>]: FsmEventMap<TStateNames>[K] & {
348
- client: TClient;
349
- };
350
- };
351
-
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
352
374
  /**
353
375
  * Single-client FSM. Wraps a BehavioralFsm and uses the config's `context`
354
376
  * object as the implicit client, so callers never pass a client argument.
@@ -363,70 +385,72 @@ type BehavioralFsmEventMap<TClient, TStateNames extends string = string> = {
363
385
  * @typeParam TInputNames - String literal union of valid input names.
364
386
  */
365
387
  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;
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;
430
454
  }
431
455
  /**
432
456
  * Create a single-client FSM from a config object.
@@ -456,7 +480,8 @@ declare class Fsm<TCtx extends object, TStateNames extends string, TInputNames e
456
480
  * ```
457
481
  */
458
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>>;
459
-
483
+ //#endregion
484
+ //#region src/behavioral-fsm.d.ts
460
485
  /**
461
486
  * Defines FSM behavior (states + transitions) while tracking per-client state
462
487
  * in a `WeakMap`. A single `BehavioralFsm` instance can drive any number of
@@ -474,124 +499,124 @@ declare function createFsm<TCtx extends object = Record<string, never>, const TS
474
499
  * @typeParam TInputNames - String literal union of valid input names.
475
500
  */
476
501
  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;
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;
595
620
  }
596
621
  /**
597
622
  * Create a behavioral FSM (one definition, many clients) from a config object.
@@ -624,5 +649,6 @@ declare class BehavioralFsm<TClient extends object, TStateNames extends string,
624
649
  * ```
625
650
  */
626
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>>;
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 };
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.mts.map