ihsm 0.1.1 → 0.1.22
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/lib/cjs/index.d.ts +4 -1
- package/lib/cjs/index.js +13 -1
- 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 +89 -6
- package/lib/cjs/internal/runtime.js +676 -52
- package/lib/cjs/internal/runtime.js.map +1 -1
- package/lib/cjs/internal/types.d.ts +174 -7
- package/lib/cjs/internal/types.js.map +1 -1
- package/lib/cjs/testing.d.ts +86 -1
- package/lib/cjs/testing.js +54 -0
- package/lib/cjs/testing.js.map +1 -1
- package/lib/cjs/types.d.ts +1 -1
- package/lib/esm/index.d.ts +4 -1
- package/lib/esm/index.js +3 -1
- 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 +89 -6
- package/lib/esm/internal/runtime.js +666 -51
- package/lib/esm/internal/runtime.js.map +1 -1
- package/lib/esm/internal/types.d.ts +174 -7
- package/lib/esm/internal/types.js.map +1 -1
- package/lib/esm/testing.d.ts +86 -1
- package/lib/esm/testing.js +53 -0
- package/lib/esm/testing.js.map +1 -1
- package/lib/esm/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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;
|
|
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
4
|
exports.asError = asError;
|
|
5
5
|
exports.quoteUnknown = quoteUnknown;
|
|
6
6
|
exports.quoteError = quoteError;
|
|
@@ -9,6 +9,7 @@ exports.hasInitialState = hasInitialState;
|
|
|
9
9
|
exports.getTransitionKey = getTransitionKey;
|
|
10
10
|
exports.defineStateName = defineStateName;
|
|
11
11
|
exports.getStateName = getStateName;
|
|
12
|
+
exports.lookupHandlerState = lookupHandlerState;
|
|
12
13
|
exports.lookupEventHandler = lookupEventHandler;
|
|
13
14
|
exports.InitialState = InitialState;
|
|
14
15
|
exports.registerStateNames = registerStateNames;
|
|
@@ -21,6 +22,7 @@ exports.serviceCallWithTimeout = serviceCallWithTimeout;
|
|
|
21
22
|
exports.createActorHandle = createActorHandle;
|
|
22
23
|
exports.getSelfNotificationsProto = getSelfNotificationsProto;
|
|
23
24
|
exports.createSelfNotifications = createSelfNotifications;
|
|
25
|
+
exports.currentTraceAnchor = currentTraceAnchor;
|
|
24
26
|
exports.planTransitionClasses = planTransitionClasses;
|
|
25
27
|
exports.executeTransitionRoutine = executeTransitionRoutine;
|
|
26
28
|
exports.createTransitionTracer = createTransitionTracer;
|
|
@@ -36,6 +38,8 @@ exports.makeActor = makeActor;
|
|
|
36
38
|
exports.asParentActor = asParentActor;
|
|
37
39
|
exports.makeChildActor = makeChildActor;
|
|
38
40
|
const types_1 = require("./types");
|
|
41
|
+
const identity_1 = require("./identity");
|
|
42
|
+
const instrumentation_1 = require("./instrumentation");
|
|
39
43
|
//#region TraceLevel
|
|
40
44
|
var TraceLevel;
|
|
41
45
|
(function (TraceLevel) {
|
|
@@ -269,6 +273,20 @@ exports.FatalErrorState = FatalErrorState;
|
|
|
269
273
|
defineStateName(TopState, 'TopState');
|
|
270
274
|
defineStateName(FatalErrorState, 'FatalErrorState');
|
|
271
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 */
|
|
272
290
|
function lookupEventHandler(hsm, eventName) {
|
|
273
291
|
let state = hsm.currentState;
|
|
274
292
|
while (true) {
|
|
@@ -496,19 +514,29 @@ function splitServiceArgs(args) {
|
|
|
496
514
|
}
|
|
497
515
|
return { callArgs: args.slice(0, -1), timeoutMs };
|
|
498
516
|
}
|
|
499
|
-
|
|
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) {
|
|
500
526
|
if (timeoutMs === 0) {
|
|
501
527
|
return Promise.reject(new CallTimeoutError(method));
|
|
502
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));
|
|
503
531
|
return new Promise((resolve, reject) => {
|
|
504
|
-
const
|
|
532
|
+
const handle = arm(() => {
|
|
505
533
|
reject(new CallTimeoutError(method));
|
|
506
|
-
}
|
|
534
|
+
});
|
|
507
535
|
promise.then(value => {
|
|
508
|
-
|
|
536
|
+
disarm(handle);
|
|
509
537
|
resolve(value);
|
|
510
538
|
}, err => {
|
|
511
|
-
|
|
539
|
+
disarm(handle);
|
|
512
540
|
reject(err);
|
|
513
541
|
});
|
|
514
542
|
});
|
|
@@ -540,8 +568,17 @@ function getFacetProto(topState, index, kind, facet) {
|
|
|
540
568
|
if (facet === 'call') {
|
|
541
569
|
built[name] = function (...args) {
|
|
542
570
|
const { callArgs, timeoutMs } = splitServiceArgs(args);
|
|
543
|
-
const
|
|
544
|
-
|
|
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);
|
|
545
582
|
};
|
|
546
583
|
}
|
|
547
584
|
else {
|
|
@@ -578,6 +615,12 @@ function createActorHandle(machine, topState, index, kind) {
|
|
|
578
615
|
Object.defineProperty(handle, 'notify', { value: createFacet(machine, topState, index, kind, 'notify'), enumerable: true });
|
|
579
616
|
Object.defineProperty(handle, 'notifyNow', { value: createFacet(machine, topState, index, kind, 'notifyNow'), enumerable: true });
|
|
580
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
|
+
});
|
|
581
624
|
handle.hsm = machine.actorHsmFor(kind);
|
|
582
625
|
return handle;
|
|
583
626
|
}
|
|
@@ -620,6 +663,18 @@ class SelfCallDeadlockError extends Error {
|
|
|
620
663
|
}
|
|
621
664
|
}
|
|
622
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
|
+
}
|
|
623
678
|
/**
|
|
624
679
|
* Lazy Node AsyncLocalStorage for non-production deadlock detection.
|
|
625
680
|
* State lives in a closure — no exported mutable slot.
|
|
@@ -654,6 +709,45 @@ exports.dispatchContext = (() => {
|
|
|
654
709
|
}
|
|
655
710
|
return { get, resetInit, markUnavailable };
|
|
656
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
|
+
})();
|
|
657
751
|
//#region transition-routines
|
|
658
752
|
/** Thrown when a generated transition table's graph hash does not match the scanned hierarchy. */
|
|
659
753
|
class TransitionTableError extends Error {
|
|
@@ -705,10 +799,14 @@ function planTransitionClasses(srcState, destState) {
|
|
|
705
799
|
}
|
|
706
800
|
return { exit: srcPath, entry: dstPath, finalState };
|
|
707
801
|
}
|
|
708
|
-
async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, toStateName, style, tracer) {
|
|
802
|
+
async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, toStateName, style, tracer, hookEvents) {
|
|
709
803
|
const statePrototype = state.prototype;
|
|
710
804
|
const stateName = getStateName(state);
|
|
711
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;
|
|
712
810
|
if ((style === 'verbose' || style === 'debug') && !hasHook) {
|
|
713
811
|
if (style === 'verbose') {
|
|
714
812
|
tracer?.traceHookSkipped(stateName, hook);
|
|
@@ -716,16 +814,19 @@ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, to
|
|
|
716
814
|
return;
|
|
717
815
|
}
|
|
718
816
|
try {
|
|
817
|
+
if (emitHookEvents) {
|
|
818
|
+
tracer?.traceHookStart?.(stateName, hook);
|
|
819
|
+
}
|
|
719
820
|
const res = statePrototype[hook].call(instance);
|
|
720
821
|
if (res) {
|
|
721
822
|
await res;
|
|
722
823
|
}
|
|
723
|
-
if (
|
|
824
|
+
if (emitHookEvents) {
|
|
724
825
|
tracer?.traceHookDone(stateName, hook);
|
|
725
826
|
}
|
|
726
827
|
}
|
|
727
828
|
catch (cause) {
|
|
728
|
-
if (
|
|
829
|
+
if (emitHookEvents) {
|
|
729
830
|
tracer?.traceHookError(stateName, hook, cause);
|
|
730
831
|
}
|
|
731
832
|
throw new TransitionError(hsm, asError(cause), stateName, hook, fromStateName, toStateName);
|
|
@@ -739,16 +840,27 @@ async function invokeLifecycleHook(hsm, instance, state, hook, fromStateName, to
|
|
|
739
840
|
async function executeTransitionRoutine(hsm, instance, plan, srcState, dstState, options = {}) {
|
|
740
841
|
const style = options.style ?? 'production';
|
|
741
842
|
const tracer = options.tracer;
|
|
843
|
+
const hookEvents = options.hookEvents ?? false;
|
|
742
844
|
const fromStateName = getStateName(srcState);
|
|
743
845
|
const toStateName = getStateName(dstState);
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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);
|
|
747
850
|
for (const state of plan.exit) {
|
|
748
|
-
await invokeLifecycleHook(hsm, instance, state, 'onExit', fromStateName, toStateName, style, tracer);
|
|
851
|
+
await invokeLifecycleHook(hsm, instance, state, 'onExit', fromStateName, toStateName, style, tracer, hookEvents);
|
|
749
852
|
}
|
|
853
|
+
let initializeOpened = false;
|
|
750
854
|
for (const state of plan.entry) {
|
|
751
|
-
|
|
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);
|
|
752
864
|
}
|
|
753
865
|
const applyState = (next) => {
|
|
754
866
|
if (options.setCurrentState) {
|
|
@@ -771,6 +883,8 @@ async function executeTransitionRoutine(hsm, instance, plan, srcState, dstState,
|
|
|
771
883
|
}
|
|
772
884
|
if (plan.finalState) {
|
|
773
885
|
applyState(plan.finalState);
|
|
886
|
+
// Close the structural transition span on the PRODUCTION path (verbose/debug returned above).
|
|
887
|
+
tracer?.traceTransitionDone(getStateName(plan.finalState));
|
|
774
888
|
}
|
|
775
889
|
}
|
|
776
890
|
function createTransitionTracer(hsm) {
|
|
@@ -778,6 +892,12 @@ function createTransitionTracer(hsm) {
|
|
|
778
892
|
traceTransitionStart(fromStateName, toStateName) {
|
|
779
893
|
hsm._tracePush(`transition from ${fromStateName} to ${toStateName}`, `started transition from ${fromStateName} to ${toStateName} `);
|
|
780
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
|
+
},
|
|
781
901
|
traceHookDone(stateName, hook) {
|
|
782
902
|
hsm._traceWrite(`${stateName}.${hook}() done`);
|
|
783
903
|
},
|
|
@@ -809,9 +929,15 @@ class RuntimeTransitionRoutine {
|
|
|
809
929
|
}
|
|
810
930
|
async execute(hsm, srcState, dstState) {
|
|
811
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);
|
|
812
935
|
await executeTransitionRoutine(hsm, hsm._instance, this.plan, srcState, dstState, {
|
|
813
936
|
style,
|
|
814
|
-
...(
|
|
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,
|
|
815
941
|
setCurrentState: state => {
|
|
816
942
|
hsm.currentState = state;
|
|
817
943
|
},
|
|
@@ -1405,42 +1531,60 @@ function createInitTask(host, resolver) {
|
|
|
1405
1531
|
.then(() => executePendingTransition(host, resolver))
|
|
1406
1532
|
.then(() => done())
|
|
1407
1533
|
.catch((err) => {
|
|
1408
|
-
host.
|
|
1534
|
+
host.reportDispatchError(asError(err));
|
|
1409
1535
|
done();
|
|
1410
1536
|
});
|
|
1411
1537
|
};
|
|
1412
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
|
+
}
|
|
1413
1563
|
/** @internal */
|
|
1414
1564
|
function createNotificationTask(host, resolver, name, args) {
|
|
1415
1565
|
const strategy = dispatchStrategyFor(host.traceLevel);
|
|
1416
1566
|
return (done) => {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1567
|
+
runWithinDispatch(host, () => {
|
|
1568
|
+
void strategy
|
|
1569
|
+
.dispatchEvent(host, resolver, name, ...args)
|
|
1570
|
+
.catch((err) => host.reportDispatchError(asError(err)))
|
|
1571
|
+
.finally(() => done());
|
|
1572
|
+
});
|
|
1421
1573
|
};
|
|
1422
1574
|
}
|
|
1423
1575
|
/** @internal */
|
|
1424
1576
|
function createServiceTask(host, resolver, name, args, resolve, reject) {
|
|
1425
|
-
const machine = host;
|
|
1426
1577
|
return (done) => {
|
|
1427
1578
|
const run = () => invokeHandler(host, resolver, name, args)
|
|
1428
1579
|
.then(resolve)
|
|
1429
1580
|
.catch((err) => {
|
|
1430
1581
|
reject(asError(err));
|
|
1431
1582
|
})
|
|
1432
|
-
.catch((err) => host.
|
|
1583
|
+
.catch((err) => host.reportDispatchError(asError(err)))
|
|
1433
1584
|
.finally(() => done());
|
|
1434
|
-
|
|
1435
|
-
void run();
|
|
1436
|
-
return;
|
|
1437
|
-
}
|
|
1438
|
-
const storage = exports.dispatchContext.get();
|
|
1439
|
-
if (storage === undefined) {
|
|
1585
|
+
runWithinDispatch(host, () => {
|
|
1440
1586
|
void run();
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1443
|
-
void storage.run({ machine }, run);
|
|
1587
|
+
});
|
|
1444
1588
|
};
|
|
1445
1589
|
}
|
|
1446
1590
|
//#endregion
|
|
@@ -1451,6 +1595,9 @@ class HsmObject {
|
|
|
1451
1595
|
topStateName;
|
|
1452
1596
|
ctxTypeName;
|
|
1453
1597
|
traceWriter;
|
|
1598
|
+
actorUuid;
|
|
1599
|
+
actorName;
|
|
1600
|
+
actorPath;
|
|
1454
1601
|
/** @internal */
|
|
1455
1602
|
_instance;
|
|
1456
1603
|
/** @internal */
|
|
@@ -1465,7 +1612,9 @@ class HsmObject {
|
|
|
1465
1612
|
dispatchErrorCallback;
|
|
1466
1613
|
_traceLevel;
|
|
1467
1614
|
_traceDomainStack;
|
|
1468
|
-
|
|
1615
|
+
_instrumentationHost;
|
|
1616
|
+
_drainWaiters = [];
|
|
1617
|
+
constructor(TopState, instance, traceWriter, traceLevel, dispatchErrorCallback, identity) {
|
|
1469
1618
|
this._instance = instance;
|
|
1470
1619
|
this._transitionState = undefined;
|
|
1471
1620
|
this._traceLevel = traceLevel;
|
|
@@ -1475,6 +1624,9 @@ class HsmObject {
|
|
|
1475
1624
|
this._jobs = [];
|
|
1476
1625
|
this._hiPriorityJobs = [];
|
|
1477
1626
|
this._isRunning = false;
|
|
1627
|
+
this.actorUuid = identity.uuid;
|
|
1628
|
+
this.actorName = identity.name;
|
|
1629
|
+
this.actorPath = identity.path;
|
|
1478
1630
|
this.topState = TopState;
|
|
1479
1631
|
this.topStateName = getStateName(TopState);
|
|
1480
1632
|
this.ctxTypeName = Object.getPrototypeOf(instance.ctx).constructor.name;
|
|
@@ -1491,6 +1643,14 @@ class HsmObject {
|
|
|
1491
1643
|
get port() {
|
|
1492
1644
|
return this._instance.portRef;
|
|
1493
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
|
+
}
|
|
1494
1654
|
get eventName() {
|
|
1495
1655
|
return this._currentEventName ?? '';
|
|
1496
1656
|
}
|
|
@@ -1538,14 +1698,37 @@ class HsmObject {
|
|
|
1538
1698
|
set traceLevel(traceLevel) {
|
|
1539
1699
|
this._traceLevel = traceLevel;
|
|
1540
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
|
+
*/
|
|
1541
1709
|
sync() {
|
|
1542
1710
|
return new Promise(resolve => {
|
|
1543
|
-
this.
|
|
1544
|
-
|
|
1711
|
+
this._drainWaiters.push(resolve);
|
|
1712
|
+
const task = (doneCallback) => {
|
|
1545
1713
|
doneCallback();
|
|
1546
|
-
}
|
|
1714
|
+
};
|
|
1715
|
+
(0, instrumentation_1.setTaskMeta)(task, { internal: true });
|
|
1716
|
+
this.pushTask(task);
|
|
1547
1717
|
});
|
|
1548
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
|
+
}
|
|
1549
1732
|
pushTask(t) {
|
|
1550
1733
|
this.enqueueTask(t, this._jobs);
|
|
1551
1734
|
}
|
|
@@ -1573,6 +1756,8 @@ class HsmObject {
|
|
|
1573
1756
|
dequeue() {
|
|
1574
1757
|
if (this._hiPriorityJobs.length == 0 && this._jobs.length == 0) {
|
|
1575
1758
|
this._isRunning = false;
|
|
1759
|
+
this._instrumentationHost?.onQueuesDrained();
|
|
1760
|
+
this.flushDrainWaiters();
|
|
1576
1761
|
return;
|
|
1577
1762
|
}
|
|
1578
1763
|
const task = this._hiPriorityJobs.length > 0 ? this._hiPriorityJobs.shift() : this._jobs.shift();
|
|
@@ -1582,10 +1767,32 @@ class HsmObject {
|
|
|
1582
1767
|
setTimeout(() => this.runTask(task).then(() => this.dequeue()), 0);
|
|
1583
1768
|
}
|
|
1584
1769
|
runTask(task) {
|
|
1770
|
+
this._instrumentationHost?.onTaskBegin(task);
|
|
1771
|
+
let outcome = 'ok';
|
|
1585
1772
|
return new Promise(resolve => {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
+
}
|
|
1589
1796
|
});
|
|
1590
1797
|
}
|
|
1591
1798
|
drainHiPriority() {
|
|
@@ -1613,19 +1820,52 @@ class HsmObject {
|
|
|
1613
1820
|
get traceHeader() {
|
|
1614
1821
|
return `${this._traceDomainStack.length === 0 ? '' : this._traceDomainStack.join('|') + '|'}`;
|
|
1615
1822
|
}
|
|
1823
|
+
get traceFrames() {
|
|
1824
|
+
return this._traceDomainStack.map(name => ({ name, kind: classifyFrameKind(name) }));
|
|
1825
|
+
}
|
|
1616
1826
|
}
|
|
1617
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
|
+
}
|
|
1618
1844
|
//#region machine
|
|
1619
1845
|
class Machine extends HsmObject {
|
|
1620
1846
|
transitionResolver;
|
|
1847
|
+
identity;
|
|
1848
|
+
instrumentation;
|
|
1621
1849
|
_dispatchStrategy;
|
|
1622
1850
|
protocolIndex;
|
|
1623
1851
|
handlerFacade;
|
|
1624
1852
|
selfActor;
|
|
1625
1853
|
selfImmediate;
|
|
1626
1854
|
actorFacades = new Map();
|
|
1627
|
-
|
|
1628
|
-
|
|
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
|
+
}
|
|
1629
1869
|
Object.defineProperty(instance, types_1.kHandlerMachine, { value: this, enumerable: false, writable: false, configurable: false });
|
|
1630
1870
|
this.protocolIndex = protocolIndex;
|
|
1631
1871
|
cacheProtocolIndex(topState, protocolIndex);
|
|
@@ -1639,7 +1879,227 @@ class Machine extends HsmObject {
|
|
|
1639
1879
|
Object.defineProperty(instance, 'notifyNow', { value: this.selfImmediate, enumerable: true, configurable: true });
|
|
1640
1880
|
this.bindPort(instance.portRef);
|
|
1641
1881
|
if (initialize) {
|
|
1642
|
-
|
|
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);
|
|
1643
2103
|
}
|
|
1644
2104
|
}
|
|
1645
2105
|
get traceLevel() {
|
|
@@ -1659,12 +2119,22 @@ class Machine extends HsmObject {
|
|
|
1659
2119
|
}
|
|
1660
2120
|
this.recordObserverEvent(name, args);
|
|
1661
2121
|
return new Promise((resolve, reject) => {
|
|
1662
|
-
|
|
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);
|
|
1663
2128
|
});
|
|
1664
2129
|
}
|
|
1665
2130
|
dispatchNotification(name, args, queue) {
|
|
1666
2131
|
this.recordObserverEvent(name, args);
|
|
1667
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
|
+
}
|
|
1668
2138
|
if (queue === 'priority') {
|
|
1669
2139
|
this.pushHiPriorityTask(task);
|
|
1670
2140
|
}
|
|
@@ -1681,14 +2151,60 @@ class Machine extends HsmObject {
|
|
|
1681
2151
|
return facade;
|
|
1682
2152
|
}
|
|
1683
2153
|
scheduleNotification(ms, name, args) {
|
|
1684
|
-
const enqueue = () => {
|
|
1685
|
-
this.dispatchNotification(name, args, 'default');
|
|
1686
|
-
};
|
|
1687
2154
|
const port = this._instance.portRef;
|
|
1688
2155
|
if (port === undefined) {
|
|
1689
2156
|
throw new Error('ihsm: deferred notification requires a port');
|
|
1690
2157
|
}
|
|
1691
|
-
|
|
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);
|
|
1692
2208
|
}
|
|
1693
2209
|
/** @internal Binds deferred self-notifications to a port instance. */
|
|
1694
2210
|
bindPort(portRef) {
|
|
@@ -1704,7 +2220,50 @@ class Machine extends HsmObject {
|
|
|
1704
2220
|
},
|
|
1705
2221
|
transition: next => machine.transition(next),
|
|
1706
2222
|
get port() {
|
|
1707
|
-
|
|
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;
|
|
1708
2267
|
},
|
|
1709
2268
|
unhandled: () => machine.unhandled(),
|
|
1710
2269
|
get eventName() {
|
|
@@ -1728,6 +2287,24 @@ class Machine extends HsmObject {
|
|
|
1728
2287
|
get traceHeader() {
|
|
1729
2288
|
return machine.traceHeader;
|
|
1730
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
|
+
},
|
|
1731
2308
|
get traceLevel() {
|
|
1732
2309
|
return machine.traceLevel;
|
|
1733
2310
|
},
|
|
@@ -1795,6 +2372,18 @@ class Machine extends HsmObject {
|
|
|
1795
2372
|
get traceHeader() {
|
|
1796
2373
|
return machine.traceHeader;
|
|
1797
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
|
+
},
|
|
1798
2387
|
};
|
|
1799
2388
|
if (includeState) {
|
|
1800
2389
|
Object.defineProperties(facade, {
|
|
@@ -1865,9 +2454,16 @@ function defaultDispatchErrorCallback(hsm, err) {
|
|
|
1865
2454
|
throw err;
|
|
1866
2455
|
}
|
|
1867
2456
|
/** @internal Spawn with embodiment kind — used by factories and `ihsm/testing`. */
|
|
1868
|
-
function spawnActor(kind, topState, ctx, port, options) {
|
|
2457
|
+
function spawnActor(kind, topState, ctx, port, options, spawnContext = {}) {
|
|
1869
2458
|
const { initialize = exports.defaultInitialize, traceLevel = TraceLevel.DEBUG, traceWriter = exports.defaultTraceWriter, dispatchErrorCallback = defaultDispatchErrorCallback, transitions } = options;
|
|
1870
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));
|
|
1871
2467
|
const boundPort = (port ?? new Port());
|
|
1872
2468
|
const instance = {
|
|
1873
2469
|
ctx,
|
|
@@ -1875,7 +2471,26 @@ function spawnActor(kind, topState, ctx, port, options) {
|
|
|
1875
2471
|
portRef: boundPort,
|
|
1876
2472
|
};
|
|
1877
2473
|
Object.setPrototypeOf(instance, topState.prototype);
|
|
1878
|
-
const machine = new Machine(topState, instance, protocolIndex, traceWriter, traceLevel, dispatchErrorCallback, initialize, transitions ?? new RuntimeTransitionResolver());
|
|
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
|
+
}
|
|
1879
2494
|
const portKind = isRequestingPort(boundPort) ? 'child' : kind === 'root' ? 'inbound' : kind;
|
|
1880
2495
|
boundPort.actor = createActorHandle(machine, topState, protocolIndex, portKind);
|
|
1881
2496
|
return createActorHandle(machine, topState, protocolIndex, kind);
|
|
@@ -1896,11 +2511,20 @@ function asParentActor(handler) {
|
|
|
1896
2511
|
}
|
|
1897
2512
|
/** Parent composes a child machine — returns full child protocol shell with `parent` set. */
|
|
1898
2513
|
function makeChildActor(parent, childTop, childCtx, port, options = {}) {
|
|
1899
|
-
const
|
|
2514
|
+
const parentMachine = parent[types_1.kParentLink];
|
|
2515
|
+
const child = spawnActor('child', childTop, childCtx, port, options, { parentMachine: parentMachine });
|
|
1900
2516
|
Object.defineProperty(child, 'parent', { value: parent, enumerable: true, writable: false, configurable: true });
|
|
1901
2517
|
return child;
|
|
1902
2518
|
}
|
|
1903
2519
|
var types_2 = require("./types");
|
|
1904
2520
|
Object.defineProperty(exports, "kHandlerMachine", { enumerable: true, get: function () { return types_2.kHandlerMachine; } });
|
|
1905
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; } });
|
|
1906
2530
|
//# sourceMappingURL=runtime.js.map
|