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