ihsm 0.0.26 → 0.1.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.
Files changed (103) hide show
  1. package/README.md +105 -113
  2. package/lib/cjs/index.d.ts +8 -1394
  3. package/lib/cjs/index.js +65 -764
  4. package/lib/cjs/index.js.map +1 -1
  5. package/lib/cjs/internal/console-instrumentation.d.ts +34 -0
  6. package/lib/cjs/internal/console-instrumentation.js +71 -0
  7. package/lib/cjs/internal/console-instrumentation.js.map +1 -0
  8. package/lib/cjs/internal/identity.d.ts +16 -0
  9. package/lib/cjs/internal/identity.js +170 -0
  10. package/lib/cjs/internal/identity.js.map +1 -0
  11. package/lib/cjs/internal/instrumentation.d.ts +47 -0
  12. package/lib/cjs/internal/instrumentation.js +201 -0
  13. package/lib/cjs/internal/instrumentation.js.map +1 -0
  14. package/lib/cjs/internal/runtime.d.ts +376 -0
  15. package/lib/cjs/internal/runtime.js +2530 -0
  16. package/lib/cjs/internal/runtime.js.map +1 -0
  17. package/lib/cjs/internal/types.d.ts +515 -0
  18. package/lib/cjs/internal/types.js +9 -0
  19. package/lib/cjs/internal/types.js.map +1 -0
  20. package/lib/cjs/test-only.d.ts +5 -0
  21. package/lib/cjs/test-only.js +21 -0
  22. package/lib/cjs/test-only.js.map +1 -0
  23. package/lib/cjs/testing.d.ts +120 -88
  24. package/lib/cjs/testing.js +126 -38
  25. package/lib/cjs/testing.js.map +1 -1
  26. package/lib/cjs/transition-routines.d.ts +3 -0
  27. package/lib/cjs/transition-routines.js +11 -0
  28. package/lib/cjs/transition-routines.js.map +1 -0
  29. package/lib/cjs/types.d.ts +5 -0
  30. package/lib/cjs/{internal/defs.private.js → types.js} +1 -1
  31. package/lib/cjs/types.js.map +1 -0
  32. package/lib/esm/index.d.ts +8 -1394
  33. package/lib/esm/index.js +5 -742
  34. package/lib/esm/index.js.map +1 -1
  35. package/lib/esm/internal/console-instrumentation.d.ts +34 -0
  36. package/lib/esm/internal/console-instrumentation.js +68 -0
  37. package/lib/esm/internal/console-instrumentation.js.map +1 -0
  38. package/lib/esm/internal/identity.d.ts +16 -0
  39. package/lib/esm/internal/identity.js +159 -0
  40. package/lib/esm/internal/identity.js.map +1 -0
  41. package/lib/esm/internal/instrumentation.d.ts +47 -0
  42. package/lib/esm/internal/instrumentation.js +178 -0
  43. package/lib/esm/internal/instrumentation.js.map +1 -0
  44. package/lib/esm/internal/runtime.d.ts +376 -0
  45. package/lib/esm/internal/runtime.js +2462 -0
  46. package/lib/esm/internal/runtime.js.map +1 -0
  47. package/lib/esm/internal/types.d.ts +515 -0
  48. package/lib/esm/internal/types.js +6 -0
  49. package/lib/esm/internal/types.js.map +1 -0
  50. package/lib/esm/test-only.d.ts +5 -0
  51. package/lib/esm/test-only.js +15 -0
  52. package/lib/esm/test-only.js.map +1 -0
  53. package/lib/esm/testing.d.ts +120 -88
  54. package/lib/esm/testing.js +125 -38
  55. package/lib/esm/testing.js.map +1 -1
  56. package/lib/esm/transition-routines.d.ts +3 -0
  57. package/lib/esm/transition-routines.js +3 -0
  58. package/lib/esm/transition-routines.js.map +1 -0
  59. package/lib/esm/types.d.ts +5 -0
  60. package/lib/esm/types.js +2 -0
  61. package/lib/esm/types.js.map +1 -0
  62. package/package.json +22 -4
  63. package/lib/cjs/internal/defs.private.d.ts +0 -41
  64. package/lib/cjs/internal/defs.private.js.map +0 -1
  65. package/lib/cjs/internal/dispatch.debug.d.ts +0 -4
  66. package/lib/cjs/internal/dispatch.debug.js +0 -332
  67. package/lib/cjs/internal/dispatch.debug.js.map +0 -1
  68. package/lib/cjs/internal/dispatch.production.d.ts +0 -6
  69. package/lib/cjs/internal/dispatch.production.js +0 -241
  70. package/lib/cjs/internal/dispatch.production.js.map +0 -1
  71. package/lib/cjs/internal/dispatch.trace.d.ts +0 -4
  72. package/lib/cjs/internal/dispatch.trace.js +0 -418
  73. package/lib/cjs/internal/dispatch.trace.js.map +0 -1
  74. package/lib/cjs/internal/hsm.d.ts +0 -60
  75. package/lib/cjs/internal/hsm.js +0 -215
  76. package/lib/cjs/internal/hsm.js.map +0 -1
  77. package/lib/cjs/internal/lookup.d.ts +0 -15
  78. package/lib/cjs/internal/lookup.js +0 -32
  79. package/lib/cjs/internal/lookup.js.map +0 -1
  80. package/lib/cjs/internal/utils.d.ts +0 -26
  81. package/lib/cjs/internal/utils.js +0 -63
  82. package/lib/cjs/internal/utils.js.map +0 -1
  83. package/lib/esm/internal/defs.private.d.ts +0 -41
  84. package/lib/esm/internal/defs.private.js +0 -2
  85. package/lib/esm/internal/defs.private.js.map +0 -1
  86. package/lib/esm/internal/dispatch.debug.d.ts +0 -4
  87. package/lib/esm/internal/dispatch.debug.js +0 -328
  88. package/lib/esm/internal/dispatch.debug.js.map +0 -1
  89. package/lib/esm/internal/dispatch.production.d.ts +0 -6
  90. package/lib/esm/internal/dispatch.production.js +0 -237
  91. package/lib/esm/internal/dispatch.production.js.map +0 -1
  92. package/lib/esm/internal/dispatch.trace.d.ts +0 -4
  93. package/lib/esm/internal/dispatch.trace.js +0 -414
  94. package/lib/esm/internal/dispatch.trace.js.map +0 -1
  95. package/lib/esm/internal/hsm.d.ts +0 -60
  96. package/lib/esm/internal/hsm.js +0 -211
  97. package/lib/esm/internal/hsm.js.map +0 -1
  98. package/lib/esm/internal/lookup.d.ts +0 -15
  99. package/lib/esm/internal/lookup.js +0 -29
  100. package/lib/esm/internal/lookup.js.map +0 -1
  101. package/lib/esm/internal/utils.d.ts +0 -26
  102. package/lib/esm/internal/utils.js +0 -52
  103. package/lib/esm/internal/utils.js.map +0 -1
