ihsm 0.0.19 → 0.0.21

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.
@@ -1,175 +1,734 @@
1
1
  /**
2
- * Default context and protocol map when a machine is created without explicit typing.
2
+ * Default context type when a machine is created without an explicit `Context` generic.
3
+ *
4
+ * Equivalent to `Record<string, any>` — use a dedicated interface in production code so
5
+ * `ctx` fields are checked at compile time.
6
+ *
3
7
  * @category Factory
4
8
  */
5
9
  export type Any = Record<string, any>;
6
10
  /**
7
- * Rejects an async {@link Hsm.call} service with an error.
11
+ * Rejects an in-flight {@link Hsm.call} service by rejecting the client's `Promise`.
12
+ *
13
+ * The runtime injects this callback as the **second** parameter of service handlers.
14
+ * Call it with any `Error` (or subclass) when the service cannot complete successfully.
15
+ * After `reject` is invoked, the client's `await call(...)` throws; no further handler
16
+ * code should call `resolve`.
17
+ *
18
+ * @param error - Failure reason propagated to the `call()` caller
19
+ *
20
+ * @remarks
21
+ * Pair with {@link ResolveCallback} on the same handler signature. Service methods are
22
+ * recognized by the `(resolve, reject, ...payload)` parameter pattern on `Protocol`.
23
+ *
8
24
  * @category Event handler
9
25
  */
10
26
  export type RejectCallback = (error: Error) => void;
11
27
  /**
12
- * Resolves an async {@link Hsm.call} service with a typed reply.
28
+ * Resolves an in-flight {@link Hsm.call} service with a typed reply.
29
+ *
30
+ * The runtime injects this callback as the **first** parameter of service handlers.
31
+ * Call it exactly once with the successful result; the client's `Promise` settles with
32
+ * that value. The handler's own return value (including `Promise<void>` from `async`
33
+ * handlers) does **not** substitute for calling `resolve`.
34
+ *
35
+ * @typeParam Reply - Success type inferred from the service method's `resolve` parameter
36
+ * @param result - Value delivered to the `call()` caller
37
+ *
38
+ * @remarks
39
+ * For `async` service handlers, perform awaits first, then call `resolve(result)` before
40
+ * returning. If you forget `resolve`, the client's `Promise` never settles.
41
+ *
13
42
  * @category Event handler
14
43
  */
15
44
  export type ResolveCallback<Reply> = (result: Reply) => void;
16
45
  /**
17
- * Called when event dispatch fails and the runtime does not recover via `onError`.
46
+ * Application hook invoked when the runtime cannot recover from a dispatch failure.
47
+ *
48
+ * Called **after** {@link StateEvents.onError} and {@link StateEvents.onUnhandled} have
49
+ * been tried and the error is still propagating, or when no recovery hook handled it.
50
+ * The default implementation logs via {@link TraceWriter} and **rethrows** the error.
51
+ *
52
+ * @param hsm - The machine handle (`Hsm` or handler `State` view) at failure time
53
+ * @param err - The error that terminated dispatch (often {@link EventHandlerError},
54
+ * {@link UnhandledEventError}, {@link TransitionError}, or {@link FatalError})
55
+ *
56
+ * @remarks
57
+ * Override at construction (`makeHsm(..., dispatchErrorCallback)`) or assign
58
+ * `hsm.dispatchErrorCallback` for integration tests that must assert failures.
59
+ * Note: {@link Hsm.sync} still resolves when the default callback throws — failures
60
+ * surface in logs, not as a rejected `sync()` Promise.
61
+ *
18
62
  * @category Factory
19
63
  */
20
64
  export interface DispatchErrorCallback<Context, Protocol extends {} | undefined> {
21
65
  (hsm: Base<Context, Protocol>, err: Error): void;
22
66
  }
23
67
  /**
24
- * Trace verbosity for dispatch logging.
68
+ * Controls how much diagnostic detail the runtime emits through {@link TraceWriter}.
69
+ *
70
+ * Set at construction via {@link makeHsm} or mutate {@link Properties.traceLevel} on a
71
+ * live instance. Changing the level swaps the internal dispatch tracer implementation.
72
+ *
25
73
  * @category Factory
26
74
  */
27
75
  export declare enum TraceLevel {
76
+ /**
77
+ * Production mode: minimal tracing overhead, no verbose dispatch steps.
78
+ * Use in hot paths and shipped bundles when trace output is disabled.
79
+ */
28
80
  PRODUCTION = 0,
81
+ /**
82
+ * Debug mode: transition boundaries, handler entry/exit, and error summaries.
83
+ * Default for {@link makeHsm}. Suitable for development and integration tests.
84
+ */
29
85
  DEBUG = 1,
86
+ /**
87
+ * Verbose debug: includes prototype-chain lookup walks, cache hits/misses, and
88
+ * nested trace domains. Use when correlating handler code with tutorial trace panels.
89
+ */
30
90
  VERBOSE_DEBUG = 2
31
91
  }
32
92
  /**
33
- * Receives trace lines from the runtime and from handlers via `this.traceWriter.write`.
93
+ * Sink for structured or human-readable trace output from the runtime and handlers.
94
+ *
95
+ * Inject a custom implementation via {@link makeHsm} to collect traces in tests,
96
+ * forward to OpenTelemetry, or suppress console noise in CI.
97
+ *
34
98
  * @category Factory
35
99
  */
36
100
  export interface TraceWriter {
101
+ /**
102
+ * Record one trace line for a machine instance.
103
+ *
104
+ * @typeParam Context - Domain context type of the machine being traced
105
+ * @typeParam Protocol - Event/service vocabulary of the machine being traced
106
+ * @param hsm - Read-only machine properties ({@link Properties.currentStateName},
107
+ * {@link Properties.traceHeader}, etc.) at the time of the write
108
+ * @param msg - Payload to record. **Strings** are formatted as
109
+ * `` `${traceHeader}${currentStateName}: ${msg}` `` by the default console writer;
110
+ * non-strings are passed through unchanged (e.g. `Error` objects on failure paths)
111
+ *
112
+ * @remarks
113
+ * Handlers may call `this.traceWriter.write(this.hsm, 'my message')` for ad-hoc
114
+ * logging that respects the same header and state prefix as runtime traces.
115
+ */
37
116
  write<Context, Protocol extends {} | undefined>(hsm: Properties<Context, Protocol>, msg: any): void;
38
117
  }
