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.
- package/README.md +273 -132
- package/lib/cjs/index.d.ts +692 -40
- package/lib/cjs/index.js +193 -31
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/index.d.ts +692 -40
- package/lib/esm/index.js +193 -31
- package/lib/esm/index.js.map +1 -1
- package/package.json +15 -11
package/lib/cjs/index.d.ts
CHANGED
|
@@ -1,175 +1,734 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Default context
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
222
|
-
*
|
|
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
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
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
|
-
*
|
|
235
|
-
* never
|
|
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
|
|
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
|
-
*
|
|
250
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
|
273
|
-
*
|
|
274
|
-
* @
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* @
|
|
278
|
-
* @
|
|
279
|
-
* @param
|
|
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>;
|