@@ -0,0 +1,2462 @@
1
+ import { kHandlerMachine, kParentLink } from './types.js';
2
+ import { actorNameFromTopState, childActorPath, mintActorIdentity, rootActorPath } from './identity.js';
3
+ import { getActiveInstrumentation, getTaskMeta, notifyActorCreated, notifyActorSpawned, notifyEnqueue, notifyError, notifyLog, notifyMacrostepBegin, notifyMacrostepEnd, notifyMicrostepBegin, notifyMicrostepEnd, notifyOutboundCallBegin, notifyOutboundCallEnd, notifyPortCallBegin, notifyPortCallEnd, setTaskMeta } from './instrumentation.js';
4
+ //#region TraceLevel
5
+ export var TraceLevel;
6
+ (function (TraceLevel) {
7
+ TraceLevel[TraceLevel["PRODUCTION"] = 0] = "PRODUCTION";
8
+ TraceLevel[TraceLevel["DEBUG"] = 1] = "DEBUG";
9
+ TraceLevel[TraceLevel["VERBOSE_DEBUG"] = 2] = "VERBOSE_DEBUG";
10
+ })(TraceLevel || (TraceLevel = {}));
11
+ //#endregion
12
+ //#region utils
13
+ /** @internal */
14
+ export function asError(err) {
15
+ return err instanceof Error ? err : new Error(String(err));
16
+ }
17
+ /** @internal */
18
+ export function quoteUnknown(err) {
19
+ return quoteError(asError(err));
20
+ }
21
+ /** @internal */
22
+ export function quoteError(err) {
23
+ return `${err.name}${err.message ? `: ${err.message}` : ' with no error message'}`;
24
+ }
25
+ /** @internal */
26
+ export function getInitialState(State) {
27
+ return State._initialState;
28
+ }
29
+ /** @internal */
30
+ export function hasInitialState(State) {
31
+ return Object.prototype.hasOwnProperty.call(State, '_initialState');
32
+ }
33
+ /** @internal */
34
+ export function getTransitionKey(FromState, ToState) {
35
+ return `${getStateName(FromState)}=>${getStateName(ToState)}`;
36
+ }
37
+ export function defineStateName(state, displayName) {
38
+ Object.defineProperty(state, '_stateName', {
39
+ value: displayName,
40
+ writable: false,
41
+ configurable: false,
42
+ enumerable: false,
43
+ });
44
+ }
45
+ /** @internal — prefers an own explicit name registered for minified browser bundles. */
46
+ export function getStateName(state) {
47
+ if (Object.prototype.hasOwnProperty.call(state, '_stateName')) {
48
+ return state._stateName;
49
+ }
50
+ return state.name;
51
+ }
52
+ //#endregion
53
+ //#region ports
54
+ /** Production port: timers, randomness, and deferred self-notifications for one machine.
55
+ * @typeParam T - Root state **constructor** (`typeof DoorTop`), not the instance type. */
56
+ export class Port {
57
+ actor;
58
+ _deferFactory;
59
+ _timerSeq = 0;
60
+ _timeoutHandles = new Map();
61
+ _intervalHandles = new Map();
62
+ /** Schedule a one-shot callback after `millis` milliseconds (platform timer). */
63
+ setTimeout(callback, millis) {
64
+ const id = ++this._timerSeq;
65
+ const handle = globalThis.setTimeout(() => {
66
+ this._timeoutHandles.delete(id);
67
+ callback();
68
+ }, Math.max(0, millis ?? 0));
69
+ this._timeoutHandles.set(id, handle);
70
+ return id;
71
+ }
72
+ /** Cancel a timer previously returned by {@link Port.setTimeout}. */
73
+ clearTimeout(id) {
74
+ if (id === undefined) {
75
+ return;
76
+ }
77
+ const handle = this._timeoutHandles.get(id);
78
+ if (handle !== undefined) {
79
+ globalThis.clearTimeout(handle);
80
+ this._timeoutHandles.delete(id);
81
+ }
82
+ }
83
+ /** Schedule a repeating callback every `millis` milliseconds. */
84
+ setInterval(callback, millis) {
85
+ const id = ++this._timerSeq;
86
+ const handle = globalThis.setInterval(callback, Math.max(0, millis ?? 0));
87
+ this._intervalHandles.set(id, handle);
88
+ return id;
89
+ }
90
+ /** Cancel an interval previously returned by {@link Port.setInterval}. */
91
+ clearInterval(id) {
92
+ if (id === undefined) {
93
+ return;
94
+ }
95
+ const handle = this._intervalHandles.get(id);
96
+ if (handle !== undefined) {
97
+ globalThis.clearInterval(handle);
98
+ this._intervalHandles.delete(id);
99
+ }
100
+ }
101
+ /** Pseudorandom number in `[0, 1)` — delegates to `Math.random()`. */
102
+ random() {
103
+ return Math.random();
104
+ }
105
+ /** Cryptographic-quality random in `[0, 1)` when the platform provides it. */
106
+ cryptoRandom() {
107
+ const crypto = globalThis.crypto;
108
+ return crypto.random?.() ?? Math.random();
109
+ }
110
+ /** Generate a UUID v4 string via `crypto.randomUUID()`. */
111
+ randomUUID() {
112
+ return globalThis.crypto.randomUUID();
113
+ }
114
+ /** Fill `array` with cryptographically strong random bytes. */
115
+ getRandomValues(array) {
116
+ globalThis.crypto.getRandomValues(array);
117
+ return array;
118
+ }
119
+ /** @internal Wired by {@link Machine.bindPort} — do not call from application code. */
120
+ bindDeferredNotifications(factory) {
121
+ this._deferFactory = factory;
122
+ }
123
+ defer(ms) {
124
+ if (this._deferFactory === undefined) {
125
+ throw new Error('ihsm: port.defer requires actor binding — pass the port to makeActor / makeTestActor');
126
+ }
127
+ return this._deferFactory(ms);
128
+ }
129
+ }
130
+ const kRequestingPort = Symbol('ihsm.requestingPort');
131
+ export class RequestingPort extends Port {
132
+ }
133
+ RequestingPort[kRequestingPort] = true;
134
+ //#endregion
135
+ //#region Errors
136
+ export class TopState {
137
+ ctx;
138
+ hsm;
139
+ notify;
140
+ notifyNow;
141
+ constructor() {
142
+ throw new Error('Fatal error: States cannot be instantiated');
143
+ }
144
+ onExit() { }
145
+ onEntry() { }
146
+ onError(error) {
147
+ throw error;
148
+ }
149
+ onUnhandled(error) {
150
+ throw error;
151
+ }
152
+ }
153
+ export class HsmError extends Error {
154
+ name;
155
+ topStateName;
156
+ stateName;
157
+ context;
158
+ cause;
159
+ constructor(name, hsm, message, cause) {
160
+ super(message);
161
+ this.name = name;
162
+ this.topStateName = hsm.topStateName;
163
+ this.stateName = hsm.currentStateName;
164
+ this.context = hsm.ctx;
165
+ this.cause = cause;
166
+ }
167
+ }
168
+ export class RuntimeError extends HsmError {
169
+ eventName;
170
+ eventPayload;
171
+ constructor(errorName, hsm, message, cause) {
172
+ super(errorName, hsm, message, cause);
173
+ this.eventName = hsm.eventName;
174
+ this.eventPayload = hsm.eventPayload;
175
+ }
176
+ }
177
+ export class TransitionError extends RuntimeError {
178
+ failedStateName;
179
+ failedCallback;
180
+ fromStateName;
181
+ toStateName;
182
+ constructor(hsm, cause, failedStateName, failedCallback, fromStateName, toStateName) {
183
+ super('TransitionError', hsm, `${failedStateName}.${failedCallback}() has failed while executing a transition from ${fromStateName} to ${toStateName}`, cause);
184
+ this.failedStateName = failedStateName;
185
+ this.failedCallback = failedCallback;
186
+ this.fromStateName = fromStateName;
187
+ this.toStateName = toStateName;
188
+ }
189
+ }
190
+ export class EventHandlerError extends RuntimeError {
191
+ constructor(hsm, cause) {
192
+ super('EventHandlerError', hsm, `an error was thrown while executing event handler #${hsm.eventName} in state ${hsm.currentStateName}`, cause);
193
+ }
194
+ }
195
+ export class UnhandledEventError extends RuntimeError {
196
+ constructor(hsm) {
197
+ super('UnhandledEventError', hsm, `event #${hsm.eventName} was unhandled in state ${hsm.currentStateName}`);
198
+ }
199
+ }
200
+ export class InitialStateError extends Error {
201
+ targetStateName;
202
+ constructor(targetState) {
203
+ super(`State '${getStateName(Object.getPrototypeOf(targetState.prototype).constructor)}' must not have more than one initial state`);
204
+ this.name = 'InitialStateError';
205
+ this.targetStateName = getStateName(targetState);
206
+ }
207
+ }
208
+ export class FatalError extends RuntimeError {
209
+ constructor(hsm, cause) {
210
+ super('FatalError', hsm, `onError() has thrown ${quoteError(cause)}`, cause);
211
+ }
212
+ }
213
+ export class InitializationError extends HsmError {
214
+ failedState;
215
+ constructor(hsm, failedState, cause) {
216
+ super('InitializationError', hsm, `state ${getStateName(failedState)} has thrown ${quoteError(cause)} during initialization`, cause);
217
+ this.failedState = failedState;
218
+ }
219
+ }
220
+ export class FatalErrorState extends TopState {
221
+ }
222
+ defineStateName(TopState, 'TopState');
223
+ defineStateName(FatalErrorState, 'FatalErrorState');
224
+ /** @internal */
225
+ export function lookupHandlerState(hsm, eventName) {
226
+ let state = hsm.currentState;
227
+ while (true) {
228
+ const prototype = state.prototype;
229
+ if (Object.prototype.hasOwnProperty.call(prototype, eventName)) {
230
+ return getStateName(state);
231
+ }
232
+ if (state === TopState) {
233
+ return undefined;
234
+ }
235
+ state = Object.getPrototypeOf(state);
236
+ }
237
+ }
238
+ /** @internal */
239
+ export function lookupEventHandler(hsm, eventName) {
240
+ let state = hsm.currentState;
241
+ while (true) {
242
+ const prototype = state.prototype;
243
+ if (Object.prototype.hasOwnProperty.call(prototype, eventName)) {
244
+ return prototype[eventName];
245
+ }
246
+ if (state === TopState) {
247
+ return undefined;
248
+ }
249
+ state = Object.getPrototypeOf(state);
250
+ }
251
+ }
252
+ export function InitialState(TargetState) {
253
+ const ParentOfTargetState = Object.getPrototypeOf(TargetState.prototype).constructor;
254
+ if (hasInitialState(ParentOfTargetState))
255
+ throw new InitialStateError(TargetState);
256
+ Object.defineProperty(TargetState, '_isInitialState', {
257
+ value: true,
258
+ writable: false,
259
+ configurable: false,
260
+ enumerable: false,
261
+ });
262
+ Object.defineProperty(ParentOfTargetState, '_initialState', {
263
+ value: TargetState,
264
+ writable: false,
265
+ configurable: false,
266
+ enumerable: false,
267
+ });
268
+ }
269
+ export function registerStateNames(exports) {
270
+ for (const [exportName, value] of Object.entries(exports)) {
271
+ if (typeof value !== 'function')
272
+ continue;
273
+ const prototype = value.prototype;
274
+ if (typeof prototype !== 'object' || prototype === null || !TopState.prototype.isPrototypeOf(prototype))
275
+ continue;
276
+ defineStateName(value, exportName);
277
+ StateGraph.forRoot(findRootState(value)).register(value);
278
+ }
279
+ }
280
+ //#endregion
281
+ //#region protocol-index
282
+ //#region Protocol collision errors
283
+ /** Thrown at construction when `Config`, state handlers, and the protocol index disagree. */
284
+ export class ProtocolCollisionError extends Error {
285
+ stateClass;
286
+ symbol;
287
+ // prettier-ignore
288
+ constructor(message, stateClass, symbol) {
289
+ super(message);
290
+ this.stateClass = stateClass;
291
+ this.symbol = symbol;
292
+ this.name = 'ProtocolCollisionError';
293
+ }
294
+ static reservedOnState(stateClass, symbol) {
295
+ return new ProtocolCollisionError(`ihsm: state class "${stateClass}" defines reserved symbol "${symbol}" — rename the protocol method; reserved symbols are: ${ReservedNames.join(', ')}`, stateClass, symbol);
296
+ }
297
+ }
298
+ //#endregion
299
+ //#region State graph
300
+ const stateGraphKey = Symbol('ihsm.stateGraph');
301
+ function findRootState(state) {
302
+ let current = state;
303
+ while (true) {
304
+ const parent = Object.getPrototypeOf(current);
305
+ if (parent === TopState) {
306
+ return current;
307
+ }
308
+ const grandparent = Object.getPrototypeOf(parent);
309
+ if (grandparent === TopState) {
310
+ return current;
311
+ }
312
+ current = parent;
313
+ }
314
+ }
315
+ function handlerBucket(handler) {
316
+ const source = Function.prototype.toString.call(handler);
317
+ if (handler.constructor.name === 'AsyncFunction' || /\basync\b/.test(source)) {
318
+ return 'services';
319
+ }
320
+ if (/\breturn\s+[^(;]/.test(source)) {
321
+ return 'services';
322
+ }
323
+ return 'notifications';
324
+ }
325
+ /** Per–root-state registry of state classes for protocol scanning. */
326
+ export class StateGraph {
327
+ states = new Set();
328
+ register(state) {
329
+ this.states.add(state);
330
+ }
331
+ collect(topState) {
332
+ if (this.states.size > 0) {
333
+ return [...this.states];
334
+ }
335
+ return StateGraph.collectAlongPrototypeChain(topState);
336
+ }
337
+ static forRoot(root) {
338
+ const host = root;
339
+ let graph = host[stateGraphKey];
340
+ if (graph === undefined) {
341
+ graph = new StateGraph();
342
+ host[stateGraphKey] = graph;
343
+ }
344
+ return graph;
345
+ }
346
+ static collectAlongPrototypeChain(topState) {
347
+ const collected = new Set();
348
+ let current = topState;
349
+ while (current !== undefined && current !== TopState) {
350
+ collected.add(current);
351
+ current = Object.getPrototypeOf(current);
352
+ if (current === TopState)
353
+ break;
354
+ }
355
+ return [...collected];
356
+ }
357
+ }
358
+ //#endregion
359
+ //#region Protocol index cache
360
+ const indexByRoot = new WeakMap();
361
+ export function cacheProtocolIndex(topState, index) {
362
+ indexByRoot.set(topState, index);
363
+ return index;
364
+ }
365
+ export function protocolIndexFor(topState) {
366
+ return indexByRoot.get(topState);
367
+ }
368
+ //#endregion
369
+ //#region Protocol index
370
+ const reservedSet = new Set(['ctx', 'hsm', 'notify', 'notifyNow', 'onEntry', 'onExit', 'onError', 'onUnhandled']);
371
+ export const ReservedNames = ['ctx', 'hsm', 'notify', 'notifyNow', 'onEntry', 'onExit', 'onError', 'onUnhandled'];
372
+ const lifecycleHooks = new Set(['onEntry', 'onExit', 'onError', 'onUnhandled']);
373
+ class ProtocolIndexImpl {
374
+ slots;
375
+ constructor(slots) {
376
+ this.slots = slots;
377
+ }
378
+ get(name) {
379
+ return this.slots.get(name);
380
+ }
381
+ *entries(kind) {
382
+ for (const [name, slot] of this.slots) {
383
+ if (kind === 'root' && (slot.bucket === 'services' || slot.bucket === 'notifications')) {
384
+ yield [name, slot];
385
+ }
386
+ else if (kind === 'inbound' && slot.bucket !== 'internalServices') {
387
+ yield [name, slot];
388
+ }
389
+ else if (kind === 'child' || kind === 'test') {
390
+ yield [name, slot];
391
+ }
392
+ }
393
+ }
394
+ }
395
+ /** Build a protocol index by scanning handler methods on the state graph (`async` → services, otherwise notifications). */
396
+ export function buildProtocolIndex(topState) {
397
+ const states = StateGraph.forRoot(findRootState(topState)).collect(topState);
398
+ for (const state of states) {
399
+ const prototype = state.prototype;
400
+ for (const name of Object.getOwnPropertyNames(prototype)) {
401
+ if (!reservedSet.has(name) || lifecycleHooks.has(name) || name === 'constructor')
402
+ continue;
403
+ if (typeof prototype[name] === 'function') {
404
+ throw ProtocolCollisionError.reservedOnState(getStateName(state), name);
405
+ }
406
+ }
407
+ }
408
+ const slots = new Map();
409
+ const seen = new Set();
410
+ for (const state of states) {
411
+ const prototype = state.prototype;
412
+ for (const name of Object.getOwnPropertyNames(prototype)) {
413
+ if (reservedSet.has(name) || lifecycleHooks.has(name) || name === 'constructor')
414
+ continue;
415
+ const handler = prototype[name];
416
+ if (typeof handler !== 'function' || seen.has(name))
417
+ continue;
418
+ seen.add(name);
419
+ const bucket = handlerBucket(handler);
420
+ slots.set(name, { bucket, name });
421
+ }
422
+ }
423
+ return new ProtocolIndexImpl(slots);
424
+ }
425
+ //#endregion
426
+ //#region handles
427
+ /** Thrown when a service client call exceeds `{ timeoutMs }`. */
428
+ export class CallTimeoutError extends Error {
429
+ method;
430
+ // prettier-ignore
431
+ constructor(method) {
432
+ super(`ihsm: service "${method}" timed out`);
433
+ this.method = method;
434
+ this.name = 'CallTimeoutError';
435
+ }
436
+ }
437
+ export const kMachine = Symbol('ihsm.machine');
438
+ export function isServiceCallOptions(value) {
439
+ if (value === null || typeof value !== 'object') {
440
+ return false;
441
+ }
442
+ const record = value;
443
+ if (!('timeoutMs' in record)) {
444
+ return false;
445
+ }
446
+ const timeoutMs = record.timeoutMs;
447
+ return timeoutMs === undefined || (typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs >= 0);
448
+ }
449
+ export function splitServiceArgs(args) {
450
+ if (args.length === 0) {
451
+ return { callArgs: [], timeoutMs: undefined };
452
+ }
453
+ const last = args[args.length - 1];
454
+ if (!isServiceCallOptions(last)) {
455
+ return { callArgs: [...args], timeoutMs: undefined };
456
+ }
457
+ const timeoutMs = last.timeoutMs;
458
+ if (timeoutMs === undefined) {
459
+ return { callArgs: [...args], timeoutMs: undefined };
460
+ }
461
+ return { callArgs: args.slice(0, -1), timeoutMs };
462
+ }
463
+ /**
464
+ * Race a service-call promise against a `timeoutMs` deadline.
465
+ *
466
+ * The deadline is armed through `timer` — the actor's port timer service — so that under a
467
+ * {@link TestPort} virtual clock a call timeout is driven by `port.advance(...)` and stays
468
+ * fully deterministic. When no port timer is available the host timer is used (production
469
+ * `Port` already delegates to the host timer, so behaviour there is unchanged).
470
+ */
471
+ export function serviceCallWithTimeout(promise, method, timeoutMs, timer) {
472
+ if (timeoutMs === 0) {
473
+ return Promise.reject(new CallTimeoutError(method));
474
+ }
475
+ const arm = (callback) => (timer !== undefined ? timer.setTimeout(callback, timeoutMs) : globalThis.setTimeout(callback, timeoutMs));
476
+ const disarm = (handle) => (timer !== undefined ? timer.clearTimeout(handle) : globalThis.clearTimeout(handle));
477
+ return new Promise((resolve, reject) => {
478
+ const handle = arm(() => {
479
+ reject(new CallTimeoutError(method));
480
+ });
481
+ promise.then(value => {
482
+ disarm(handle);
483
+ resolve(value);
484
+ }, err => {
485
+ disarm(handle);
486
+ reject(err);
487
+ });
488
+ });
489
+ }
490
+ const facetProtoCache = new WeakMap();
491
+ /**
492
+ * Build (and cache) the frozen prototype for one facet of one embodiment.
493
+ * Delivery mode is fixed by the facet — `call` dispatches services, `notify`
494
+ * the default queue, `notifyNow` the priority queue — so the runtime no longer
495
+ * needs to infer it per handler at the call site.
496
+ */
497
+ function getFacetProto(topState, index, kind, facet) {
498
+ let map = facetProtoCache.get(topState);
499
+ if (map === undefined) {
500
+ map = new Map();
501
+ facetProtoCache.set(topState, map);
502
+ }
503
+ const cacheKey = `${kind}:${facet}`;
504
+ let proto = map.get(cacheKey);
505
+ if (proto === undefined) {
506
+ const built = Object.create(null);
507
+ // Dispatch mode is fixed by the facet, not by the handler's signature, so
508
+ // every member visible to this embodiment kind is exposed on every facet.
509
+ // The static types (`NotifyFacet` / `CallFacet`) are the gate that decides
510
+ // which members are legal to reach through which facet; the runtime never
511
+ // needs to guess service-vs-notification from the handler's return type.
512
+ const queue = facet === 'notifyNow' ? 'priority' : 'default';
513
+ for (const [name] of index.entries(kind)) {
514
+ if (facet === 'call') {
515
+ built[name] = function (...args) {
516
+ const { callArgs, timeoutMs } = splitServiceArgs(args);
517
+ const machine = this[kMachine];
518
+ const begin = machine.beginOutboundCall?.(name, machine.actorUuid);
519
+ const promise = machine.dispatchService(name, callArgs).then(value => {
520
+ machine.endOutboundCall?.(begin, 'ok');
521
+ return value;
522
+ }, cause => {
523
+ const err = asError(cause);
524
+ machine.endOutboundCall?.(begin, 'error', err);
525
+ throw err;
526
+ });
527
+ return timeoutMs === undefined ? promise : serviceCallWithTimeout(promise, name, timeoutMs, machine.callTimer);
528
+ };
529
+ }
530
+ else {
531
+ built[name] = function (...args) {
532
+ this[kMachine].dispatchNotification(name, args, queue);
533
+ };
534
+ }
535
+ }
536
+ proto = Object.freeze(built);
537
+ map.set(cacheKey, proto);
538
+ }
539
+ return proto;
540
+ }
541
+ function createFacet(machine, topState, index, kind, facet) {
542
+ const facetHandle = Object.create(getFacetProto(topState, index, kind, facet));
543
+ Object.defineProperty(facetHandle, kMachine, { value: machine, enumerable: false });
544
+ return facetHandle;
545
+ }
546
+ /** @internal */
547
+ export function createActorHandle(machine, topState, index, kind) {
548
+ // Faceted surface only — protocol members live under `notify` / `notifyNow`
549
+ // / `call`. There are no flat methods on the handle, so `actor.theEvent()`
550
+ // is a compile-time and runtime error; callers must go through a facet.
551
+ const handle = {};
552
+ Object.defineProperty(handle, kMachine, { value: machine, enumerable: false });
553
+ if (kind === 'test') {
554
+ Object.defineProperty(handle, 'ctx', {
555
+ enumerable: true,
556
+ get() {
557
+ return machine.ctx;
558
+ },
559
+ });
560
+ }
561
+ Object.defineProperty(handle, 'notify', { value: createFacet(machine, topState, index, kind, 'notify'), enumerable: true });
562
+ Object.defineProperty(handle, 'notifyNow', { value: createFacet(machine, topState, index, kind, 'notifyNow'), enumerable: true });
563
+ Object.defineProperty(handle, 'call', { value: createFacet(machine, topState, index, kind, 'call'), enumerable: true });
564
+ Object.defineProperty(handle, 'id', {
565
+ enumerable: true,
566
+ get() {
567
+ return machine.actorUuid;
568
+ },
569
+ });
570
+ handle.hsm = machine.actorHsmFor(kind);
571
+ return handle;
572
+ }
573
+ const selfProtoCache = new WeakMap();
574
+ /** @internal */
575
+ export function getSelfNotificationsProto(topState, index, queue) {
576
+ let map = selfProtoCache.get(topState);
577
+ if (map === undefined) {
578
+ map = new Map();
579
+ selfProtoCache.set(topState, map);
580
+ }
581
+ let proto = map.get(queue);
582
+ if (proto === undefined) {
583
+ const built = Object.create(null);
584
+ // Self-send always uses notification dispatch (you cannot await a service
585
+ // on yourself); `SelfNotifications<C>` is the static gate for which members
586
+ // are reachable, so expose every member visible to the handler embodiment.
587
+ for (const [name] of index.entries('inbound')) {
588
+ built[name] = function (...args) {
589
+ this[kMachine].dispatchNotification(name, args, queue);
590
+ };
591
+ }
592
+ proto = Object.freeze(built);
593
+ map.set(queue, proto);
594
+ }
595
+ return proto;
596
+ }
597
+ export function createSelfNotifications(machine, topState, index, queue) {
598
+ const handle = Object.create(getSelfNotificationsProto(topState, index, queue));
599
+ Object.defineProperty(handle, kMachine, { value: machine, enumerable: false });
600
+ return handle;
601
+ }
602
+ //#region dispatch-guard
603
+ /// <reference types="node" />
604
+ /** Thrown in debug builds when a service targets the machine currently dispatching. */
605
+ export class SelfCallDeadlockError extends Error {
606
+ constructor() {
607
+ super('ihsm: awaiting a service on your own machine from inside your own dispatch deadlocks');
608
+ this.name = 'SelfCallDeadlockError';
609
+ }
610
+ }
611
+ function errorPhaseFromError(err) {
612
+ if (err instanceof TransitionError) {
613
+ return err.failedCallback === 'onEntry' ? 'onEntry' : 'onExit';
614
+ }
615
+ if (err instanceof UnhandledEventError)
616
+ return 'unhandled';
617
+ if (err instanceof InitializationError)
618
+ return 'initialize';
619
+ if (err instanceof EventHandlerError)
620
+ return 'handler';
621
+ return 'handler';
622
+ }
623
+ /**
624
+ * Lazy Node AsyncLocalStorage for non-production deadlock detection.
625
+ * State lives in a closure — no exported mutable slot.
626
+ */
627
+ export const dispatchContext = (() => {
628
+ let storage = undefined;
629
+ function get() {
630
+ if (storage !== undefined) {
631
+ return storage ?? undefined;
632
+ }
633
+ if (typeof process === 'undefined' || process.versions?.node === undefined) {
634
+ storage = null;
635
+ return undefined;
636
+ }
637
+ try {
638
+ // Dynamic require — no unconditional `node:async_hooks` import (browser-safe bundle).
639
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
640
+ const hooks = require('node:async_hooks');
641
+ storage = new hooks.AsyncLocalStorage();
642
+ return storage;
643
+ }
644
+ catch {
645
+ storage = null;
646
+ return undefined;
647
+ }
648
+ }
649
+ function resetInit() {
650
+ storage = undefined;
651
+ }
652
+ function markUnavailable() {
653
+ storage = null;
654
+ }
655
+ return { get, resetInit, markUnavailable };
656
+ })();
657
+ /**
658
+ * Best-effort current runtime trace anchor (`actorUuid`, `macrostepId`, `stepSeq`) for user code.
659
+ * Returns `undefined` when called outside an active handler dispatch turn.
660
+ */
661
+ export function currentTraceAnchor() {
662
+ const token = dispatchContext.get()?.getStore();
663
+ if (token?.actorUuid === undefined)
664
+ return undefined;
665
+ return {
666
+ actorUuid: token.actorUuid,
667
+ macrostepId: token.macrostepId,
668
+ stepSeq: token.stepSeq,
669
+ };
670
+ }
671
+ /** Lazy ALS carrying the currently-executing proxied port call (when instrumentation is active). */
672
+ const portCallContext = (() => {
673
+ let storage = undefined;
674
+ function get() {
675
+ if (storage !== undefined) {
676
+ return storage ?? undefined;
677
+ }
678
+ if (typeof process === 'undefined' || process.versions?.node === undefined) {
679
+ storage = null;
680
+ return undefined;
681
+ }
682
+ try {
683
+ // Dynamic require — keeps browser bundles free of `node:async_hooks`.
684
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
685
+ const hooks = require('node:async_hooks');
686
+ storage = new hooks.AsyncLocalStorage();
687
+ return storage;
688
+ }
689
+ catch {
690
+ storage = null;
691
+ return undefined;
692
+ }
693
+ }
694
+ return { get };
695
+ })();
696
+ //#region transition-routines
697
+ /** Thrown when a generated transition table's graph hash does not match the scanned hierarchy. */
698
+ export class TransitionTableError extends Error {
699
+ constructor(message) {
700
+ super(message);
701
+ this.name = 'TransitionTableError';
702
+ }
703
+ }
704
+ /** Compute the LCA transition path (same algorithm as `dispatch.production.ts`). */
705
+ export function planTransitionClasses(srcState, destState) {
706
+ const src = srcState;
707
+ let dst = destState;
708
+ let srcPath = [];
709
+ const end = TopState;
710
+ const srcIndex = new Map();
711
+ const dstPath = [];
712
+ let cur = src;
713
+ let i = 0;
714
+ while (cur !== end) {
715
+ srcPath.push(cur);
716
+ srcIndex.set(cur, i);
717
+ cur = Object.getPrototypeOf(cur);
718
+ ++i;
719
+ }
720
+ cur = dst;
721
+ while (cur !== end) {
722
+ const index = srcIndex.get(cur);
723
+ if (index !== undefined) {
724
+ srcPath = srcPath.slice(0, index);
725
+ break;
726
+ }
727
+ dstPath.unshift(cur);
728
+ cur = Object.getPrototypeOf(cur);
729
+ }
730
+ while (hasInitialState(dst)) {
731
+ dst = getInitialState(dst);
732
+ dstPath.push(dst);
733
+ }
734
+ let finalState;
735
+ if (dstPath.length !== 0) {
736
+ finalState = dstPath[dstPath.length - 1];
737
+ }
738
+ else if (srcPath.length !== 0) {
739
+ finalState = Object.getPrototypeOf(srcPath[srcPath.length - 1]);
740
+ }
741
+ else {
742
+ finalState = undefined;
743
+ }
744
+ return { exit: srcPath, entry: dstPath, finalState };
745
+ }
746
+ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, toStateName, style, tracer, hookEvents) {
747
+ const statePrototype = state.prototype;
748
+ const stateName = getStateName(state);
749
+ const hasHook = Object.prototype.hasOwnProperty.call(statePrototype, hook);
750
+ // Emit hook tracer callbacks for real (own) hooks at verbose, or always for a structural seam
751
+ // (instrumentation) whose entry/exit spans are not TraceLevel-gated (spec §4.8). Skipped default
752
+ // hooks are never eventized.
753
+ const emitHookEvents = (style === 'verbose' || hookEvents) && hasHook;
754
+ if ((style === 'verbose' || style === 'debug') && !hasHook) {
755
+ if (style === 'verbose') {
756
+ tracer?.traceHookSkipped(stateName, hook);
757
+ }
758
+ return;
759
+ }
760
+ try {
761
+ if (emitHookEvents) {
762
+ tracer?.traceHookStart?.(stateName, hook);
763
+ }
764
+ const res = statePrototype[hook].call(instance);
765
+ if (res) {
766
+ await res;
767
+ }
768
+ if (emitHookEvents) {
769
+ tracer?.traceHookDone(stateName, hook);
770
+ }
771
+ }
772
+ catch (cause) {
773
+ if (emitHookEvents) {
774
+ tracer?.traceHookError(stateName, hook, cause);
775
+ }
776
+ throw new TransitionError(hsm, asError(cause), stateName, hook, fromStateName, toStateName);
777
+ }
778
+ }
779
+ /**
780
+ * Execute a planned transition path with production or verbose semantics.
781
+ *
782
+ * Used by the runtime dispatch layer, generated transition tables (`@ihsm/tools`), and oracle tests.
783
+ */
784
+ export async function executeTransitionRoutine(hsm, instance, plan, srcState, dstState, options = {}) {
785
+ const style = options.style ?? 'production';
786
+ const tracer = options.tracer;
787
+ const hookEvents = options.hookEvents ?? false;
788
+ const fromStateName = getStateName(srcState);
789
+ const toStateName = getStateName(dstState);
790
+ // The structural transition span fires whenever a tracer is attached (the instrumentation seam is
791
+ // not TraceLevel-gated); the console tracer is only ever supplied off-PRODUCTION, so this is a
792
+ // no-op there unless an explicit instrumentation tracer is present.
793
+ tracer?.traceTransitionStart(fromStateName, toStateName);
794
+ for (const state of plan.exit) {
795
+ await invokeLifecycleHook(hsm, instance, state, 'onExit', fromStateName, toStateName, style, tracer, hookEvents);
796
+ }
797
+ let initializeOpened = false;
798
+ for (const state of plan.entry) {
799
+ if (!initializeOpened && state !== dstState) {
800
+ tracer?.traceInitializeStart?.(toStateName);
801
+ initializeOpened = true;
802
+ }
803
+ await invokeLifecycleHook(hsm, instance, state, 'onEntry', fromStateName, toStateName, style, tracer, hookEvents);
804
+ }
805
+ if (initializeOpened) {
806
+ const finalName = plan.finalState !== undefined ? getStateName(plan.finalState) : toStateName;
807
+ tracer?.traceInitializeDone?.(finalName);
808
+ }
809
+ const applyState = (next) => {
810
+ if (options.setCurrentState) {
811
+ options.setCurrentState(next);
812
+ }
813
+ else if ('currentState' in hsm) {
814
+ hsm.currentState = next;
815
+ }
816
+ };
817
+ if (style === 'verbose') {
818
+ const finalState = plan.entry.length !== 0 ? plan.entry[plan.entry.length - 1] : plan.exit.length !== 0 ? Object.getPrototypeOf(plan.exit[plan.exit.length - 1]) : srcState;
819
+ tracer?.traceTransitionDone(getStateName(finalState));
820
+ applyState(finalState);
821
+ return;
822
+ }
823
+ if (style === 'debug' && plan.finalState) {
824
+ tracer?.traceTransitionDone(getStateName(plan.finalState));
825
+ applyState(plan.finalState);
826
+ return;
827
+ }
828
+ if (plan.finalState) {
829
+ applyState(plan.finalState);
830
+ // Close the structural transition span on the PRODUCTION path (verbose/debug returned above).
831
+ tracer?.traceTransitionDone(getStateName(plan.finalState));
832
+ }
833
+ }
834
+ export function createTransitionTracer(hsm) {
835
+ return {
836
+ traceTransitionStart(fromStateName, toStateName) {
837
+ hsm._tracePush(`transition from ${fromStateName} to ${toStateName}`, `started transition from ${fromStateName} to ${toStateName} `);
838
+ },
839
+ traceInitializeStart(stateName) {
840
+ hsm._tracePush(`initialize ${stateName}`, `started initialize drill-down from ${stateName}`);
841
+ },
842
+ traceInitializeDone(finalStateName) {
843
+ hsm._tracePopDone(`done initialize drill-down at ${finalStateName}`);
844
+ },
845
+ traceHookDone(stateName, hook) {
846
+ hsm._traceWrite(`${stateName}.${hook}() done`);
847
+ },
848
+ traceHookSkipped(stateName, hook) {
849
+ hsm._traceWrite(`${stateName}.${hook}() skipped: default empty implementation`);
850
+ },
851
+ traceHookError(stateName, hook, cause) {
852
+ hsm._tracePopError(`${stateName}.${hook}() has thrown ${quoteUnknown(cause)}`);
853
+ },
854
+ traceTransitionDone(finalStateName) {
855
+ hsm._tracePopDone(`final state is ${finalStateName}`);
856
+ },
857
+ };
858
+ }
859
+ /** Collect canonical transition trace lines (for oracle comparison). */
860
+ export function transitionTraceLines(lines) {
861
+ return lines
862
+ .map(line => {
863
+ const idx = line.indexOf(': ');
864
+ return idx >= 0 ? line.slice(idx + 2) : line;
865
+ })
866
+ .filter(line => line.startsWith('started transition from ') || line.endsWith('.onExit() done') || line.endsWith('.onEntry() done') || line.includes('.onExit() skipped:') || line.includes('.onEntry() skipped:') || line.includes('.onExit() has thrown') || line.includes('.onEntry() has thrown') || line.startsWith('done: final state is ') || (line.startsWith('failure: ') && line.includes('.onExit() has thrown')) || (line.startsWith('failure: ') && line.includes('.onEntry() has thrown')));
867
+ }
868
+ //#region actor-dispatch
869
+ class RuntimeTransitionRoutine {
870
+ plan;
871
+ constructor(plan) {
872
+ this.plan = plan;
873
+ }
874
+ async execute(hsm, srcState, dstState) {
875
+ const style = hsm.traceLevel === TraceLevel.PRODUCTION ? 'production' : hsm.traceLevel === TraceLevel.DEBUG ? 'debug' : 'verbose';
876
+ const machine = hsm;
877
+ const instTracer = machine.instrumentation?.transition;
878
+ const tracer = instTracer ?? (style !== 'production' ? createTransitionTracer(hsm) : undefined);
879
+ await executeTransitionRoutine(hsm, hsm._instance, this.plan, srcState, dstState, {
880
+ style,
881
+ ...(tracer !== undefined ? { tracer } : {}),
882
+ // The instrumentation seam's entry/exit spans are structural (not TraceLevel-gated); the
883
+ // console tracer keeps its verbose-only gating.
884
+ hookEvents: instTracer !== undefined,
885
+ setCurrentState: state => {
886
+ hsm.currentState = state;
887
+ },
888
+ });
889
+ }
890
+ }
891
+ export class RuntimeTransitionResolver {
892
+ cache = new Map();
893
+ hasCached(src, dest) {
894
+ return this.cache.has(getTransitionKey(src, dest));
895
+ }
896
+ resolve(src, dest) {
897
+ const key = getTransitionKey(src, dest);
898
+ let routine = this.cache.get(key);
899
+ if (routine === undefined) {
900
+ routine = new RuntimeTransitionRoutine(planTransitionClasses(src, dest));
901
+ this.cache.set(key, routine);
902
+ }
903
+ return routine;
904
+ }
905
+ }
906
+ /** @internal */
907
+ export async function executePendingTransition(host, resolver) {
908
+ if (host._transitionState === undefined) {
909
+ if (host.traceLevel === TraceLevel.VERBOSE_DEBUG) {
910
+ host._traceWrite('no transition requested');
911
+ }
912
+ return;
913
+ }
914
+ try {
915
+ const srcState = host.currentState;
916
+ const destState = host._transitionState;
917
+ if (host.traceLevel === TraceLevel.VERBOSE_DEBUG) {
918
+ host._traceWrite(`requested transition from ${getStateName(srcState)} to ${getStateName(destState)} `);
919
+ const runtimeResolver = resolver;
920
+ if (runtimeResolver.hasCached(srcState, destState)) {
921
+ host._traceWrite(`transition cache hit for ${getStateName(srcState)} to ${getStateName(destState)} `);
922
+ }
923
+ else {
924
+ host._traceWrite(`transition cache miss for ${getStateName(srcState)} to ${getStateName(destState)} `);
925
+ }
926
+ }
927
+ try {
928
+ await resolver.resolve(srcState, destState).execute(host, srcState, destState);
929
+ }
930
+ catch (transitionError) {
931
+ host.currentState = FatalErrorState;
932
+ throw transitionError;
933
+ }
934
+ }
935
+ finally {
936
+ host._transitionState = undefined;
937
+ }
938
+ }
939
+ //#region Service / notification dispatch tasks
940
+ async function completePendingTransitions(host, resolver, onComplete) {
941
+ await executePendingTransition(host, resolver);
942
+ onComplete();
943
+ }
944
+ async function doError(host, resolver, err, onComplete) {
945
+ host._transitionState = undefined;
946
+ const messageHandler = host.currentState.prototype.onError;
947
+ try {
948
+ const result = messageHandler.call(host._instance, new EventHandlerError(host, err));
949
+ if (result) {
950
+ await result;
951
+ }
952
+ await completePendingTransitions(host, resolver, onComplete);
953
+ }
954
+ catch (recoveryErr) {
955
+ if (recoveryErr instanceof TransitionError) {
956
+ throw new FatalError(host, recoveryErr);
957
+ }
958
+ const recoveryError = asError(recoveryErr);
959
+ host.transition(FatalErrorState);
960
+ await completePendingTransitions(host, resolver, onComplete);
961
+ throw new FatalError(host, recoveryError);
962
+ }
963
+ }
964
+ async function doUnhandledEvent(host, resolver, error, onComplete) {
965
+ try {
966
+ const result = host.currentState.prototype.onUnhandled.call(host._instance, error);
967
+ if (result) {
968
+ await result;
969
+ }
970
+ await completePendingTransitions(host, resolver, onComplete);
971
+ }
972
+ catch (recoveryErr) {
973
+ if (recoveryErr instanceof TransitionError) {
974
+ host.currentState = FatalErrorState;
975
+ throw recoveryErr;
976
+ }
977
+ await doError(host, resolver, asError(recoveryErr), onComplete);
978
+ }
979
+ }
980
+ async function invokeHandler(host, resolver, name, args, options = {}) {
981
+ const recover = options.recover ?? false;
982
+ const finishEvent = () => {
983
+ host._currentEventName = undefined;
984
+ host._currentEventPayload = undefined;
985
+ };
986
+ host._currentEventName = name;
987
+ host._currentEventPayload = [...args];
988
+ try {
989
+ const eventHandler = lookupEventHandler(host, name);
990
+ if (!eventHandler) {
991
+ await doUnhandledEvent(host, resolver, new UnhandledEventError(host), finishEvent);
992
+ return undefined;
993
+ }
994
+ try {
995
+ const result = eventHandler.call(host._instance, ...args);
996
+ const settled = result instanceof Promise ? await result : result;
997
+ await completePendingTransitions(host, resolver, finishEvent);
998
+ return settled;
999
+ }
1000
+ catch (recoveryErr) {
1001
+ if (recoveryErr instanceof UnhandledEventError) {
1002
+ await doUnhandledEvent(host, resolver, recoveryErr, finishEvent);
1003
+ return undefined;
1004
+ }
1005
+ if (recoveryErr instanceof TransitionError) {
1006
+ finishEvent();
1007
+ throw recoveryErr;
1008
+ }
1009
+ if (recover) {
1010
+ await doError(host, resolver, asError(recoveryErr), finishEvent);
1011
+ return undefined;
1012
+ }
1013
+ finishEvent();
1014
+ throw asError(recoveryErr);
1015
+ }
1016
+ }
1017
+ catch (err) {
1018
+ finishEvent();
1019
+ throw err;
1020
+ }
1021
+ }
1022
+ async function executeInitProduction(hsm) {
1023
+ let currState = hsm.topState;
1024
+ try {
1025
+ while (true) {
1026
+ const proto = currState.prototype;
1027
+ if (proto.hasOwnProperty('onEntry')) {
1028
+ proto.onEntry.call(hsm._instance);
1029
+ }
1030
+ if (hasInitialState(currState)) {
1031
+ currState = getInitialState(currState);
1032
+ }
1033
+ else
1034
+ break;
1035
+ }
1036
+ hsm.currentState = currState;
1037
+ }
1038
+ catch (cause) {
1039
+ if (cause instanceof TransitionError) {
1040
+ throw cause;
1041
+ }
1042
+ hsm.currentState = FatalErrorState;
1043
+ throw new InitializationError(hsm, currState, asError(cause));
1044
+ }
1045
+ }
1046
+ async function executeInitDebug(hsm) {
1047
+ hsm._traceWrite('begin initialization');
1048
+ try {
1049
+ let currState = hsm.topState;
1050
+ hsm._tracePush(`initialize`, `started initialization from ${getStateName(hsm.topState)}`);
1051
+ try {
1052
+ while (true) {
1053
+ if (Object.prototype.hasOwnProperty.call(currState.prototype, 'onEntry')) {
1054
+ currState.prototype['onEntry'].call(hsm._instance);
1055
+ }
1056
+ if (hasInitialState(currState)) {
1057
+ currState = getInitialState(currState);
1058
+ }
1059
+ else {
1060
+ break;
1061
+ }
1062
+ }
1063
+ hsm._tracePopDone(`final state is ${getStateName(currState)}`);
1064
+ hsm.currentState = currState;
1065
+ }
1066
+ catch (cause) {
1067
+ if (cause instanceof TransitionError) {
1068
+ throw cause;
1069
+ }
1070
+ hsm._tracePopError(`initialization failed from top state '${getStateName(hsm.topState)}' as ${getStateName(currState)}.onEntry() handler has raised ${quoteUnknown(cause)}; final state is ${getStateName(FatalErrorState)}`);
1071
+ hsm.currentState = FatalErrorState;
1072
+ throw new InitializationError(hsm, currState, asError(cause));
1073
+ }
1074
+ }
1075
+ finally {
1076
+ hsm._traceWrite('end initialization');
1077
+ }
1078
+ }
1079
+ async function executeInitVerbose(hsm) {
1080
+ hsm._traceWrite('begin initialization');
1081
+ try {
1082
+ let currState = hsm.topState;
1083
+ hsm._tracePush(`initialize`, `started initialization from ${getStateName(hsm.topState)}`);
1084
+ try {
1085
+ while (true) {
1086
+ if (Object.prototype.hasOwnProperty.call(currState.prototype, 'onEntry')) {
1087
+ currState.prototype['onEntry'].call(hsm._instance);
1088
+ hsm._traceWrite(`${getStateName(currState)}.onEntry() done`);
1089
+ }
1090
+ else {
1091
+ hsm._traceWrite(`skip ${getStateName(currState)}.onEntry(): default empty implementation`);
1092
+ }
1093
+ if (hasInitialState(currState)) {
1094
+ const newInitialState = getInitialState(currState);
1095
+ hsm._traceWrite(`${getStateName(currState)} initial state is ${getStateName(newInitialState)}`);
1096
+ currState = newInitialState;
1097
+ }
1098
+ else {
1099
+ hsm._traceWrite(`${getStateName(currState)} has no initial state; final state is ${getStateName(currState)}`);
1100
+ break;
1101
+ }
1102
+ }
1103
+ hsm._tracePopDone(`final state is ${getStateName(currState)}`);
1104
+ hsm.currentState = currState;
1105
+ }
1106
+ catch (cause) {
1107
+ if (cause instanceof TransitionError) {
1108
+ throw cause;
1109
+ }
1110
+ hsm._tracePopError(`initialization failed from top state '${getStateName(hsm.topState)}' as ${getStateName(currState)}.onEntry() handler has raised ${quoteUnknown(cause)}; final state is ${getStateName(FatalErrorState)}`);
1111
+ hsm.currentState = FatalErrorState;
1112
+ throw new InitializationError(hsm, currState, asError(cause));
1113
+ }
1114
+ }
1115
+ finally {
1116
+ hsm._traceWrite('end initialization');
1117
+ }
1118
+ }
1119
+ async function dispatchEventProduction(hsm, resolver, eventName, ...eventPayload) {
1120
+ await invokeHandler(hsm, resolver, eventName, eventPayload, { recover: true });
1121
+ }
1122
+ function debugFinishEventDispatch(hsm) {
1123
+ hsm._traceWrite(`end event dispatch`);
1124
+ hsm._currentEventName = undefined;
1125
+ hsm._currentEventPayload = undefined;
1126
+ }
1127
+ async function debugDoError(hsm, resolver, err, onComplete) {
1128
+ hsm._transitionState = undefined;
1129
+ hsm._tracePush(`error recovery`, `started error recovery`);
1130
+ try {
1131
+ hsm._tracePush('execute', 'started #onError handler execution');
1132
+ const result = hsm.currentState.prototype.onError.call(hsm._instance, new EventHandlerError(hsm, err));
1133
+ if (result) {
1134
+ await result;
1135
+ }
1136
+ hsm._tracePopDone('error handler execution successful');
1137
+ await completePendingTransitions(hsm, resolver, () => {
1138
+ hsm._tracePopDone('error recovery successful');
1139
+ onComplete();
1140
+ });
1141
+ }
1142
+ catch (recoveryErr) {
1143
+ hsm._tracePopError(`error handler execution failure: ${quoteUnknown(recoveryErr)}`);
1144
+ if (recoveryErr instanceof TransitionError) {
1145
+ hsm._tracePopError(`error recovery failure: ${quoteUnknown(recoveryErr)}`);
1146
+ throw new FatalError(hsm, recoveryErr);
1147
+ }
1148
+ const recoveryError = asError(recoveryErr);
1149
+ hsm.transition(FatalErrorState);
1150
+ await completePendingTransitions(hsm, resolver, () => {
1151
+ hsm._tracePopError(`error recovery failure: ${quoteUnknown(recoveryError)}`);
1152
+ onComplete();
1153
+ });
1154
+ throw new FatalError(hsm, recoveryError);
1155
+ }
1156
+ }
1157
+ async function debugDoUnhandledEvent(hsm, resolver, error, onComplete) {
1158
+ hsm._tracePush('unhandled recovery', `started unhandled event recovery`);
1159
+ try {
1160
+ hsm._tracePush('execute', 'started #onUnhandled handler execution');
1161
+ const result = hsm.currentState.prototype.onUnhandled.call(hsm._instance, error);
1162
+ if (result) {
1163
+ await result;
1164
+ }
1165
+ hsm._tracePopDone('unhandled handler execution successful');
1166
+ await completePendingTransitions(hsm, resolver, () => {
1167
+ hsm._tracePopDone('unhandled event recovery successful');
1168
+ onComplete();
1169
+ });
1170
+ }
1171
+ catch (recoveryErr) {
1172
+ hsm._tracePopError(`unhandled event recovery failure: ${quoteUnknown(recoveryErr)}`);
1173
+ if (recoveryErr instanceof TransitionError) {
1174
+ hsm.currentState = FatalErrorState;
1175
+ hsm._tracePopError(`unhandled event recovery failure: ${quoteUnknown(recoveryErr)}`);
1176
+ throw recoveryErr;
1177
+ }
1178
+ try {
1179
+ await debugDoError(hsm, resolver, asError(recoveryErr), () => {
1180
+ hsm._tracePopDone('unhandled event recovery successful');
1181
+ onComplete();
1182
+ });
1183
+ }
1184
+ catch (nestedErr) {
1185
+ hsm._tracePopError(`unhandled event recovery failure: ${quoteUnknown(nestedErr)}`);
1186
+ throw nestedErr;
1187
+ }
1188
+ }
1189
+ }
1190
+ async function dispatchEventDebug(hsm, resolver, eventName, ...eventPayload) {
1191
+ const eventLabel = String(eventName);
1192
+ hsm._traceWrite(`begin event dispatch of #${eventLabel}`);
1193
+ hsm._tracePush(`#${eventLabel}`, `started event dispatch`);
1194
+ hsm._currentEventName = eventLabel;
1195
+ hsm._currentEventPayload = eventPayload;
1196
+ try {
1197
+ const eventHandler = lookupEventHandler(hsm, eventName);
1198
+ if (!eventHandler) {
1199
+ try {
1200
+ await debugDoUnhandledEvent(hsm, resolver, new UnhandledEventError(hsm), () => {
1201
+ hsm._tracePopDone('event dispatch successful');
1202
+ debugFinishEventDispatch(hsm);
1203
+ });
1204
+ return;
1205
+ }
1206
+ catch (recoveryErr) {
1207
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(recoveryErr)}`);
1208
+ debugFinishEventDispatch(hsm);
1209
+ throw recoveryErr;
1210
+ }
1211
+ }
1212
+ try {
1213
+ hsm._tracePush('execute', 'started event handler execution');
1214
+ const result = eventHandler.call(hsm._instance, ...eventPayload);
1215
+ if (result) {
1216
+ await result;
1217
+ }
1218
+ hsm._tracePopDone('event handler execution successful');
1219
+ await completePendingTransitions(hsm, resolver, () => {
1220
+ hsm._tracePopDone(`event dispatch successful`);
1221
+ debugFinishEventDispatch(hsm);
1222
+ });
1223
+ }
1224
+ catch (recoveryErr) {
1225
+ hsm._tracePopError(quoteUnknown(recoveryErr));
1226
+ if (recoveryErr instanceof UnhandledEventError) {
1227
+ try {
1228
+ await debugDoUnhandledEvent(hsm, resolver, recoveryErr, () => {
1229
+ hsm._tracePopDone('event dispatch successful');
1230
+ debugFinishEventDispatch(hsm);
1231
+ });
1232
+ return;
1233
+ }
1234
+ catch (nestedErr) {
1235
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(nestedErr)}`);
1236
+ debugFinishEventDispatch(hsm);
1237
+ throw nestedErr;
1238
+ }
1239
+ }
1240
+ else if (recoveryErr instanceof TransitionError) {
1241
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(recoveryErr)}`);
1242
+ debugFinishEventDispatch(hsm);
1243
+ throw recoveryErr;
1244
+ }
1245
+ else {
1246
+ try {
1247
+ await debugDoError(hsm, resolver, asError(recoveryErr), () => {
1248
+ hsm._tracePopDone('event dispatch successful');
1249
+ debugFinishEventDispatch(hsm);
1250
+ });
1251
+ }
1252
+ catch (nestedErr) {
1253
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(nestedErr)}`);
1254
+ debugFinishEventDispatch(hsm);
1255
+ throw nestedErr;
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ catch (err) {
1261
+ debugFinishEventDispatch(hsm);
1262
+ throw err;
1263
+ }
1264
+ }
1265
+ function verboseFinishEventDispatch(hsm) {
1266
+ hsm._traceWrite(`end event dispatch`);
1267
+ hsm._currentEventName = undefined;
1268
+ hsm._currentEventPayload = undefined;
1269
+ }
1270
+ async function verboseDoError(hsm, resolver, err, onComplete) {
1271
+ hsm._transitionState = undefined;
1272
+ hsm._tracePush(`error recovery`, `started error recovery`);
1273
+ hsm._tracePush(`lookup`, `started lookup of #onError event handler`);
1274
+ let errorLookupState = hsm.currentState;
1275
+ let messageHandler;
1276
+ while (errorLookupState != TopState) {
1277
+ const errorPrototype = errorLookupState.prototype;
1278
+ if (Object.prototype.hasOwnProperty.call(errorPrototype, 'onError')) {
1279
+ hsm._tracePopDone(`found in state ${getStateName(errorLookupState)}`);
1280
+ messageHandler = errorPrototype['onError'];
1281
+ break;
1282
+ }
1283
+ hsm._traceWrite(`not found in state ${getStateName(errorLookupState)}`);
1284
+ errorLookupState = Object.getPrototypeOf(errorLookupState);
1285
+ }
1286
+ if (messageHandler === undefined) {
1287
+ hsm._tracePopDone(`found in state ${getStateName(TopState)}`);
1288
+ messageHandler = TopState.prototype.onError;
1289
+ }
1290
+ try {
1291
+ hsm._tracePush('execute', 'started #onError handler execution');
1292
+ const result = messageHandler.call(hsm._instance, new EventHandlerError(hsm, err));
1293
+ if (result) {
1294
+ await result;
1295
+ }
1296
+ hsm._tracePopDone('error handler execution successful');
1297
+ await completePendingTransitions(hsm, resolver, () => {
1298
+ hsm._tracePopDone('error recovery successful');
1299
+ onComplete();
1300
+ });
1301
+ }
1302
+ catch (recoveryErr) {
1303
+ hsm._tracePopError(`error handler execution failure: ${quoteUnknown(recoveryErr)}`);
1304
+ if (recoveryErr instanceof TransitionError) {
1305
+ hsm._tracePopError(`error recovery failure: ${quoteUnknown(recoveryErr)}`);
1306
+ throw recoveryErr;
1307
+ }
1308
+ const recoveryError = asError(recoveryErr);
1309
+ hsm.transition(FatalErrorState);
1310
+ await completePendingTransitions(hsm, resolver, () => {
1311
+ hsm._tracePopError(`error recovery failure: ${quoteUnknown(recoveryError)}`);
1312
+ onComplete();
1313
+ });
1314
+ throw new FatalError(hsm, recoveryError);
1315
+ }
1316
+ }
1317
+ async function verboseDoUnhandledEvent(hsm, resolver, error, onComplete) {
1318
+ hsm._tracePush('unhandled recovery', `started unhandled event recovery`);
1319
+ let unhandledLookupState = hsm.currentState;
1320
+ hsm._tracePush(`lookup`, `started lookup of #onUnhandled event handler`);
1321
+ let messageHandler;
1322
+ while (true) {
1323
+ const unhandledPrototype = unhandledLookupState.prototype;
1324
+ if (Object.prototype.hasOwnProperty.call(unhandledPrototype, 'onUnhandled')) {
1325
+ hsm._tracePopDone(`found in state ${getStateName(unhandledLookupState)}`);
1326
+ messageHandler = unhandledPrototype.onUnhandled;
1327
+ break;
1328
+ }
1329
+ hsm._traceWrite(`not found in state ${getStateName(unhandledLookupState)}`);
1330
+ unhandledLookupState = Object.getPrototypeOf(unhandledLookupState);
1331
+ if (unhandledLookupState == TopState) {
1332
+ hsm._tracePopDone(`found in state ${getStateName(unhandledLookupState)}`);
1333
+ messageHandler = unhandledPrototype.onUnhandled;
1334
+ break;
1335
+ }
1336
+ }
1337
+ try {
1338
+ hsm._tracePush('execute', 'started #onUnhandled handler execution');
1339
+ const result = messageHandler.call(hsm._instance, error);
1340
+ if (result) {
1341
+ await result;
1342
+ }
1343
+ hsm._tracePopDone('unhandled handler execution successful');
1344
+ await completePendingTransitions(hsm, resolver, () => {
1345
+ hsm._tracePopDone('unhandled event recovery successful');
1346
+ onComplete();
1347
+ });
1348
+ }
1349
+ catch (recoveryErr) {
1350
+ hsm._tracePopError(`unhandled event recovery failure: ${quoteUnknown(recoveryErr)}`);
1351
+ if (recoveryErr instanceof TransitionError) {
1352
+ hsm.currentState = FatalErrorState;
1353
+ hsm._tracePopError(`unhandled event recovery failure: ${quoteUnknown(recoveryErr)}`);
1354
+ throw recoveryErr;
1355
+ }
1356
+ try {
1357
+ await verboseDoError(hsm, resolver, asError(recoveryErr), () => {
1358
+ hsm._tracePopDone('unhandled event recovery successful');
1359
+ onComplete();
1360
+ });
1361
+ }
1362
+ catch (nestedErr) {
1363
+ hsm._tracePopError(`unhandled event recovery failure: ${quoteUnknown(nestedErr)}`);
1364
+ throw nestedErr;
1365
+ }
1366
+ }
1367
+ }
1368
+ async function dispatchEventVerbose(hsm, resolver, eventName, ...eventPayload) {
1369
+ const eventLabel = String(eventName);
1370
+ hsm._traceWrite(`begin event dispatch of #${eventLabel}`);
1371
+ hsm._tracePush(`#${eventLabel}`, `started event dispatch`);
1372
+ hsm._currentEventName = eventLabel;
1373
+ hsm._currentEventPayload = eventPayload;
1374
+ try {
1375
+ const eventHandler = lookupEventHandler(hsm, eventName);
1376
+ if (!eventHandler) {
1377
+ hsm._traceWrite(`event #${eventLabel} is unhandled in state ${hsm.currentStateName}`);
1378
+ try {
1379
+ await verboseDoUnhandledEvent(hsm, resolver, new UnhandledEventError(hsm), () => {
1380
+ hsm._tracePopDone('event dispatch successful');
1381
+ verboseFinishEventDispatch(hsm);
1382
+ });
1383
+ return;
1384
+ }
1385
+ catch (recoveryErr) {
1386
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(recoveryErr)}`);
1387
+ verboseFinishEventDispatch(hsm);
1388
+ throw recoveryErr;
1389
+ }
1390
+ }
1391
+ try {
1392
+ hsm._tracePush('execute', 'started event handler execution');
1393
+ const result = eventHandler.call(hsm._instance, ...eventPayload);
1394
+ if (result) {
1395
+ await result;
1396
+ }
1397
+ hsm._tracePopDone('event handler execution successful');
1398
+ await completePendingTransitions(hsm, resolver, () => {
1399
+ hsm._tracePopDone(`event dispatch successful`);
1400
+ verboseFinishEventDispatch(hsm);
1401
+ });
1402
+ }
1403
+ catch (recoveryErr) {
1404
+ hsm._tracePopError(quoteUnknown(recoveryErr));
1405
+ if (recoveryErr instanceof UnhandledEventError) {
1406
+ hsm._traceWrite(`event #${eventLabel} is unhandled in state ${hsm.currentStateName}`);
1407
+ try {
1408
+ await verboseDoUnhandledEvent(hsm, resolver, recoveryErr, () => {
1409
+ hsm._tracePopDone('event dispatch successful');
1410
+ verboseFinishEventDispatch(hsm);
1411
+ });
1412
+ return;
1413
+ }
1414
+ catch (nestedErr) {
1415
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(nestedErr)}`);
1416
+ verboseFinishEventDispatch(hsm);
1417
+ throw nestedErr;
1418
+ }
1419
+ }
1420
+ else if (recoveryErr instanceof TransitionError) {
1421
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(recoveryErr)}`);
1422
+ verboseFinishEventDispatch(hsm);
1423
+ throw recoveryErr;
1424
+ }
1425
+ else {
1426
+ try {
1427
+ await verboseDoError(hsm, resolver, asError(recoveryErr), () => {
1428
+ hsm._tracePopDone('event dispatch successful');
1429
+ verboseFinishEventDispatch(hsm);
1430
+ });
1431
+ }
1432
+ catch (nestedErr) {
1433
+ hsm._tracePopError(`event dispatch failed: ${quoteUnknown(nestedErr)}`);
1434
+ verboseFinishEventDispatch(hsm);
1435
+ throw nestedErr;
1436
+ }
1437
+ }
1438
+ }
1439
+ }
1440
+ catch (err) {
1441
+ verboseFinishEventDispatch(hsm);
1442
+ throw err;
1443
+ }
1444
+ }
1445
+ const productionDispatchStrategy = {
1446
+ executeInit: executeInitProduction,
1447
+ dispatchEvent: dispatchEventProduction,
1448
+ };
1449
+ const debugDispatchStrategy = {
1450
+ executeInit: executeInitDebug,
1451
+ dispatchEvent: dispatchEventDebug,
1452
+ };
1453
+ const verboseDispatchStrategy = {
1454
+ executeInit: executeInitVerbose,
1455
+ dispatchEvent: dispatchEventVerbose,
1456
+ };
1457
+ function dispatchStrategyFor(traceLevel) {
1458
+ switch (traceLevel) {
1459
+ case TraceLevel.PRODUCTION:
1460
+ return productionDispatchStrategy;
1461
+ case TraceLevel.DEBUG:
1462
+ return debugDispatchStrategy;
1463
+ case TraceLevel.VERBOSE_DEBUG:
1464
+ return verboseDispatchStrategy;
1465
+ }
1466
+ }
1467
+ //#endregion
1468
+ /** @internal */
1469
+ export function createInitTask(host, resolver) {
1470
+ const strategy = dispatchStrategyFor(host.traceLevel);
1471
+ return (done) => {
1472
+ strategy
1473
+ .executeInit(host)
1474
+ .then(() => executePendingTransition(host, resolver))
1475
+ .then(() => done())
1476
+ .catch((err) => {
1477
+ host.reportDispatchError(asError(err));
1478
+ done();
1479
+ });
1480
+ };
1481
+ }
1482
+ /**
1483
+ * Run a dispatch body inside the ambient `dispatchContext` token so that any `notify`/`call`/timer
1484
+ * the handler issues can be attributed to the running `(macrostepId, stepSeq)` (CORE-B, §5.6.3).
1485
+ * Falls back to a plain run when no ALS is available (browser) or the host predates the seam.
1486
+ */
1487
+ function runWithinDispatch(host, run) {
1488
+ const machine = host;
1489
+ const wants = machine.needsDispatchContext?.() ?? host.traceLevel !== TraceLevel.PRODUCTION;
1490
+ if (!wants) {
1491
+ run();
1492
+ return;
1493
+ }
1494
+ const storage = dispatchContext.get();
1495
+ if (storage === undefined) {
1496
+ run();
1497
+ return;
1498
+ }
1499
+ const token = machine.buildDispatchToken?.() ?? { machine: host };
1500
+ storage.run(token, run);
1501
+ }
1502
+ function currentPortCallToken() {
1503
+ const storage = portCallContext.get();
1504
+ return storage?.getStore();
1505
+ }
1506
+ /** @internal */
1507
+ export function createNotificationTask(host, resolver, name, args) {
1508
+ const strategy = dispatchStrategyFor(host.traceLevel);
1509
+ return (done) => {
1510
+ runWithinDispatch(host, () => {
1511
+ void strategy
1512
+ .dispatchEvent(host, resolver, name, ...args)
1513
+ .catch((err) => host.reportDispatchError(asError(err)))
1514
+ .finally(() => done());
1515
+ });
1516
+ };
1517
+ }
1518
+ /** @internal */
1519
+ export function createServiceTask(host, resolver, name, args, resolve, reject) {
1520
+ return (done) => {
1521
+ const run = () => invokeHandler(host, resolver, name, args)
1522
+ .then(resolve)
1523
+ .catch((err) => {
1524
+ reject(asError(err));
1525
+ })
1526
+ .catch((err) => host.reportDispatchError(asError(err)))
1527
+ .finally(() => done());
1528
+ runWithinDispatch(host, () => {
1529
+ void run();
1530
+ });
1531
+ };
1532
+ }
1533
+ //#endregion
1534
+ //#region hsm
1535
+ /** @internal */
1536
+ export class HsmObject {
1537
+ topState;
1538
+ topStateName;
1539
+ ctxTypeName;
1540
+ traceWriter;
1541
+ actorUuid;
1542
+ actorName;
1543
+ actorPath;
1544
+ /** @internal */
1545
+ _instance;
1546
+ /** @internal */
1547
+ _jobs;
1548
+ /** @internal */
1549
+ _hiPriorityJobs;
1550
+ _isRunning = false;
1551
+ _transitionState;
1552
+ _currentEventName;
1553
+ _currentEventPayload;
1554
+ _observers;
1555
+ dispatchErrorCallback;
1556
+ _traceLevel;
1557
+ _traceDomainStack;
1558
+ _instrumentationHost;
1559
+ _drainWaiters = [];
1560
+ constructor(TopState, instance, traceWriter, traceLevel, dispatchErrorCallback, identity) {
1561
+ this._instance = instance;
1562
+ this._transitionState = undefined;
1563
+ this._traceLevel = traceLevel;
1564
+ this._currentEventName = undefined;
1565
+ this._currentEventPayload = undefined;
1566
+ this._traceDomainStack = [];
1567
+ this._jobs = [];
1568
+ this._hiPriorityJobs = [];
1569
+ this._isRunning = false;
1570
+ this.actorUuid = identity.uuid;
1571
+ this.actorName = identity.name;
1572
+ this.actorPath = identity.path;
1573
+ this.topState = TopState;
1574
+ this.topStateName = getStateName(TopState);
1575
+ this.ctxTypeName = Object.getPrototypeOf(instance.ctx).constructor.name;
1576
+ this.currentState = TopState;
1577
+ this.traceWriter = traceWriter;
1578
+ this.dispatchErrorCallback = dispatchErrorCallback;
1579
+ }
1580
+ get ctx() {
1581
+ return this._instance.ctx;
1582
+ }
1583
+ set ctx(ctx) {
1584
+ this._instance.ctx = ctx;
1585
+ }
1586
+ get port() {
1587
+ return this._instance.portRef;
1588
+ }
1589
+ /** The bound port when it provides a timer service, so service-call timeouts honour a virtual clock. */
1590
+ get callTimer() {
1591
+ const port = this._instance.portRef;
1592
+ if (port !== undefined && typeof port.setTimeout === 'function' && typeof port.clearTimeout === 'function') {
1593
+ return port;
1594
+ }
1595
+ return undefined;
1596
+ }
1597
+ get eventName() {
1598
+ return this._currentEventName ?? '';
1599
+ }
1600
+ get eventPayload() {
1601
+ return this._currentEventPayload ?? [];
1602
+ }
1603
+ get currentStateName() {
1604
+ return getStateName(Object.getPrototypeOf(this._instance).constructor);
1605
+ }
1606
+ get currentState() {
1607
+ return Object.getPrototypeOf(this._instance).constructor;
1608
+ }
1609
+ set currentState(newState) {
1610
+ Object.setPrototypeOf(this._instance, newState.prototype);
1611
+ }
1612
+ subscribe(observer) {
1613
+ if (this._observers === undefined)
1614
+ this._observers = new Set();
1615
+ this._observers.add(observer);
1616
+ return {
1617
+ dispose: () => {
1618
+ this._observers?.delete(observer);
1619
+ },
1620
+ };
1621
+ }
1622
+ _notifyObservers(eventName, eventPayload) {
1623
+ if (this._observers === undefined || this._observers.size === 0)
1624
+ return;
1625
+ const message = { event: String(eventName), payload: [...eventPayload] };
1626
+ for (const observer of this._observers)
1627
+ observer(message);
1628
+ }
1629
+ recordObserverEvent(eventName, eventPayload) {
1630
+ this._notifyObservers(eventName, eventPayload);
1631
+ }
1632
+ transition(nextState) {
1633
+ this._transitionState = nextState;
1634
+ }
1635
+ unhandled() {
1636
+ throw new UnhandledEventError(this);
1637
+ }
1638
+ get traceLevel() {
1639
+ return this._traceLevel;
1640
+ }
1641
+ set traceLevel(traceLevel) {
1642
+ this._traceLevel = traceLevel;
1643
+ }
1644
+ /**
1645
+ * Resolve when the actor next reaches stability (mailbox fully drained).
1646
+ *
1647
+ * The resolver fires at the queue-drain point — *after* {@link InstrumentationHost.onQueuesDrained}
1648
+ * — so the closing `macrostep.end` and the macrostep-boundary reset are observable before `sync()`
1649
+ * resolves, and a subsequent external stimulus deterministically starts its own macrostep. The
1650
+ * pushed task is a no-op (internal) whose only purpose is to guarantee a drain cycle occurs.
1651
+ */
1652
+ sync() {
1653
+ return new Promise(resolve => {
1654
+ this._drainWaiters.push(resolve);
1655
+ const task = (doneCallback) => {
1656
+ doneCallback();
1657
+ };
1658
+ setTaskMeta(task, { internal: true });
1659
+ this.pushTask(task);
1660
+ });
1661
+ }
1662
+ /** Invoke the user dispatch-error callback, first notifying instrumentation (pure observer). */
1663
+ reportDispatchError(err) {
1664
+ this._instrumentationHost?.onDispatchError(err);
1665
+ this.dispatchErrorCallback(this, err);
1666
+ }
1667
+ flushDrainWaiters() {
1668
+ if (this._drainWaiters.length === 0)
1669
+ return;
1670
+ const waiters = this._drainWaiters;
1671
+ this._drainWaiters = [];
1672
+ for (const resolve of waiters)
1673
+ resolve();
1674
+ }
1675
+ pushTask(t) {
1676
+ this.enqueueTask(t, this._jobs);
1677
+ }
1678
+ pushHiPriorityTask(t) {
1679
+ this.enqueueTask(t, this._hiPriorityJobs);
1680
+ }
1681
+ unshiftHiPriorityTask(t) {
1682
+ this._hiPriorityJobs.unshift(t);
1683
+ if (this._isRunning)
1684
+ return;
1685
+ this._isRunning = true;
1686
+ this.dequeue();
1687
+ }
1688
+ enqueueTask(t, queue) {
1689
+ queue.push(t);
1690
+ if (this._isRunning)
1691
+ return;
1692
+ this._isRunning = true;
1693
+ this.dequeue();
1694
+ }
1695
+ restore(state, ctx) {
1696
+ this.currentState = state;
1697
+ this.ctx = ctx;
1698
+ }
1699
+ dequeue() {
1700
+ if (this._hiPriorityJobs.length == 0 && this._jobs.length == 0) {
1701
+ this._isRunning = false;
1702
+ this._instrumentationHost?.onQueuesDrained();
1703
+ this.flushDrainWaiters();
1704
+ return;
1705
+ }
1706
+ const task = this._hiPriorityJobs.length > 0 ? this._hiPriorityJobs.shift() : this._jobs.shift();
1707
+ this.exec(task);
1708
+ }
1709
+ exec(task) {
1710
+ setTimeout(() => this.runTask(task).then(() => this.dequeue()), 0);
1711
+ }
1712
+ runTask(task) {
1713
+ this._instrumentationHost?.onTaskBegin(task);
1714
+ let outcome = 'ok';
1715
+ return new Promise(resolve => {
1716
+ const runBody = () => {
1717
+ task(() => {
1718
+ this.drainHiPriority()
1719
+ .then(() => {
1720
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1721
+ resolve();
1722
+ })
1723
+ .catch((_err) => {
1724
+ outcome = 'error';
1725
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1726
+ resolve();
1727
+ });
1728
+ });
1729
+ };
1730
+ try {
1731
+ runBody();
1732
+ }
1733
+ catch (err) {
1734
+ outcome = 'error';
1735
+ this._instrumentationHost?.onTaskEnd(task, outcome);
1736
+ resolve();
1737
+ throw asError(err);
1738
+ }
1739
+ });
1740
+ }
1741
+ drainHiPriority() {
1742
+ if (this._hiPriorityJobs.length === 0) {
1743
+ return Promise.resolve();
1744
+ }
1745
+ const task = this._hiPriorityJobs.shift();
1746
+ return this.runTask(task).then(() => this.drainHiPriority());
1747
+ }
1748
+ _tracePush(d, msg) {
1749
+ this._traceDomainStack.push(d);
1750
+ this.traceWriter.write(this, msg);
1751
+ }
1752
+ _tracePopDone(msg) {
1753
+ this.traceWriter.write(this, `done: ${msg}`);
1754
+ this._traceDomainStack.pop();
1755
+ }
1756
+ _tracePopError(msg) {
1757
+ this.traceWriter.write(this, `failure: ${msg}`);
1758
+ this._traceDomainStack.pop();
1759
+ }
1760
+ _traceWrite(msg) {
1761
+ this.traceWriter.write(this, msg);
1762
+ }
1763
+ get traceHeader() {
1764
+ return `${this._traceDomainStack.length === 0 ? '' : this._traceDomainStack.join('|') + '|'}`;
1765
+ }
1766
+ get traceFrames() {
1767
+ return this._traceDomainStack.map(name => ({ name, kind: classifyFrameKind(name) }));
1768
+ }
1769
+ }
1770
+ /** Best-effort classification of a live trace-domain name into a structured {@link TraceFrame.kind}. */
1771
+ function classifyFrameKind(name) {
1772
+ if (name.startsWith('#'))
1773
+ return 'event';
1774
+ if (name === 'execute')
1775
+ return 'handler';
1776
+ if (name === 'initialize' || name.startsWith('initialize'))
1777
+ return 'initialize';
1778
+ if (name.startsWith('transition'))
1779
+ return 'transition';
1780
+ if (name.includes('onEntry'))
1781
+ return 'onEntry';
1782
+ if (name.includes('onExit'))
1783
+ return 'onExit';
1784
+ return 'handler';
1785
+ }
1786
+ //#region machine
1787
+ export class Machine extends HsmObject {
1788
+ transitionResolver;
1789
+ identity;
1790
+ instrumentation;
1791
+ _dispatchStrategy;
1792
+ protocolIndex;
1793
+ handlerFacade;
1794
+ selfActor;
1795
+ selfImmediate;
1796
+ actorFacades = new Map();
1797
+ _macrostepCounter = 0;
1798
+ _currentMacrostep;
1799
+ _microstepFromState;
1800
+ _childSpawnCounters = new Map();
1801
+ _nextTraceCallId = 0;
1802
+ _proxiedPort;
1803
+ constructor(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, identity, instrumentation, transitionResolver) {
1804
+ super(topState, instance, traceWriter, traceLevel, dispatchErrorCallback, identity);
1805
+ this.identity = identity;
1806
+ this.instrumentation = instrumentation;
1807
+ if (instrumentation !== undefined) {
1808
+ this._instrumentationHost = this;
1809
+ notifyActorCreated(instrumentation, identity);
1810
+ }
1811
+ Object.defineProperty(instance, kHandlerMachine, { value: this, enumerable: false, writable: false, configurable: false });
1812
+ this.protocolIndex = protocolIndex;
1813
+ cacheProtocolIndex(topState, protocolIndex);
1814
+ this.transitionResolver = transitionResolver ?? new RuntimeTransitionResolver();
1815
+ this._dispatchStrategy = dispatchStrategyFor(traceLevel);
1816
+ this.selfActor = createSelfNotifications(this, topState, protocolIndex, 'default');
1817
+ this.selfImmediate = createSelfNotifications(this, topState, protocolIndex, 'priority');
1818
+ this.handlerFacade = this.buildHandlerFacade(instance);
1819
+ instance.hsm = this.handlerFacade;
1820
+ Object.defineProperty(instance, 'notify', { value: this.selfActor, enumerable: true, configurable: true });
1821
+ Object.defineProperty(instance, 'notifyNow', { value: this.selfImmediate, enumerable: true, configurable: true });
1822
+ this.bindPort(instance.portRef);
1823
+ if (initialize) {
1824
+ const initTask = createInitTask(this, this.transitionResolver);
1825
+ setTaskMeta(initTask, { event: 'initialize', queue: 'default', triggerKind: 'init', internal: false });
1826
+ this.pushTask(initTask);
1827
+ }
1828
+ }
1829
+ allocateChildSpawnIndex(childTopName) {
1830
+ const childName = actorNameFromTopState(childTopName);
1831
+ const index = this._childSpawnCounters.get(childName) ?? 0;
1832
+ this._childSpawnCounters.set(childName, index + 1);
1833
+ return index;
1834
+ }
1835
+ needsDispatchContext() {
1836
+ return this.instrumentation !== undefined || this.traceLevel !== TraceLevel.PRODUCTION;
1837
+ }
1838
+ buildDispatchToken() {
1839
+ return {
1840
+ machine: this,
1841
+ actorUuid: this.actorUuid,
1842
+ macrostepId: this._currentMacrostep?.id,
1843
+ stepSeq: this._currentMacrostep?.stepSeq,
1844
+ };
1845
+ }
1846
+ readDispatchCause(kind) {
1847
+ const storage = dispatchContext.get();
1848
+ const token = storage?.getStore();
1849
+ if (token?.actorUuid === undefined)
1850
+ return undefined;
1851
+ return {
1852
+ actorUuid: token.actorUuid,
1853
+ macrostepId: token.macrostepId,
1854
+ stepSeq: token.stepSeq,
1855
+ kind,
1856
+ };
1857
+ }
1858
+ nextTraceCallId() {
1859
+ this._nextTraceCallId += 1;
1860
+ return this._nextTraceCallId;
1861
+ }
1862
+ beginPortCall(method) {
1863
+ if (this.instrumentation === undefined)
1864
+ return undefined;
1865
+ const cause = this.readDispatchCause('wire');
1866
+ const info = {
1867
+ callId: this.nextTraceCallId(),
1868
+ method,
1869
+ cause,
1870
+ };
1871
+ notifyPortCallBegin(this.instrumentation, info);
1872
+ return info;
1873
+ }
1874
+ endPortCall(begin, outcome, error) {
1875
+ if (this.instrumentation === undefined || begin === undefined)
1876
+ return;
1877
+ const info = {
1878
+ callId: begin.callId,
1879
+ method: begin.method,
1880
+ outcome,
1881
+ error,
1882
+ };
1883
+ notifyPortCallEnd(this.instrumentation, info);
1884
+ }
1885
+ beginOutboundCall(service, targetUuid) {
1886
+ if (this.instrumentation === undefined)
1887
+ return undefined;
1888
+ const portToken = currentPortCallToken();
1889
+ const cause = this.readDispatchCause('wire') ?? portToken?.cause;
1890
+ const info = {
1891
+ callId: this.nextTraceCallId(),
1892
+ service,
1893
+ targetUuid,
1894
+ cause,
1895
+ };
1896
+ notifyOutboundCallBegin(this.instrumentation, info);
1897
+ return info;
1898
+ }
1899
+ endOutboundCall(begin, outcome, error) {
1900
+ if (this.instrumentation === undefined || begin === undefined)
1901
+ return;
1902
+ const info = {
1903
+ callId: begin.callId,
1904
+ service: begin.service,
1905
+ outcome,
1906
+ error,
1907
+ };
1908
+ notifyOutboundCallEnd(this.instrumentation, info);
1909
+ }
1910
+ slotBucket(eventName) {
1911
+ return this.protocolIndex.get(eventName)?.bucket ?? 'notifications';
1912
+ }
1913
+ onTaskBegin(task) {
1914
+ const meta = getTaskMeta(task);
1915
+ if (meta?.internal === true || this.instrumentation === undefined)
1916
+ return;
1917
+ if (this._currentMacrostep === undefined) {
1918
+ const id = `${this.actorUuid}:${++this._macrostepCounter}`;
1919
+ const trigger = meta?.event ?? 'unknown';
1920
+ const triggerKind = meta?.triggerKind ?? (this._jobs.length + this._hiPriorityJobs.length > 0 ? 'self' : 'external');
1921
+ this._currentMacrostep = {
1922
+ id,
1923
+ trigger,
1924
+ triggerKind,
1925
+ startState: this.currentStateName,
1926
+ stepSeq: -1,
1927
+ transitioned: false,
1928
+ outcome: 'ok',
1929
+ cause: meta?.cause,
1930
+ };
1931
+ notifyMacrostepBegin(this.instrumentation, {
1932
+ id,
1933
+ actor: this.identity,
1934
+ trigger,
1935
+ triggerKind,
1936
+ startState: this.currentStateName,
1937
+ cause: meta?.cause,
1938
+ delayMs: meta?.delayMs,
1939
+ });
1940
+ }
1941
+ const macrostep = this._currentMacrostep;
1942
+ macrostep.stepSeq += 1;
1943
+ const seq = macrostep.stepSeq;
1944
+ const fromState = this.currentStateName;
1945
+ this._microstepFromState = fromState;
1946
+ // Stamp seq + fromState on the task so onTaskEnd pairs correctly even when nested priority
1947
+ // drains mutate the shared macrostep counter between this task's begin and end.
1948
+ if (meta !== undefined) {
1949
+ meta.seq = seq;
1950
+ meta.fromState = fromState;
1951
+ }
1952
+ else {
1953
+ setTaskMeta(task, { seq, fromState });
1954
+ }
1955
+ const eventName = meta?.event ?? this.eventName ?? 'unknown';
1956
+ const handlerState = lookupHandlerState(this, eventName);
1957
+ const storage = dispatchContext.get();
1958
+ const runMicrostep = () => {
1959
+ notifyMicrostepBegin(this.instrumentation, {
1960
+ macrostepId: macrostep.id,
1961
+ seq,
1962
+ event: eventName,
1963
+ bucket: this.slotBucket(eventName),
1964
+ queue: meta?.queue ?? 'default',
1965
+ fromState: this.currentStateName,
1966
+ handlerState,
1967
+ cause: meta?.cause,
1968
+ });
1969
+ };
1970
+ if (storage !== undefined && this.needsDispatchContext()) {
1971
+ storage.run(this.buildDispatchToken(), runMicrostep);
1972
+ }
1973
+ else {
1974
+ runMicrostep();
1975
+ }
1976
+ }
1977
+ onTaskEnd(task, outcome) {
1978
+ const meta = getTaskMeta(task);
1979
+ if (meta?.internal === true || this.instrumentation === undefined || this._currentMacrostep === undefined)
1980
+ return;
1981
+ const macrostep = this._currentMacrostep;
1982
+ const fromState = meta?.fromState ?? this._microstepFromState ?? macrostep.startState;
1983
+ const seq = meta?.seq ?? macrostep.stepSeq;
1984
+ const transitioned = this.currentStateName !== fromState;
1985
+ if (transitioned)
1986
+ macrostep.transitioned = true;
1987
+ if (outcome === 'error')
1988
+ macrostep.outcome = 'error';
1989
+ notifyMicrostepEnd(this.instrumentation, {
1990
+ macrostepId: macrostep.id,
1991
+ seq,
1992
+ toState: this.currentStateName,
1993
+ transitioned,
1994
+ async: false,
1995
+ outcome,
1996
+ });
1997
+ }
1998
+ onDispatchError(err) {
1999
+ if (this.instrumentation === undefined)
2000
+ return;
2001
+ notifyError(this.instrumentation, {
2002
+ phase: errorPhaseFromError(err),
2003
+ errorClass: err.name,
2004
+ error: err,
2005
+ recovered: false,
2006
+ });
2007
+ }
2008
+ onQueuesDrained() {
2009
+ if (this.instrumentation === undefined || this._currentMacrostep === undefined)
2010
+ return;
2011
+ const macrostep = this._currentMacrostep;
2012
+ notifyMacrostepEnd(this.instrumentation, {
2013
+ id: macrostep.id,
2014
+ endState: this.currentStateName,
2015
+ steps: macrostep.stepSeq + 1,
2016
+ transitioned: macrostep.transitioned,
2017
+ outcome: macrostep.outcome,
2018
+ });
2019
+ this._currentMacrostep = undefined;
2020
+ }
2021
+ resolveEnqueueCause() {
2022
+ const inherited = this.readDispatchCause('message');
2023
+ if (inherited === undefined) {
2024
+ return { actorUuid: this.actorUuid, kind: 'cause' };
2025
+ }
2026
+ if (inherited.actorUuid !== this.actorUuid) {
2027
+ return inherited;
2028
+ }
2029
+ return { ...inherited, kind: 'cause' };
2030
+ }
2031
+ enqueueWithInstrumentation(task, event, queue, triggerKind) {
2032
+ const cause = this.resolveEnqueueCause();
2033
+ setTaskMeta(task, { event, queue, cause, triggerKind });
2034
+ notifyEnqueue(this.instrumentation, {
2035
+ event,
2036
+ queue,
2037
+ cause,
2038
+ targetUuid: this.actorUuid,
2039
+ });
2040
+ if (queue === 'priority') {
2041
+ this.pushHiPriorityTask(task);
2042
+ }
2043
+ else {
2044
+ this.pushTask(task);
2045
+ }
2046
+ }
2047
+ get traceLevel() {
2048
+ return super.traceLevel;
2049
+ }
2050
+ set traceLevel(traceLevel) {
2051
+ super.traceLevel = traceLevel;
2052
+ this._dispatchStrategy = dispatchStrategyFor(traceLevel);
2053
+ }
2054
+ dispatchService(name, args) {
2055
+ if (this.traceLevel !== TraceLevel.PRODUCTION) {
2056
+ const storage = dispatchContext.get();
2057
+ const token = storage?.getStore();
2058
+ if (token?.machine === this) {
2059
+ throw new SelfCallDeadlockError();
2060
+ }
2061
+ }
2062
+ this.recordObserverEvent(name, args);
2063
+ return new Promise((resolve, reject) => {
2064
+ const task = createServiceTask(this, this.transitionResolver, name, args, resolve, reject);
2065
+ if (this.instrumentation !== undefined) {
2066
+ this.enqueueWithInstrumentation(task, name, 'default', 'call');
2067
+ return;
2068
+ }
2069
+ this.pushTask(task);
2070
+ });
2071
+ }
2072
+ dispatchNotification(name, args, queue) {
2073
+ this.recordObserverEvent(name, args);
2074
+ const task = createNotificationTask(this, this.transitionResolver, name, args);
2075
+ if (this.instrumentation !== undefined) {
2076
+ const triggerKind = this._currentMacrostep === undefined ? 'external' : 'self';
2077
+ this.enqueueWithInstrumentation(task, name, queue, triggerKind);
2078
+ return;
2079
+ }
2080
+ if (queue === 'priority') {
2081
+ this.pushHiPriorityTask(task);
2082
+ }
2083
+ else {
2084
+ this.pushTask(task);
2085
+ }
2086
+ }
2087
+ actorHsmFor(kind) {
2088
+ let facade = this.actorFacades.get(kind);
2089
+ if (facade === undefined) {
2090
+ facade = this.buildActorHsm(kind);
2091
+ this.actorFacades.set(kind, facade);
2092
+ }
2093
+ return facade;
2094
+ }
2095
+ scheduleNotification(ms, name, args) {
2096
+ const port = this._instance.portRef;
2097
+ if (port === undefined) {
2098
+ throw new Error('ihsm: deferred notification requires a port');
2099
+ }
2100
+ if (this.instrumentation === undefined) {
2101
+ port.setTimeout(() => this.dispatchNotification(name, args, 'default'), ms);
2102
+ return;
2103
+ }
2104
+ // Capture the arming step's dispatch token now (the timer fires later while the actor is idle,
2105
+ // so the ambient token is gone by then). The fired macrostep links back to it as `timer` (§5.3.1).
2106
+ const armed = this.readDispatchCause('timer');
2107
+ const cause = armed ?? { actorUuid: this.actorUuid, kind: 'timer' };
2108
+ port.setTimeout(() => this.enqueueTimerNotification(name, args, cause, ms), ms);
2109
+ }
2110
+ enqueueTimerNotification(name, args, cause, delayMs) {
2111
+ this.recordObserverEvent(name, args);
2112
+ const task = createNotificationTask(this, this.transitionResolver, name, args);
2113
+ setTaskMeta(task, { event: name, queue: 'default', cause, triggerKind: 'timer', delayMs });
2114
+ notifyEnqueue(this.instrumentation, { event: name, queue: 'default', cause, delayMs, targetUuid: this.actorUuid });
2115
+ this.pushTask(task);
2116
+ }
2117
+ _actorLogger;
2118
+ /** Severity-typed handler logger surfaced as `this.hsm.log.*` (CORE-F, §4.10.1). */
2119
+ get logger() {
2120
+ if (this._actorLogger === undefined) {
2121
+ const emit = (severity, message, attributes) => this.emitUserLog(severity, message, attributes);
2122
+ this._actorLogger = {
2123
+ trace: (m, a) => emit('trace', m, a),
2124
+ debug: (m, a) => emit('debug', m, a),
2125
+ info: (m, a) => emit('info', m, a),
2126
+ warn: (m, a) => emit('warn', m, a),
2127
+ error: (m, a) => emit('error', m, a),
2128
+ fatal: (m, a) => emit('fatal', m, a),
2129
+ };
2130
+ }
2131
+ return this._actorLogger;
2132
+ }
2133
+ emitUserLog(severity, message, attributes) {
2134
+ const isError = message instanceof Error;
2135
+ const text = isError ? message.message : message;
2136
+ const body = `${this.traceHeader}${this.currentStateName}: ${text}`;
2137
+ // User logs fire on intent and are never TraceLevel-gated; mirror to the TraceWriter for console.
2138
+ this.traceWriter.write(this, body);
2139
+ if (this.instrumentation === undefined)
2140
+ return;
2141
+ const record = {
2142
+ severity,
2143
+ body,
2144
+ attributes,
2145
+ frames: this.traceFrames,
2146
+ error: isError ? message : undefined,
2147
+ source: 'user',
2148
+ };
2149
+ notifyLog(this.instrumentation, record);
2150
+ }
2151
+ /** @internal Binds deferred self-notifications to a port instance. */
2152
+ bindPort(portRef) {
2153
+ if (portRef instanceof Port) {
2154
+ portRef.bindDeferredNotifications(ms => this.createDeferredSelfNotifications(ms));
2155
+ }
2156
+ }
2157
+ buildHandlerFacade(instance) {
2158
+ const machine = this;
2159
+ const facade = {
2160
+ get ctx() {
2161
+ return machine.ctx;
2162
+ },
2163
+ transition: next => machine.transition(next),
2164
+ get port() {
2165
+ const rawPort = instance.portRef;
2166
+ if (machine.instrumentation === undefined || rawPort === undefined || typeof rawPort !== 'object') {
2167
+ return rawPort;
2168
+ }
2169
+ if (machine._proxiedPort !== undefined)
2170
+ return machine._proxiedPort;
2171
+ const proxy = new Proxy(rawPort, {
2172
+ get(target, prop, receiver) {
2173
+ const value = Reflect.get(target, prop, receiver);
2174
+ if (typeof value !== 'function')
2175
+ return value;
2176
+ const method = String(prop);
2177
+ return (...args) => {
2178
+ const begin = machine.beginPortCall(method);
2179
+ if (begin === undefined) {
2180
+ return Reflect.apply(value, target, args);
2181
+ }
2182
+ const call = () => Reflect.apply(value, target, args);
2183
+ const storage = portCallContext.get();
2184
+ try {
2185
+ const result = storage !== undefined ? storage.run({ machine, callId: begin.callId, method, cause: begin.cause }, call) : call();
2186
+ if (result instanceof Promise) {
2187
+ return result.then((value) => {
2188
+ machine.endPortCall(begin, 'ok');
2189
+ return value;
2190
+ }, (cause) => {
2191
+ const err = asError(cause);
2192
+ machine.endPortCall(begin, 'error', err);
2193
+ throw err;
2194
+ });
2195
+ }
2196
+ machine.endPortCall(begin, 'ok');
2197
+ return result;
2198
+ }
2199
+ catch (cause) {
2200
+ const err = asError(cause);
2201
+ machine.endPortCall(begin, 'error', err);
2202
+ throw err;
2203
+ }
2204
+ };
2205
+ },
2206
+ });
2207
+ machine._proxiedPort = proxy;
2208
+ return proxy;
2209
+ },
2210
+ unhandled: () => machine.unhandled(),
2211
+ get eventName() {
2212
+ return machine.eventName;
2213
+ },
2214
+ get eventPayload() {
2215
+ return machine.eventPayload;
2216
+ },
2217
+ get currentState() {
2218
+ return machine.currentState;
2219
+ },
2220
+ get currentStateName() {
2221
+ return machine.currentStateName;
2222
+ },
2223
+ get topState() {
2224
+ return machine.topState;
2225
+ },
2226
+ get topStateName() {
2227
+ return machine.topStateName;
2228
+ },
2229
+ get traceHeader() {
2230
+ return machine.traceHeader;
2231
+ },
2232
+ get traceFrames() {
2233
+ return machine.traceFrames;
2234
+ },
2235
+ get log() {
2236
+ return machine.logger;
2237
+ },
2238
+ get id() {
2239
+ return machine.actorUuid;
2240
+ },
2241
+ get actorUuid() {
2242
+ return machine.actorUuid;
2243
+ },
2244
+ get actorName() {
2245
+ return machine.actorName;
2246
+ },
2247
+ get actorPath() {
2248
+ return machine.actorPath;
2249
+ },
2250
+ get traceLevel() {
2251
+ return machine.traceLevel;
2252
+ },
2253
+ set traceLevel(level) {
2254
+ machine.traceLevel = level;
2255
+ },
2256
+ get traceWriter() {
2257
+ return machine.traceWriter;
2258
+ },
2259
+ set traceWriter(writer) {
2260
+ machine.traceWriter = writer;
2261
+ },
2262
+ get dispatchErrorCallback() {
2263
+ return machine.dispatchErrorCallback;
2264
+ },
2265
+ set dispatchErrorCallback(cb) {
2266
+ machine.dispatchErrorCallback = cb;
2267
+ },
2268
+ };
2269
+ return facade;
2270
+ }
2271
+ createDeferredSelfNotifications(ms) {
2272
+ const machine = this;
2273
+ const proto = Object.create(null);
2274
+ for (const [name, slot] of this.protocolIndex.entries('inbound')) {
2275
+ if (slot.bucket === 'notifications' || slot.bucket === 'internalNotifications') {
2276
+ proto[name] = (...args) => {
2277
+ machine.scheduleNotification(ms, name, args);
2278
+ };
2279
+ }
2280
+ }
2281
+ for (const [name, slot] of this.protocolIndex.entries('root')) {
2282
+ if (slot.bucket === 'notifications') {
2283
+ proto[name] = (...args) => {
2284
+ machine.scheduleNotification(ms, name, args);
2285
+ };
2286
+ }
2287
+ }
2288
+ return Object.create(Object.freeze(proto));
2289
+ }
2290
+ buildActorHsm(kind) {
2291
+ const machine = this;
2292
+ const includeState = kind !== 'root';
2293
+ const includeOwner = kind === 'child' || kind === 'test';
2294
+ const facade = {
2295
+ sync: () => machine.sync(),
2296
+ get currentStateName() {
2297
+ return machine.currentStateName;
2298
+ },
2299
+ get topStateName() {
2300
+ return machine.topStateName;
2301
+ },
2302
+ get traceLevel() {
2303
+ return machine.traceLevel;
2304
+ },
2305
+ set traceLevel(level) {
2306
+ machine.traceLevel = level;
2307
+ },
2308
+ get traceWriter() {
2309
+ return machine.traceWriter;
2310
+ },
2311
+ set traceWriter(writer) {
2312
+ machine.traceWriter = writer;
2313
+ },
2314
+ get traceHeader() {
2315
+ return machine.traceHeader;
2316
+ },
2317
+ get id() {
2318
+ return machine.actorUuid;
2319
+ },
2320
+ get actorUuid() {
2321
+ return machine.actorUuid;
2322
+ },
2323
+ get actorName() {
2324
+ return machine.actorName;
2325
+ },
2326
+ get actorPath() {
2327
+ return machine.actorPath;
2328
+ },
2329
+ };
2330
+ if (includeState) {
2331
+ Object.defineProperties(facade, {
2332
+ currentState: {
2333
+ enumerable: true,
2334
+ get() {
2335
+ return machine.currentState;
2336
+ },
2337
+ },
2338
+ topState: {
2339
+ enumerable: true,
2340
+ get() {
2341
+ return machine.topState;
2342
+ },
2343
+ },
2344
+ });
2345
+ }
2346
+ if (includeOwner) {
2347
+ Object.defineProperties(facade, {
2348
+ restore: {
2349
+ enumerable: true,
2350
+ value: (state, ctx) => machine.restore(state, ctx),
2351
+ },
2352
+ dispatchErrorCallback: {
2353
+ enumerable: true,
2354
+ get() {
2355
+ return machine.dispatchErrorCallback;
2356
+ },
2357
+ set(cb) {
2358
+ machine.dispatchErrorCallback = cb;
2359
+ },
2360
+ },
2361
+ });
2362
+ }
2363
+ return facade;
2364
+ }
2365
+ }
2366
+ //#region factories
2367
+ export function isRequestingPort(port) {
2368
+ if (port === null || typeof port !== 'object') {
2369
+ return false;
2370
+ }
2371
+ const ctor = Object.getPrototypeOf(port)?.constructor;
2372
+ if (ctor === undefined) {
2373
+ return false;
2374
+ }
2375
+ return ctor[kRequestingPort] === true;
2376
+ }
2377
+ class ConsoleTraceWriter {
2378
+ write(hsm, msg) {
2379
+ if (typeof msg === 'string') {
2380
+ console.log(`${hsm.traceHeader}${hsm.currentStateName}: ${msg}`);
2381
+ }
2382
+ else {
2383
+ console.log(msg);
2384
+ }
2385
+ }
2386
+ }
2387
+ /** @internal */
2388
+ export const defaultTraceWriter = new ConsoleTraceWriter();
2389
+ /** @internal */
2390
+ export const defaultInitialize = true;
2391
+ /** @internal */
2392
+ export function defaultDispatchErrorCallback(hsm, err) {
2393
+ hsm.traceWriter.write(hsm, `An event dispatch has failed; error ${err.name}: ${err.message} has not been managed`);
2394
+ hsm.traceWriter.write(hsm, err);
2395
+ throw err;
2396
+ }
2397
+ /** @internal Spawn with embodiment kind — used by factories and `ihsm/testing`. */
2398
+ export function spawnActor(kind, topState, ctx, port, options, spawnContext = {}) {
2399
+ const { initialize = defaultInitialize, traceLevel = TraceLevel.DEBUG, traceWriter = defaultTraceWriter, dispatchErrorCallback = defaultDispatchErrorCallback, transitions } = options;
2400
+ const protocolIndex = buildProtocolIndex(topState);
2401
+ const topStateName = getStateName(topState);
2402
+ const parentMachine = spawnContext.parentMachine;
2403
+ // Tracing is a cross-cutting concern: the actor adopts the globally-registered collector(s) at
2404
+ // spawn (no per-actor `instrumentation` option). Snapshotting here keeps "no collector" actors
2405
+ // at zero overhead while still sharing one collector instance across a parent and its children.
2406
+ const resolvedInstrumentation = getActiveInstrumentation();
2407
+ const identity = parentMachine !== undefined ? mintActorIdentity('child', childActorPath(parentMachine.actorPath, topStateName, parentMachine.allocateChildSpawnIndex(topStateName)), parentMachine.actorUuid) : mintActorIdentity(kind, rootActorPath(topStateName));
2408
+ const boundPort = (port ?? new Port());
2409
+ const instance = {
2410
+ ctx,
2411
+ hsm: undefined,
2412
+ portRef: boundPort,
2413
+ };
2414
+ Object.setPrototypeOf(instance, topState.prototype);
2415
+ const machine = new Machine(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, identity, resolvedInstrumentation, transitions ?? new RuntimeTransitionResolver());
2416
+ if (resolvedInstrumentation !== undefined) {
2417
+ const token = dispatchContext.get()?.getStore();
2418
+ const parentCause = token?.actorUuid !== undefined
2419
+ ? {
2420
+ actorUuid: token.actorUuid,
2421
+ macrostepId: token.macrostepId,
2422
+ stepSeq: token.stepSeq,
2423
+ kind: 'spawn',
2424
+ }
2425
+ : {
2426
+ actorUuid: identity.parentUuid ?? identity.uuid,
2427
+ kind: 'spawn',
2428
+ };
2429
+ const spawnInfo = {
2430
+ parent: parentCause,
2431
+ child: identity,
2432
+ };
2433
+ notifyActorSpawned(resolvedInstrumentation, spawnInfo);
2434
+ }
2435
+ const portKind = isRequestingPort(boundPort) ? 'child' : kind === 'root' ? 'inbound' : kind;
2436
+ boundPort.actor = createActorHandle(machine, topState, protocolIndex, portKind);
2437
+ return createActorHandle(machine, topState, protocolIndex, kind);
2438
+ }
2439
+ /** Production black-box — public protocol only (generated handle). */
2440
+ export function makeActor(topState, ctx, port, options = {}) {
2441
+ return spawnActor('root', topState, ctx, port, options);
2442
+ }
2443
+ export function asParentActor(handler) {
2444
+ const machine = handler[kHandlerMachine];
2445
+ if (machine === undefined) {
2446
+ throw new Error('ihsm: asParentActor requires an active handler machine');
2447
+ }
2448
+ return {
2449
+ top: machine.topState,
2450
+ [kParentLink]: machine,
2451
+ };
2452
+ }
2453
+ /** Parent composes a child machine — returns full child protocol shell with `parent` set. */
2454
+ export function makeChildActor(parent, childTop, childCtx, port, options = {}) {
2455
+ const parentMachine = parent[kParentLink];
2456
+ const child = spawnActor('child', childTop, childCtx, port, options, { parentMachine: parentMachine });
2457
+ Object.defineProperty(child, 'parent', { value: parent, enumerable: true, writable: false, configurable: true });
2458
+ return child;
2459
+ }
2460
+ export { kHandlerMachine, kParentLink } from './types.js';
2461
+ export { configureRunSeed, getRunSeed, getRunNamespace, actorNameFromTopState, mintActorIdentity, rootActorPath, childActorPath } from './identity.js';
2462
+ //# sourceMappingURL=runtime.js.map