39
118
  /**
119
+ * Read-only snapshot of runtime metadata shared by client handles and active handlers.
120
+ *
121
+ * Exposed on {@link State} (inside handlers as `this.hsm` properties and forwarded
122
+ * getters on {@link TopState}) and on {@link Hsm} (external client code). Values
123
+ * reflect the **current dispatch** when read from within an event handler; outside
124
+ * handlers, {@link eventName} and {@link eventPayload} are empty.
125
+ *
40
126
  * @category State machine
41
127
  */
42
128
  export interface Properties<Context, Protocol extends {} | undefined> {
129
+ /**
130
+ * Constructor (`Function`) of the **leaf** state class currently executing.
131
+ *
132
+ * Compare with {@link topState}, which is always the root composite passed to
133
+ * {@link makeHsm}. After a transition, this updates to the new leaf's constructor.
134
+ */
43
135
  readonly currentState: StateClass<Context, Protocol>;
136
+ /**
137
+ * Human-readable name of {@link currentState}.
138
+ *
139
+ * Sourced from {@link defineStateName} / {@link registerStateNames} when registered;
140
+ * otherwise `Class.name` (unreliable under minification — register names in browser builds).
141
+ */
44
142
  readonly currentStateName: string;
143
+ /**
144
+ * Constructor of the root state class supplied to {@link makeHsm}.
145
+ *
146
+ * Constant for the lifetime of the instance unless you replace the entire machine.
147
+ */
45
148
  readonly topState: StateClass<Context, Protocol>;
149
+ /** Display name of {@link topState} (same naming rules as {@link currentStateName}). */
46
150
  readonly topStateName: string;
151
+ /**
152
+ * Runtime label derived from `ctx` constructor name, used as the first segment of
153
+ * {@link traceHeader} in verbose traces.
154
+ */
47
155
  readonly ctxTypeName: string;
156
+ /**
157
+ * Prefix for nested trace domains, built from internal dispatch stack frames.
158
+ *
159
+ * Empty at the top level; grows like `domain|subdomain|` during nested operations.
160
+ * Handlers rarely need to read this directly — it is prepended automatically by
161
+ * the default {@link TraceWriter}.
162
+ */
48
163
  readonly traceHeader: string;
164
+ /**
165
+ * Name of the event or service currently being dispatched.
166
+ *
167
+ * Matches the string passed to {@link Base.post}, {@link Hsm.call}, or
168
+ * {@link State.postNow}. Empty string when no handler is running.
169
+ */
49
170
  readonly eventName: string;
171
+ /**
172
+ * Arguments passed with the current dispatch, excluding injected `resolve` / `reject`
173
+ * for {@link Hsm.call} services.
174
+ *
175
+ * Empty array when idle. Typed as `any[]` at runtime; correlate with
176
+ * {@link EventPayload} / {@link ServiceRequest} at compile time on the client.
177
+ */
50
178
  readonly eventPayload: any[];
179
+ /**
180
+ * Active trace verbosity; changing this swaps dispatch tracing behavior immediately.
181
+ *
182
+ * @see TraceLevel
183
+ */
51
184
  traceLevel: TraceLevel;
185
+ /**
186
+ * Destination for runtime and handler-initiated trace lines.
187
+ *
188
+ * Replaceable at any time (e.g. swap in a test double before `post`/`call`).
189
+ */
52
190
  traceWriter: TraceWriter;
191
+ /**
192
+ * Last-resort error hook when {@link StateEvents.onError} / {@link StateEvents.onUnhandled}
193
+ * do not recover.
194
+ *
195
+ * @see DispatchErrorCallback
196
+ */
53
197
  dispatchErrorCallback: DispatchErrorCallback<Context, Protocol>;
54
198
  }
55
199
  /**
200
+ * Fire-and-forget event posting API available on both {@link Hsm} (clients) and {@link State} (handlers).
201
+ *
202
+ * Events enqueue on the machine mailbox and run **one at a time** — no re-entrancy while a
203
+ * handler is active.
204
+ *
56
205
  * @category State machine
57
206
  */
58
207
  export interface Base<Context, Protocol extends {} | undefined> extends Properties<Context, Protocol> {
208
+ /**
209
+ * Enqueue a **normal-priority** event for later dispatch on the active state.
210
+ *
211
+ * Returns immediately; the handler runs asynchronously when the mailbox reaches this job.
212
+ * Dispatch walks the **prototype chain** from the current leaf upward until a method named
213
+ * `eventName` is found.
214
+ *
215
+ * @typeParam EventName - Literal key of `Protocol` being posted
216
+ * @param eventName - Event or service name. Must be `keyof Protocol` and must **not** collide
217
+ * with reserved {@link State} method names (`transition`, `post`, `ctx`, …)
218
+ * @param eventPayload - Arguments tuple inferred from `Protocol[eventName]` handler parameters.
219
+ * For **events**, pass every parameter **except** `resolve` / `reject`. For fire-and-forget
220
+ * events, the handler return type must be `void` or `Promise<void>`
221
+ *
222
+ * @remarks
223
+ * **Client usage:** `door.post('open')` then `await door.sync()` to wait for completion.
224
+ *
225
+ * **Handler usage:** `this.post('tick')` schedules work **after** the current handler returns
226
+ * and after any {@link State.transition} it requested. Normal-priority posts run **after** all
227
+ * {@link State.postNow} hi-priority jobs drained for the current turn.
228
+ *
229
+ * **Ordering:** FIFO among normal-priority jobs. Multiple posts before one `sync()` are
230
+ * processed in submission order.
231
+ *
232
+ * **Typing:** With `Protocol extends undefined`, accepts any `string` and `any[]` (legacy mode).
233
+ *
234
+ * @example Client fire-and-forget
235
+ * ```ts
236
+ * door.post('open');
237
+ * await door.sync(); // handler + transition complete
238
+ * ```
239
+ *
240
+ * @example Handler chains follow-up work
241
+ * ```ts
242
+ * approve(): void {
243
+ * this.ctx.approved = true;
244
+ * this.post('notify'); // runs after this handler finishes
245
+ * }
246
+ * ```
247
+ */
59
248
  post<EventName extends keyof Protocol>(eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
249
+ /**
250
+ * Schedule a normal-priority {@link post} after a wall-clock delay.
251
+ *
252
+ * Uses `setTimeout` internally; when the timer fires, the event is enqueued like an ordinary
253
+ * `post`. Timers are **not** cancelled if the machine transitions or the scheduling handler throws.
254
+ *
255
+ * @typeParam EventName - Literal key of `Protocol` being scheduled
256
+ * @param millis - Delay in milliseconds before enqueueing (≥ 0). Subject to event-loop timer granularity
257
+ * @param eventName - Event name (same constraints as {@link post})
258
+ * @param eventPayload - Handler arguments tuple (same as {@link post})
259
+ *
260
+ * @remarks
261
+ * Available on {@link State} and {@link Hsm}. Typical pattern: handler schedules reminder,
262
+ * client waits with `await sleep(millis); await hsm.sync()`.
263
+ *
264
+ * Does **not** block the calling handler — returns as soon as the timer is registered.
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * scheduleReminder(text: string): void {
269
+ * this.deferredPost(50, 'deliver', text);
270
+ * }
271
+ * deliver(text: string): void {
272
+ * this.ctx.message = text;
273
+ * }
274
+ * ```
275
+ */
60
276
  deferredPost<EventName extends keyof Protocol>(millis: number, eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
61
277
  }
62
278
  /**
279
+ * Handler-facing API available inside state class methods (`this` / `this.hsm`).
280
+ *
281
+ * Extends {@link Base} with context access, transitions, async sleep, explicit unhandled
282
+ * signaling, and {@link postNow} hi-priority re-dispatch. Only code running **inside** an
283
+ * active handler should call {@link transition}, {@link unhandled}, or {@link postNow}.
284
+ *
63
285
  * @category State machine
64
286
  */
65
287
  export interface State<Context, Protocol extends {} | undefined> extends Base<Context, Protocol> {
288
+ /**
289
+ * Mutable domain data object shared across all states of this machine instance.
290
+ *
291
+ * Passed as the second argument to {@link makeHsm}; survives transitions unless replaced
292
+ * by {@link Hsm.restore}. Update fields freely for internal transitions (no `transition()`).
293
+ *
294
+ * @remarks
295
+ * Context is **not** the active state name — state is which class prototype is active;
296
+ * context is arbitrary application data (counters, buffers, IDs, flags).
297
+ */
66
298
  readonly ctx: Context;
299
+ /**
300
+ * Schedule an **external** transition to `nextState` after the current handler completes.
301
+ *
302
+ * Does **not** run exit/entry immediately. When the handler returns successfully (including
303
+ * after awaiting an `async` handler's Promise), the runtime:
304
+ *
305
+ * 1. Computes the **lowest common ancestor (LCA)** on the class prototype chain
306
+ * 2. Runs `onExit()` from the current leaf up to (but not including) the LCA
307
+ * 3. Switches the instance prototype to `nextState` (descending `@InitialState` chains for composites)
308
+ * 4. Runs `onEntry()` down from the LCA to the target leaf
309
+ *
310
+ * @param nextState - Destination state **class** constructor (not an instance)
311
+ *
312
+ * @throws {@link TransitionError} when `onExit` or `onEntry` throws along the path (may transition to {@link FatalErrorState})
313
+ * @throws {@link EventHandlerError} when the triggering handler threw before transition phase
314
+ *
315
+ * @remarks
316
+ * - **Self-transition** to the same leaf with unchanged initial descent: optimized to skip exit/entry
317
+ * - **Internal transition:** omit `transition()` — active class unchanged, no exit/entry
318
+ * - **`transition()` inside `onEntry`/`onExit`:** scheduled transition is **cleared** when that lifecycle dispatch ends — use {@link post} from `onEntry` for follow-up work
319
+ * - Transition paths are **cached** per `From=>To` pair for hot loops
320
+ * - Only the **last** `transition()` call wins if invoked multiple times in one handler
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * open(): void {
325
+ * this.ctx.openCount += 1;
326
+ * this.transition(Open);
327
+ * }
328
+ * ```
329
+ */
67
330
  transition(nextState: StateClass<Context, Protocol>): void;
331
+ /**
332
+ * Declare that the current event has **no handler** on this state (explicit super-call pattern).
333
+ *
334
+ * Throws {@link UnhandledEventError} unless an ancestor's {@link StateEvents.onUnhandled}
335
+ * catches it. Prefer **omitting** the method entirely when a state should inherit a parent's
336
+ * handler — only call `unhandled()` when you intentionally defer to the error model.
337
+ *
338
+ * @returns `never` — always throws or redirects via `onUnhandled`
339
+ *
340
+ * @throws {@link UnhandledEventError} by default
341
+ *
342
+ * @remarks
343
+ * Runtime dispatch already throws when no method exists on the prototype chain; `unhandled()`
344
+ * is for handlers that exist but choose not to handle the event.
345
+ */
68
346
  unhandled(): never;
347
+ /**
348
+ * Pause the **current handler** without blocking the JavaScript event loop.
349
+ *
350
+ * Returns a Promise resolved after `millis` milliseconds via `setTimeout`. The mailbox
351
+ * remains **locked** to this handler until the Promise settles — other `post`/`call` jobs
352
+ * queue but do not run.
353
+ *
354
+ * @param millis - Sleep duration in milliseconds (≥ 0)
355
+ * @returns Promise that resolves (never rejects) when the delay elapses
356
+ *
357
+ * @remarks
358
+ * Use for simple delays inside handlers. For calendar-time deferral of **new** events,
359
+ * prefer {@link deferredPost}. Composable with `async` handlers: `await this.sleep(100)`.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * async pulse(): Promise<void> {
364
+ * await this.sleep(50);
365
+ * this.ctx.pulses += 1;
366
+ * }
367
+ * ```
368
+ */
69
369
  sleep(millis: number): Promise<void>;
70
- /** Handler-only: queue an event on the internal high-priority mailbox (before normal `post`). */
370
+ /**
371
+ * Enqueue a **hi-priority** event processed before normal {@link post} jobs from the same turn.
372
+ *
373
+ * Only valid **inside** a running handler (`this.postNow`). Clients must use {@link post}.
374
+ * Hi-priority jobs drain immediately after the current handler and its transition complete,
375
+ * **before** any normal-priority posts the handler enqueued (including chained `this.post`).
376
+ *
377
+ * @typeParam EventName - Literal key of `Protocol`
378
+ * @param eventName - Event name (same typing rules as {@link post})
379
+ * @param eventPayload - Handler arguments (same tuple as {@link post})
380
+ *
381
+ * @remarks
382
+ * Models **extended transitions**: multiple internal steps (lock, capture, validate) that
383
+ * must complete before deferred side effects. See tutorial `17-post-now`.
384
+ *
385
+ * Multiple `postNow` calls run in FIFO order within the hi-priority queue. You may need
386
+ * an extra {@link Hsm.sync} after the first to drain postNow follow-ups.
387
+ *
388
+ * @example
389
+ * ```ts
390
+ * confirm(): void {
391
+ * this.post('cancel'); // normal — runs last
392
+ * this.postNow('lockInventory'); // hi — runs first among follow-ups
393
+ * this.postNow('capturePayment');
394
+ * this.transition(Confirmed);
395
+ * }
396
+ * ```
397
+ */
71
398
  postNow<EventName extends keyof Protocol>(eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
72
399
  }
73
400
  /**
74
- * Actor handle returned by {@link makeHsm} — client API (`post`, `call`, `sync`, `restore`).
401
+ * Client handle returned by {@link makeHsm} — post events, await services, synchronize, restore.
402
+ *
403
+ * The same object is also the runtime actor (`HsmObject`); external code should treat it as
404
+ * an **actor reference**: send messages, never mutate internal queues directly.
405
+ *
75
406
  * @category State machine
76
407
  */
77
408
  export interface Hsm<Context = Any, Protocol extends {} | undefined = undefined> extends Base<Context, Protocol> {
409
+ /** @inheritdoc State.ctx */
78
410
  readonly ctx: Context;
411
+ /**
412
+ * Wait until all previously enqueued mailbox work completes through a **sync marker**.
413
+ *
414
+ * Returns a Promise resolved when the marker job reaches the front of the queue and runs —
415
+ * meaning every job enqueued **before** this `sync()` call has finished (handlers, transitions,
416
+ * hi-priority drains, and previously scheduled timers that have already fired).
417
+ *
418
+ * @returns Promise that resolves when the queue drains up to the marker (does not reject on handler errors unless {@link dispatchErrorCallback} rethrows to caller)
419
+ *
420
+ * @remarks
421
+ * - **After `post`:** one `sync()` waits for that handler and its transition
422
+ * - **Batch posts:** single `sync()` waits for **all** jobs enqueued before it
423
+ * - **After chained handler `post`s:** call `sync()` again to drain follow-up work
424
+ * - **After `postNow` chains:** may require **two** `sync()` calls (handler + hi-priority drain)
425
+ * - **After `call`:** usually unnecessary — `await call(...)` already waits for `resolve`/`reject`
426
+ * - **Initialization:** `makeHsm(..., initialize: true)` enqueues init work; await `sync()` before asserting initial state
427
+ *
428
+ * @example
429
+ * ```ts
430
+ * door.post('open');
431
+ * await door.sync();
432
+ * expect(door.currentStateName).toBe('Open');
433
+ * ```
434
+ */
79
435
  sync(): Promise<void>;
436
+ /**
437
+ * Atomically replace the active leaf state and context **without** running `onExit` / `onEntry`.
438
+ *
439
+ * Used for persistence rehydration, snapshot restore, time-travel debugging, and test fixtures.
440
+ * Does not enqueue mailbox jobs — the next `post`/`call` runs from the restored configuration.
441
+ *
442
+ * @param state - Leaf or composite state **class** to activate (prototype switched immediately)
443
+ * @param ctx - New context object (replaces {@link ctx} reference entirely)
444
+ *
445
+ * @remarks
446
+ * Caller is responsible for consistency: restored `ctx` should match what `state` expects.
447
+ * Does not walk `@InitialState` — if you restore a composite class, you get that exact class,
448
+ * not its default child. Queued jobs from before `restore` are **not** cancelled.
449
+ *
450
+ * @example
451
+ * ```ts
452
+ * checkpoint.restore(SavedState, savedCtx);
453
+ * await checkpoint.sync(); // drain any pre-restore jobs first if needed
454
+ * ```
455
+ */
80
456
  restore(state: StateClass<Context, Protocol>, ctx: Context): void;
457
+ /**
458
+ * Invoke a **service** handler and await its typed result over the mailbox.
459
+ *
460
+ * Enqueues a dispatch job like {@link post}, but the runtime prepends `resolve` and `reject`
461
+ * callbacks to the handler invocation. The returned Promise settles when the handler calls
462
+ * `resolve(value)` or `reject(error)` — **not** when the handler function returns.
463
+ *
464
+ * @typeParam EventName - Literal service name on `Protocol`
465
+ * @param eventName - Service key whose handler signature starts with
466
+ * `(resolve: ResolveCallback<T>, reject: RejectCallback, ...payload)`
467
+ * @param eventPayload - Request arguments **after** resolve/reject (client never passes callbacks)
468
+ * @returns Promise resolving to `T` inferred from the handler's `resolve` parameter type
469
+ *
470
+ * @throws Propagates any `Error` passed to `reject`, or {@link EventHandlerError} /
471
+ * {@link UnhandledEventError} if dispatch fails before the service runs
472
+ *
473
+ * @remarks
474
+ * - Same **serialized** mailbox as `post` — no concurrent handler re-entrancy
475
+ * - Return type {@link ServiceResponse} is inferred from `Protocol[eventName]`
476
+ * - Use {@link ResolveCallback} / {@link RejectCallback} in handler signatures for clarity
477
+ * - `async` handlers should `await` work then call `resolve(result)`
478
+ *
479
+ * @example
480
+ * ```ts
481
+ * // Protocol: getBalance(resolve: ResolveCallback<number>, reject: RejectCallback): void
482
+ * const balance = await wallet.call('getBalance');
483
+ * ```
484
+ */
81
485
  call<EventName extends keyof Protocol>(eventName: ServiceName<Protocol, EventName>, ...eventPayload: ServiceRequest<Protocol, EventName>): Promise<ServiceResponse<Protocol, EventName>>;
82
486
  }
83
487
  /**
488
+ * Valid first argument to {@link Base.post} / {@link State.postNow} — a protocol key that names
489
+ * a **void** handler (event), excluding reserved {@link State} method names.
490
+ *
491
+ * @typeParam Protocol - Machine vocabulary interface, or `undefined` for untyped `string` mode
492
+ * @typeParam EventName - Member key being constrained
493
+ *
494
+ * @remarks
495
+ * Collisions with `keyof State` become `never`, preventing `post('transition', …)` at compile time.
496
+ *
84
497
  * @category Event handler
85
498
  */
86
499
  export type PostedEvent<Protocol extends {} | undefined, EventName extends keyof Protocol> = Protocol extends undefined ? string : EventName extends keyof State<any, any> ? never : EventName;
87
500
  /**
501
+ * Tuple of arguments for {@link Base.post} after the event name, inferred from the handler signature.
502
+ *
503
+ * @typeParam Protocol - Machine vocabulary interface
504
+ * @typeParam EventName - Event key whose parameter list is extracted
505
+ *
506
+ * @remarks
507
+ * For `open(): void`, payload is `[]`. For `setTarget(celsius: number): void`, payload is `[number]`.
508
+ * Service-shaped methods (leading resolve/reject) are not valid events — payload becomes `never`.
509
+ *
88
510
  * @category Event handler
89
511
  */
90
512
  export type EventPayload<Protocol extends {} | undefined, EventName extends keyof Protocol> = Protocol extends undefined ? any[] : Protocol[EventName] extends (...payload: infer Payload) => Promise<void> | void ? (Payload extends any[] ? Payload : never) : never;
91
513
  /**
92
- * Constructor type for a state class in the machine hierarchy.
514
+ * Constructor type for a state class participating in the hierarchy.
515
+ *
516
+ * Satisfied by any `Function` whose `prototype` derives from {@link TopState}. Used wherever
517
+ * the runtime expects a state **class**, not an instance:
518
+ * {@link makeHsm}, {@link State.transition}, {@link InitialState}, {@link Hsm.restore}.
519
+ *
520
+ * @typeParam Context - Domain context carried by the machine
521
+ * @typeParam Protocol - Event/service vocabulary
522
+ *
93
523
  * @category State machine
94
524
  */
95
525
  export type StateClass<Context = Any, Protocol extends {} | undefined = undefined> = Function & {
96
526
  prototype: TopState<Context, Protocol>;
97
527
  };
98
528
  /**
529
+ * Tuple of client-supplied arguments to {@link Hsm.call}, excluding injected resolve/reject.
530
+ *
531
+ * @typeParam Protocol - Machine vocabulary interface
532
+ * @typeParam EventName - Service key whose request parameters are extracted
99
533
  *
534
+ * @remarks
535
+ * Extracted from everything after `(resolve, reject, ...payload)` on the service method.
536
+ *
537
+ * @category Event handler
100
538
  */
101
539
  export type ServiceRequest<Protocol, EventName extends keyof Protocol> = Protocol extends undefined ? any[] : Protocol[EventName] extends (resolve: (result: infer Reply) => void, reject: (error: infer Error) => void, ...payload: infer Payload) => Promise<void> | void ? (Payload extends any[] ? Payload : never) : never;
102
540
  /**
541
+ * Success type returned by {@link Hsm.call}, inferred from the service handler's `resolve` callback.
542
+ *
543
+ * @typeParam Protocol - Machine vocabulary interface
544
+ * @typeParam EventName - Service key whose reply type is extracted
103
545
  *
546
+ * @remarks
547
+ * For `getBalance(resolve: (n: number) => void, …)`, response is `number`.
548
+ *
549
+ * @category Event handler
104
550
  */
105
551
  export type ServiceResponse<Protocol, EventName extends keyof Protocol> = Protocol extends undefined ? any : Protocol[EventName] extends (resolve: infer Reply, reject: infer Error, ...payload: infer Payload) => Promise<void> | void ? Reply : never;
106
552
  /**
553
+ * Valid first argument to {@link Hsm.call} — protocol keys whose handlers use the service signature
554
+ * `(resolve, reject, ...payload)`.
555
+ *
556
+ * @typeParam Protocol - Machine vocabulary interface
557
+ * @typeParam EventName - Candidate key (unused generic for symmetry with other aliases)
107
558
  *
559
+ * @category Event handler
108
560
  */
109
561
  export type ServiceName<Protocol, EventName> = Protocol extends undefined ? string : EventName extends keyof State<any, any> ? never : EventName;
110
562
  /**
111
- * Optional lifecycle hooks implemented by state classes (`onEntry`, `onExit`, …).
563
+ * Lifecycle hooks optionally overridden on state classes.
564
+ *
565
+ * Default implementations on {@link TopState} are empty for entry/exit and rethrow for errors.
566
+ * Only states that **define their own** `onEntry`/`onExit` participate in verbose trace exit lists.
567
+ *
112
568
  * @category State machine
113
569
  */
114
570
  export interface StateEvents<Context, Protocol extends {} | undefined> {
571
+ /**
572
+ * Invoked when **leaving** this state during an external transition.
573
+ *
574
+ * Runs during the exit phase **after** the triggering handler completes and before the
575
+ * prototype switches away. Async `onExit` is awaited before continuing up the LCA path.
576
+ *
577
+ * @throws {@link TransitionError} when this hook throws — may route to {@link FatalErrorState}
578
+ *
579
+ * @remarks
580
+ * Not called for internal transitions (handler returns without {@link State.transition}).
581
+ * Not called on {@link Hsm.restore}. Self-transitions may skip exit when optimized.
582
+ */
115
583
  onExit(): Promise<void> | void;
584
+ /**
585
+ * Invoked when **entering** this state during initialization or an external transition.
586
+ *
587
+ * Runs during the entry phase after the prototype points at this state. Async `onEntry` is
588
+ * awaited before entering nested initial substates or running deeper `onEntry` hooks.
589
+ *
590
+ * @throws {@link TransitionError} or {@link InitializationError} when this hook throws
591
+ *
592
+ * @remarks
593
+ * During `makeHsm(..., initialize: true)`, `onEntry` runs from root down through each
594
+ * `@InitialState` chain to the initial leaf. Schedule follow-up transitions via {@link Base.post},
595
+ * not {@link State.transition}, from within `onEntry`.
596
+ */
116
597
  onEntry(): Promise<void> | void;
598
+ /**
599
+ * Recovery hook when an event handler throws {@link EventHandlerError}.
600
+ *
601
+ * @typeParam EventName - Correlated event key from `Protocol`
602
+ * @param error - Typed runtime error with {@link RuntimeError.eventName},
603
+ * {@link RuntimeError.eventPayload}, {@link HsmError.context}, and {@link HsmError.cause}
604
+ *
605
+ * @throws Rethrow to propagate; throwing here becomes {@link FatalError}
606
+ *
607
+ * @remarks
608
+ * Default {@link TopState} implementation rethrows. Override to log, transition to a safe
609
+ * state, or swallow. Uncaught errors invoke {@link Properties.dispatchErrorCallback}.
610
+ */
117
611
  onError<EventName extends keyof Protocol>(error: RuntimeError<Context, Protocol, EventName>): Promise<void> | void;
612
+ /**
613
+ * Recovery hook when dispatch would raise {@link UnhandledEventError}.
614
+ *
615
+ * @typeParam EventName - Correlated event key from `Protocol`
616
+ * @param error - Describes the unmatched event and active state
617
+ *
618
+ * @throws Default {@link TopState} implementation rethrows → may enter `onError`
619
+ *
620
+ * @remarks
621
+ * Override to implement catch-all handlers, auditing, or transition to an error state
622
+ * without defining every event on every leaf.
623
+ */
118
624
  onUnhandled<EventName extends keyof Protocol>(error: UnhandledEventError<Context, Protocol, EventName>): Promise<void> | void;
119
625
  }
120
626
  /**
121
- * Root of the state class hierarchy; hosts mailbox machinery. Subclass or pass to {@link makeHsm}.
627
+ * Abstract root class for every state in the hierarchy.
628
+ *
629
+ * States are **never constructed directly** — the runtime binds one instance object whose
630
+ * prototype moves along the class chain. Subclass `TopState` (or a child state), implement
631
+ * your `Protocol` methods, and pass the root class to {@link makeHsm}.
632
+ *
633
+ * Forwards {@link State} / {@link Properties} APIs and default {@link StateEvents} behavior.
634
+ *
122
635
  * @category State machine
123
636
  */
124
637
  export declare abstract class TopState<Context = Any, Protocol extends {} | undefined = undefined> implements State<Context, Protocol>, StateEvents<Context, Protocol> {
638
+ /** Domain context (injected by runtime — do not assign in constructors). */
125
639
  readonly ctx: Context;
640
+ /** Handler view of the machine (`this` inside methods delegates here for core operations). */
126
641
  readonly hsm: State<Context, Protocol>;
127
642
  constructor();
643
+ /** @inheritdoc Properties.eventName */
128
644
  get eventName(): string;
645
+ /** @inheritdoc Properties.eventPayload */
129
646
  get eventPayload(): any[];
647
+ /** @inheritdoc Properties.traceHeader */
130
648
  get traceHeader(): string;
649
+ /** @inheritdoc Properties.topState */
131
650
  get topState(): StateClass<Context, Protocol>;
651
+ /** @inheritdoc Properties.currentStateName */
132
652
  get currentStateName(): string;
653
+ /** @inheritdoc Properties.currentState */
133
654
  get currentState(): StateClass<Context, Protocol>;
655
+ /** @inheritdoc Properties.ctxTypeName */
134
656
  get ctxTypeName(): string;
657
+ /** @inheritdoc Properties.traceLevel */
135
658
  set traceLevel(value: TraceLevel);
659
+ /** @inheritdoc Properties.traceLevel */
136
660
  get traceLevel(): TraceLevel;
661
+ /** @inheritdoc Properties.topStateName */
137
662
  get topStateName(): string;
663
+ /** @inheritdoc Properties.traceWriter */
138
664
  get traceWriter(): TraceWriter;
665
+ /** @inheritdoc Properties.traceWriter */
139
666
  set traceWriter(value: TraceWriter);
667
+ /** @inheritdoc Properties.dispatchErrorCallback */
140
668
  get dispatchErrorCallback(): DispatchErrorCallback<Context, Protocol>;
669
+ /** @inheritdoc Properties.dispatchErrorCallback */
141
670
  set dispatchErrorCallback(value: DispatchErrorCallback<Context, Protocol>);
671
+ /** @inheritdoc State.transition */
142
672
  transition(nextState: StateClass<Context, Protocol>): void;
673
+ /** @inheritdoc State.unhandled */
143
674
  unhandled(): never;
675
+ /** @inheritdoc State.sleep */
144
676
  sleep(millis: number): Promise<void>;
677
+ /** @inheritdoc Base.post */
145
678
  post<EventName extends keyof Protocol>(eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
679
+ /** @inheritdoc Base.deferredPost */
146
680
  deferredPost<EventName extends keyof Protocol>(millis: number, eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
681
+ /** @inheritdoc State.postNow */
147
682
  postNow<EventName extends keyof Protocol>(eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
683
+ /** @inheritdoc StateEvents.onExit */
148
684
  onExit(): Promise<void> | void;
685
+ /** @inheritdoc StateEvents.onEntry */
149
686
  onEntry(): Promise<void> | void;
687
+ /** @inheritdoc StateEvents.onError */
150
688
  onError<EventName extends keyof Protocol>(error: RuntimeError<Context, Protocol, EventName>): Promise<void> | void;
689
+ /** @inheritdoc StateEvents.onUnhandled */
151
690
  onUnhandled<EventName extends keyof Protocol>(error: UnhandledEventError<Context, Protocol, EventName>): Promise<void> | void;
152
691
  }
153
692
  /**
693
+ * Base class for all ihsm runtime errors carrying machine context.
694
+ *
695
+ * @typeParam Context - Domain context at failure time
696
+ * @typeParam Protocol - Vocabulary type (for typed subclasses)
697
+ *
154
698
  * @category Error
155
699
  */
156
700
  export declare abstract class HsmError<Context, Protocol extends {} | undefined> extends Error {
701
+ /** Discriminator matching the class name (`EventHandlerError`, etc.). */
157
702
  name: string;
703
+ /** {@link Properties.topStateName} when the error was constructed. */
158
704
  topStateName: string;
705
+ /** {@link Properties.currentStateName} when the error was constructed. */
159
706
  stateName: string;
707
+ /** Snapshot of {@link State.ctx} when the error was constructed. */
160
708
  context: Context;
709
+ /** Original thrown value when this error wraps a handler or lifecycle failure. */
161
710
  cause?: Error;
162
711
  protected constructor(name: string, hsm: State<Context, Protocol>, message: string, cause?: Error);
163
712
  }
164
713
  /**
714
+ * Error base for failures during **event dispatch**, with correlated event metadata.
715
+ *
716
+ * @typeParam Context - Domain context
717
+ * @typeParam Protocol - Vocabulary interface
718
+ * @typeParam EventName - Event or service key being processed
719
+ *
165
720
  * @category Error
166
721
  */
167
722
  export declare abstract class RuntimeError<Context, Protocol extends {} | undefined, EventName extends keyof Protocol> extends HsmError<Context, Protocol> {
723
+ /** Event or service name that was active when the failure occurred. */
168
724
  eventName: PostedEvent<Protocol, EventName>;
725
+ /** Client-supplied arguments (excluding resolve/reject for services). */
169
726
  eventPayload: EventPayload<Protocol, EventName>;
170
727
  protected constructor(errorName: string, hsm: State<Context, Protocol>, message: string, cause?: Error);
171
728
  }
172
729
  /**
730
+ * Thrown when {@link StateEvents.onExit} or {@link StateEvents.onEntry} throws during a transition.
731
+ *
173
732
  * @category Error
174
733
  */
175
734
  export declare class TransitionError<Context, Protocol extends {} | undefined, EventName extends keyof Protocol> extends RuntimeError<Context, Protocol, EventName> {
@@ -177,106 +736,199 @@ export declare class TransitionError<Context, Protocol extends {} | undefined, E
177
736
  failedCallback: 'onExit' | 'onEntry';
178
737
  fromStateName: string;
179
738
  toStateName: string;
739
+ /**
740
+ * @param hsm - Machine view at failure time
741
+ * @param cause - Error thrown from the lifecycle hook
742
+ * @param failedStateName - Display name of the state whose hook failed
743
+ * @param failedCallback - Which hook failed (`onExit` or `onEntry`)
744
+ * @param fromStateName - Leaf state before the transition
745
+ * @param toStateName - Requested destination state
746
+ */
180
747
  constructor(hsm: State<Context, Protocol>, cause: Error, failedStateName: string, failedCallback: 'onExit' | 'onEntry', fromStateName: string, toStateName: string);
181
748
  }
182
749
  /**
750
+ * Thrown when an event handler body throws and {@link StateEvents.onError} does not recover.
751
+ *
183
752
  * @category Error
184
753
  */
185
754
  export declare class EventHandlerError<Context, Protocol extends {} | undefined, EventName extends keyof Protocol> extends RuntimeError<Context, Protocol, EventName> {
755
+ /**
756
+ * @param hsm - Machine view with {@link eventName} set to the failing handler
757
+ * @param cause - Error thrown from handler code
758
+ */
186
759
  constructor(hsm: State<Context, Protocol>, cause: Error);
187
760
  }
188
761
  /**
762
+ * Thrown when no handler matches the dispatched event and {@link StateEvents.onUnhandled} rethrows.
763
+ *
189
764
  * @category Error
190
765
  */
191
766
  export declare class UnhandledEventError<Context, Protocol extends {} | undefined, EventName extends keyof Protocol> extends RuntimeError<Context, Protocol, EventName> {
767
+ /** @param hsm - Machine view with the unmatched {@link eventName} */
192
768
  constructor(hsm: State<Context, Protocol>);
193
769
  }
194
770
  /**
771
+ * Thrown at **class definition** time when {@link InitialState} is applied twice to one parent.
772
+ *
195
773
  * @category Error
196
774
  */
197
775
  export declare class InitialStateError<Context, Protocol extends {} | undefined> extends Error {
776
+ /** Display name of the state passed to the duplicate {@link InitialState} call. */
198
777
  targetStateName: string;
778
+ /** @param targetState - The state class whose parent already has an initial substate */
199
779
  constructor(targetState: StateClass<Context, Protocol>);
200
780
  }
201
781
  /**
782
+ * Thrown when {@link StateEvents.onError} itself throws, leaving the machine unrecoverable.
783
+ *
202
784
  * @category Error
203
785
  */
204
786
  export declare class FatalError<Context, Protocol extends {} | undefined, EventName extends keyof Protocol> extends RuntimeError<Context, Protocol, EventName> {
787
+ /**
788
+ * @param hsm - Machine view at failure time
789
+ * @param cause - Error thrown from `onError`
790
+ */
205
791
  constructor(hsm: State<Context, Protocol>, cause: Error);
206
792
  }
207
793
  /**
794
+ * Thrown when {@link StateEvents.onEntry} fails during the initial `@InitialState` walk at startup.
795
+ *
208
796
  * @category Error
209
797
  */
210
798
  export declare class InitializationError<Context, Protocol extends {} | undefined> extends HsmError<Context, Protocol> {
211
799
  failedState: StateClass<Context, Protocol>;
800
+ /**
801
+ * @param hsm - Partially initialized machine
802
+ * @param failedState - State class whose `onEntry` threw
803
+ * @param cause - Original error from `onEntry`
804
+ */
212
805
  constructor(hsm: State<Context, Protocol>, failedState: StateClass<Context, Protocol>, cause: Error);
213
806
  }
214
807
  /**
215
- * Terminal error state class used when the machine cannot recover.
808
+ * Terminal sink state class used when the runtime cannot recover from an error.
809
+ *
810
+ * Assign or transition here from custom {@link StateEvents.onError} handlers when you need a
811
+ * well-defined quiescent state. Display name is pre-registered as `'FatalErrorState'`.
812
+ *
216
813
  * @category State machine
217
814
  */
218
815
  export declare class FatalErrorState<Context, Protocol extends {} | undefined> extends TopState<Context, Protocol> {
219
816
  }
220
817
  /**
221
- * Marks `TargetState` as the initial substate of its parent composite state.
222
- * @category Factory
818
+ * Declares `TargetState` as the **initial substate** of its direct parent composite.
819
+ *
820
+ * Apply as a TypeScript decorator or call as a function at class definition time. Exactly **one**
821
+ * initial child is allowed per parent; a second mark throws {@link InitialStateError}.
822
+ *
823
+ * @typeParam Context - Domain context type
824
+ * @typeParam Protocol - Vocabulary interface
825
+ * @param TargetState - Child state class whose **parent** is `Object.getPrototypeOf(TargetState.prototype).constructor`
826
+ *
827
+ * @throws {@link InitialStateError} when the parent already has an initial substate
828
+ *
829
+ * @remarks
830
+ * During {@link makeHsm} initialization, the runtime descends `@InitialState` chains from the
831
+ * root until the deepest initial leaf is active, running {@link StateEvents.onEntry} along the path.
832
+ *
833
+ * @example
834
+ * ```ts
835
+ * class Composite extends TopState {}
836
+ *
837
+ * @InitialState
838
+ * class Idle extends Composite {}
839
+ * ```
223
840
  */
224
841
  export declare function InitialState<Context, Protocol extends {} | undefined>(TargetState: StateClass<Context, Protocol>): void;
225
842
  /**
226
- * Assigns a stable display name to a state class.
843
+ * Assigns a stable **display name** to a single state class.
844
+ *
845
+ * Minifiers rewrite `Class.name` in production browser bundles; explicit registration keeps
846
+ * {@link Properties.currentStateName}, trace output, and error messages readable everywhere.
227
847
  *
228
- * Minifiers (and browser production bundles) rewrite class names, so the
229
- * built-in `Class.name` is unreliable in optimized builds. Registering an
230
- * explicit name keeps {@link Properties.currentStateName},
231
- * {@link Properties.topStateName}, trace output, and error messages stable in
232
- * every environment (Node and minified browsers alike).
848
+ * @typeParam Context - Domain context type
849
+ * @typeParam Protocol - Vocabulary interface
850
+ * @param state - State class constructor to tag
851
+ * @param name - Non-empty display string used in traces and errors (not required to match `Class.name`)
233
852
  *
234
- * The name is stored as a non-enumerable, non-inherited own property, so it is
235
- * never accidentally shared with subclasses through the prototype chain.
853
+ * @remarks
854
+ * Stored as a non-enumerable own property — never inherited by subclasses from the prototype chain.
855
+ * {@link registerStateNames} is preferred when every state is a named export from one module.
236
856
  *
237
857
  * @example
238
858
  * ```ts
239
859
  * class Door extends TopState {}
240
860
  * defineStateName(Door, 'Door');
241
861
  * ```
862
+ *
242
863
  * @category State machine
243
864
  */
244
865
  export declare function defineStateName<Context, Protocol extends {} | undefined>(state: StateClass<Context, Protocol>, name: string): void;
245
866
  /**
246
- * Registers stable display names for every state class found in an exports map,
247
- * using the export key as the display name.
867
+ * Registers display names for **every** state class in an exports object, using each **export key** as the name.
248
868
  *
249
- * This is the convenient way to make a whole machine module minification-safe:
250
- * pass the module namespace (or an object literal of the state classes) and
251
- * each state class gets its export key as its display name. Non state-class
252
- * values (factory functions, interfaces compiled away, constants) are ignored.
869
+ * @param exports - Module namespace (`import * as machine`) or object literal of state classes.
870
+ * Non-constructor exports (constants, functions, types) are silently skipped
253
871
  *
254
- * @example
872
+ * @remarks
873
+ * Export keys survive minification even when class identifiers are mangled — this is the recommended
874
+ * approach for browser bundles without `keep_classnames`. Call once at module load after all state
875
+ * classes are defined.
876
+ *
877
+ * @example Single module
255
878
  * ```ts
256
- * // machine.ts
257
879
  * export class DoorTop extends TopState {}
258
880
  * export class Open extends DoorTop {}
259
881
  * export class Closed extends DoorTop {}
260
882
  * registerStateNames({ DoorTop, Open, Closed });
261
883
  * ```
262
- * @example
884
+ *
885
+ * @example Re-exporting namespace
263
886
  * ```ts
264
- * // from another module
265
887
  * import * as machine from './machine';
266
888
  * registerStateNames(machine);
267
889
  * ```
890
+ *
268
891
  * @category State machine
269
892
  */
270
893
  export declare function registerStateNames(exports: Record<string, unknown>): void;
271
894
  /**
272
- * Creates a state machine instance bound to the given context object.
273
- *
274
- * @param topState - Root state class
275
- * @param ctx - Mutable domain context
276
- * @param initialize - When `true`, walk `@InitialState` chain and run `onEntry` on the path to the initial leaf (default `true`)
277
- * @param traceLevel - Trace verbosity (default {@link TraceLevel.DEBUG})
278
- * @param traceWriter - Trace sink (default console logger)
279
- * @param dispatchErrorCallback - Hook when dispatch throws and is not recovered (default: log and rethrow)
895
+ * Creates and optionally initializes a hierarchical state machine **actor** bound to `ctx`.
896
+ *
897
+ * The returned {@link Hsm} is the single runtime object: external clients call `post` / `call` /
898
+ * `sync`; the active state is the instance prototype chain updated by {@link State.transition}.
899
+ *
900
+ * @typeParam Context - Domain context type (inferred from `ctx` when passed inline)
901
+ * @typeParam Protocol - Event/service vocabulary (inferred from `topState` when it implements `Protocol`)
902
+ * @param topState - Root state **class** constructor (must extend {@link TopState})
903
+ * @param ctx - Mutable domain object shared by all states; stored on the instance as {@link Hsm.ctx}
904
+ * @param initialize - When `true` (default), enqueue the initial walk: descend `@InitialState`
905
+ * chains from `topState` and run {@link StateEvents.onEntry} on each entered state until the
906
+ * initial leaf is active. When `false`, prototype starts at `topState` with **no** entry hooks
907
+ * @param traceLevel - Initial {@link TraceLevel} (default {@link TraceLevel.DEBUG})
908
+ * @param traceWriter - {@link TraceWriter} implementation (default: prefixes strings with state name and logs to `console`)
909
+ * @param dispatchErrorCallback - Last-resort error hook (default: trace + rethrow)
910
+ * @returns Client handle implementing {@link Hsm} for the same `Context` and `Protocol`
911
+ *
912
+ * @remarks
913
+ * - Await {@link Hsm.sync} after creation when `initialize: true` before asserting initial state
914
+ * - Zero runtime npm dependencies; safe to embed in browsers when state names are registered
915
+ * - Transition LCA paths are cached per machine instance for the lifetime of the actor
916
+ *
917
+ * @example Minimal door machine
918
+ * ```ts
919
+ * const door = makeHsm(DoorTop, { openCount: 0 });
920
+ * await door.sync();
921
+ * door.post('open');
922
+ * await door.sync();
923
+ * ```
924
+ *
925
+ * @example Custom tracing in tests
926
+ * ```ts
927
+ * const writer = { write: (_hsm, msg) => logs.push(msg) };
928
+ * const sm = makeHsm(Top, ctx, true, TraceLevel.VERBOSE_DEBUG, writer);
929
+ * await sm.sync();
930
+ * ```
931
+ *
280
932
  * @category Factory
281
933
  */
282
934
  export declare function makeHsm<Context, Protocol extends undefined | {}>(topState: StateClass<Context, Protocol>, ctx: Context, initialize?: boolean, traceLevel?: TraceLevel, traceWriter?: TraceWriter, dispatchErrorCallback?: DispatchErrorCallback<Context, Protocol>): Hsm<Context, Protocol>;