@zlink-systems/framework 0.28.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/Builders.d.ts +2 -0
  3. package/dist/contracts/Configuration/InternalDefaults.d.ts +3 -0
  4. package/dist/contracts/Configuration/InternalDefaults.js +4 -1
  5. package/dist/contracts/Configuration/RegistrationBuilders.js +9 -0
  6. package/dist/contracts/Configuration/RegistrationNormalizers.d.ts +1 -0
  7. package/dist/contracts/Configuration/RegistrationNormalizers.js +16 -0
  8. package/dist/contracts/Configuration/RegistrationTypes.d.ts +3 -0
  9. package/dist/runtime/actors/actor-mailbox.d.ts +2 -2
  10. package/dist/runtime/actors/actor-mailbox.js +2 -2
  11. package/dist/runtime/actors/actor-runtime-contracts.d.ts +1 -0
  12. package/dist/runtime/actors/index.d.ts +2 -0
  13. package/dist/runtime/actors/index.js +21 -6
  14. package/dist/runtime/backend/node/node-raw-mesh-backend.js +1 -3
  15. package/dist/runtime/execution/serial-execution-queue.d.ts +12 -3
  16. package/dist/runtime/execution/serial-execution-queue.js +116 -28
  17. package/dist/runtime/foundation/operation-registry.d.ts +3 -2
  18. package/dist/runtime/foundation/operation-registry.js +5 -1
  19. package/dist/runtime/foundation/service-authority-payload-codec.d.ts +0 -2
  20. package/dist/runtime/foundation/service-authority-payload-codec.js +0 -43
  21. package/dist/runtime/foundation/service-stateful-registry.d.ts +4 -3
  22. package/dist/runtime/foundation/service-stateful-registry.js +5 -2
  23. package/dist/runtime/foundation/service-stateful-runtime.d.ts +1 -8
  24. package/dist/runtime/foundation/service-stateful-runtime.js +105 -145
  25. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -11
  26. package/dist/runtime/foundation/service-stateful-wire-codec.js +1 -23
  27. package/dist/runtime/foundation/service-topology-registry.d.ts +5 -0
  28. package/dist/runtime/foundation/service-topology-registry.js +16 -17
  29. package/dist/runtime/handlers/handler-instance-scope.js +1 -1
  30. package/dist/runtime/host/actor-placement-coordinator.d.ts +2 -1
  31. package/dist/runtime/host/actor-placement-coordinator.js +2 -1
  32. package/dist/runtime/host/actor-runtime-options-factory.d.ts +2 -1
  33. package/dist/runtime/host/actor-runtime-options-factory.js +1 -0
  34. package/dist/runtime/host/index.d.ts +1 -1
  35. package/dist/runtime/host/index.js +3 -2
  36. package/dist/runtime/host/instance-activation-authority.d.ts +0 -1
  37. package/dist/runtime/host/instance-activation-authority.js +46 -97
  38. package/dist/runtime/host/spot-address-transport.js +1 -1
  39. package/dist/runtime/host/stateful-authority-route-runtime.d.ts +1 -0
  40. package/dist/runtime/host/stateful-authority-route-runtime.js +17 -9
  41. package/dist/runtime/locations/domain-store-contract.d.ts +3 -1
  42. package/dist/runtime/locations/in-memory-authority-store.d.ts +3 -0
  43. package/dist/runtime/locations/in-memory-authority-store.js +47 -12
  44. package/dist/runtime/locations/in-memory-location-store.d.ts +2 -1
  45. package/dist/runtime/locations/in-memory-location-store.js +15 -10
  46. package/dist/runtime/locations/internal-location-contracts.d.ts +5 -0
  47. package/dist/runtime/locations/internal-store-contracts.d.ts +1 -1
  48. package/dist/runtime/locations/location-store-repository.d.ts +2 -0
  49. package/dist/runtime/locations/location-store-repository.js +130 -54
  50. package/dist/runtime/spots/spot-entry-activation.d.ts +1 -0
  51. package/dist/runtime/spots/spot-entry-activation.js +3 -0
  52. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +1 -0
  53. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -0
  54. package/dist/runtime/spots/spot-serial-executor.d.ts +1 -0
  55. package/dist/runtime/spots/spot-serial-executor.js +14 -2
  56. package/dist/runtime/spots/spot-serial-turn-executor.d.ts +3 -0
  57. package/dist/runtime/spots/spot-serial-turn-executor.js +5 -0
  58. package/dist/runtime/streams/index.d.ts +0 -1
  59. package/dist/runtime/streams/index.js +4 -3
  60. package/dist/runtime/streams/protocol.js +0 -9
  61. package/dist/runtime/streams/session-context.d.ts +0 -4
  62. package/dist/runtime/streams/session-context.js +0 -26
  63. package/dist/runtime/streams/session-serial-executor.d.ts +0 -1
  64. package/dist/runtime/streams/session-serial-executor.js +0 -3
  65. package/dist/runtime/streams/stream-message-utils.d.ts +0 -1
  66. package/dist/runtime/streams/stream-message-utils.js +0 -20
  67. package/dist/runtime/streams/stream-session-runtime.d.ts +6 -3
  68. package/dist/runtime/streams/stream-session-runtime.js +40 -85
  69. package/package.json +3 -3
  70. package/src/contracts/Configuration/Builders.ts +2 -0
  71. package/src/contracts/Configuration/InternalDefaults.ts +4 -0
  72. package/src/contracts/Configuration/RegistrationBuilders.ts +13 -0
  73. package/src/contracts/Configuration/RegistrationNormalizers.ts +25 -0
  74. package/src/contracts/Configuration/RegistrationTypes.ts +3 -0
  75. package/src/runtime/actors/actor-mailbox.ts +7 -2
  76. package/src/runtime/actors/actor-runtime-contracts.ts +4 -0
  77. package/src/runtime/actors/index.ts +77 -42
  78. package/src/runtime/backend/node/node-raw-mesh-backend.ts +1 -7
  79. package/src/runtime/execution/serial-execution-queue.ts +117 -29
  80. package/src/runtime/foundation/operation-registry.ts +16 -9
  81. package/src/runtime/foundation/service-authority-payload-codec.ts +0 -46
  82. package/src/runtime/foundation/service-stateful-registry.ts +10 -5
  83. package/src/runtime/foundation/service-stateful-runtime.ts +152 -224
  84. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -25
  85. package/src/runtime/foundation/service-topology-registry.ts +30 -20
  86. package/src/runtime/handlers/handler-instance-scope.ts +2 -1
  87. package/src/runtime/host/actor-placement-coordinator.ts +8 -2
  88. package/src/runtime/host/actor-runtime-options-factory.ts +6 -0
  89. package/src/runtime/host/index.ts +6 -2
  90. package/src/runtime/host/instance-activation-authority.ts +58 -125
  91. package/src/runtime/host/spot-address-transport.ts +1 -1
  92. package/src/runtime/host/stateful-authority-route-runtime.ts +22 -9
  93. package/src/runtime/locations/domain-store-contract.ts +6 -1
  94. package/src/runtime/locations/in-memory-authority-store.ts +83 -12
  95. package/src/runtime/locations/in-memory-location-store.ts +22 -10
  96. package/src/runtime/locations/internal-location-contracts.ts +6 -0
  97. package/src/runtime/locations/internal-store-contracts.ts +1 -0
  98. package/src/runtime/locations/location-store-repository.ts +182 -69
  99. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +1 -1
  100. package/src/runtime/spots/spot-entry-activation.ts +4 -0
  101. package/src/runtime/spots/spot-node-runtime-manager.ts +7 -0
  102. package/src/runtime/spots/spot-serial-executor.ts +14 -5
  103. package/src/runtime/spots/spot-serial-turn-executor.ts +10 -0
  104. package/src/runtime/streams/index.ts +3 -1
  105. package/src/runtime/streams/protocol.ts +0 -13
  106. package/src/runtime/streams/session-context.ts +0 -32
  107. package/src/runtime/streams/session-serial-executor.ts +0 -8
  108. package/src/runtime/streams/stream-message-utils.ts +1 -27
  109. package/src/runtime/streams/stream-session-runtime.ts +54 -97
  110. package/dist/runtime/streams/session-requests.d.ts +0 -25
  111. package/dist/runtime/streams/session-requests.js +0 -101
  112. package/src/runtime/streams/session-requests.ts +0 -110
@@ -142,6 +142,8 @@ export interface ZLinkFanoutChannelBuilder {
142
142
  subscriberConnections(): ZLinkEndpointConnections;
143
143
  }
144
144
  export interface ZLinkStreamNodeBuilder {
145
+ setHeartbeat(intervalMs: number, timeoutMs: number): this;
146
+ setIdleTimeout(timeoutMs: number): this;
145
147
  bind(endpointOrPort?: string | number): this;
146
148
  setBindHost(bindHost: string): this;
147
149
  setAdvertiseHost(advertiseHost: string): this;
@@ -12,3 +12,6 @@ export declare const ZLINK_RELOCATION_MAX_BLOB_REFERENCE_BYTES = 4096;
12
12
  export declare const AUTHORITY_ENVELOPE_MAX_BYTES: number;
13
13
  export declare const CREATION_INTENT_MAX_BYTES: number;
14
14
  export declare const CREATION_TERMINAL_ENVELOPE_MAX_BYTES: number;
15
+ export declare const DEFAULT_STREAM_HEARTBEAT_INTERVAL_MS = 1000;
16
+ export declare const DEFAULT_STREAM_HEARTBEAT_TIMEOUT_MS = 5000;
17
+ export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 0;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.CREATION_TERMINAL_ENVELOPE_MAX_BYTES = exports.CREATION_INTENT_MAX_BYTES = exports.AUTHORITY_ENVELOPE_MAX_BYTES = exports.ZLINK_RELOCATION_MAX_BLOB_REFERENCE_BYTES = exports.DEFAULT_WORKER_IDLE_TIMEOUT_MS = exports.DEFAULT_WORKER_MIN_THREADS = exports.DEFAULT_ACTIVATION_CONCURRENCY_LIMIT = exports.RELOCATION_STATE_CHUNK_DATA_MAX_BYTES = exports.DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE = void 0;
36
+ exports.DEFAULT_STREAM_IDLE_TIMEOUT_MS = exports.DEFAULT_STREAM_HEARTBEAT_TIMEOUT_MS = exports.DEFAULT_STREAM_HEARTBEAT_INTERVAL_MS = exports.CREATION_TERMINAL_ENVELOPE_MAX_BYTES = exports.CREATION_INTENT_MAX_BYTES = exports.AUTHORITY_ENVELOPE_MAX_BYTES = exports.ZLINK_RELOCATION_MAX_BLOB_REFERENCE_BYTES = exports.DEFAULT_WORKER_IDLE_TIMEOUT_MS = exports.DEFAULT_WORKER_MIN_THREADS = exports.DEFAULT_ACTIVATION_CONCURRENCY_LIMIT = exports.RELOCATION_STATE_CHUNK_DATA_MAX_BYTES = exports.DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE = void 0;
37
37
  exports.defaultWorkerMaxThreads = defaultWorkerMaxThreads;
38
38
  const os = __importStar(require("node:os"));
39
39
  /** Default complete header plus payload size accepted by a StreamNode. */
@@ -52,3 +52,6 @@ exports.ZLINK_RELOCATION_MAX_BLOB_REFERENCE_BYTES = 4096;
52
52
  exports.AUTHORITY_ENVELOPE_MAX_BYTES = 1024 * 1024;
53
53
  exports.CREATION_INTENT_MAX_BYTES = 1024 * 1024;
54
54
  exports.CREATION_TERMINAL_ENVELOPE_MAX_BYTES = 1024 * 1024;
55
+ exports.DEFAULT_STREAM_HEARTBEAT_INTERVAL_MS = 1_000;
56
+ exports.DEFAULT_STREAM_HEARTBEAT_TIMEOUT_MS = 5_000;
57
+ exports.DEFAULT_STREAM_IDLE_TIMEOUT_MS = 0;
@@ -522,6 +522,15 @@ class DefaultClientServerChannelServerBuilder {
522
522
  }
523
523
  class DefaultStreamNodeBuilder {
524
524
  streamNode;
525
+ setHeartbeat(intervalMs, timeoutMs) {
526
+ this.streamNode.heartbeatIntervalMs = intervalMs;
527
+ this.streamNode.heartbeatTimeoutMs = timeoutMs;
528
+ return this;
529
+ }
530
+ setIdleTimeout(timeoutMs) {
531
+ this.streamNode.idleTimeoutMs = timeoutMs;
532
+ return this;
533
+ }
525
534
  constructor(streamNode) {
526
535
  this.streamNode = streamNode;
527
536
  }
@@ -4,6 +4,7 @@ import type { ZLinkNetworkOptions } from './Builders';
4
4
  export declare function normalizeWorkerOptions(value: ZLinkFrameworkRegistrationOptions['worker']): ZLinkWorkerOptions | undefined;
5
5
  export declare function normalizeNetworkOptions(value: ZLinkFrameworkRegistrationOptions['network']): ZLinkNetworkOptions;
6
6
  export declare function toChannelMap(channels: ZLinkFrameworkRegistrationOptions['channels'], network: ZLinkNetworkOptions): Map<string, ZLinkChannelOptions>;
7
+ export declare function normalizeStreamLiveness(options: ZLinkStreamNodeOptions): Required<Pick<ZLinkStreamNodeOptions, 'heartbeatIntervalMs' | 'heartbeatTimeoutMs' | 'idleTimeoutMs'>>;
7
8
  export declare function toStreamNodeMap(streamNodes: ZLinkFrameworkRegistrationOptions['streamNodes'], network: ZLinkNetworkOptions): Map<string, ZLinkStreamNodeOptions>;
8
9
  export declare function toRouteChannelOptions(options: ZLinkFrameworkRegistrationOptions): Map<string, ZLinkRouteChannelOptions>;
9
10
  export declare function requirePositiveInteger(label: string, value: number | undefined): void;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeWorkerOptions = normalizeWorkerOptions;
4
4
  exports.normalizeNetworkOptions = normalizeNetworkOptions;
5
5
  exports.toChannelMap = toChannelMap;
6
+ exports.normalizeStreamLiveness = normalizeStreamLiveness;
6
7
  exports.toStreamNodeMap = toStreamNodeMap;
7
8
  exports.toRouteChannelOptions = toRouteChannelOptions;
8
9
  exports.requirePositiveInteger = requirePositiveInteger;
@@ -60,6 +61,20 @@ function toChannelMap(channels, network) {
60
61
  ];
61
62
  }));
62
63
  }
64
+ function normalizeStreamLiveness(options) {
65
+ const heartbeatIntervalMs = options.heartbeatIntervalMs ?? InternalDefaults_1.DEFAULT_STREAM_HEARTBEAT_INTERVAL_MS;
66
+ const heartbeatTimeoutMs = options.heartbeatTimeoutMs ?? InternalDefaults_1.DEFAULT_STREAM_HEARTBEAT_TIMEOUT_MS;
67
+ const idleTimeoutMs = options.idleTimeoutMs ?? InternalDefaults_1.DEFAULT_STREAM_IDLE_TIMEOUT_MS;
68
+ if (!Number.isFinite(heartbeatIntervalMs) || heartbeatIntervalMs <= 0)
69
+ throw new ConfigurationException_1.ZLinkConfigurationException('STREAM heartbeat interval must be positive.');
70
+ if (!Number.isFinite(heartbeatTimeoutMs) || heartbeatTimeoutMs <= 0)
71
+ throw new ConfigurationException_1.ZLinkConfigurationException('STREAM heartbeat timeout must be positive.');
72
+ if (heartbeatTimeoutMs <= heartbeatIntervalMs)
73
+ throw new ConfigurationException_1.ZLinkConfigurationException('STREAM heartbeat timeout must be greater than interval.');
74
+ if (!Number.isFinite(idleTimeoutMs) || idleTimeoutMs < 0)
75
+ throw new ConfigurationException_1.ZLinkConfigurationException('STREAM idle timeout must not be negative.');
76
+ return { heartbeatIntervalMs, heartbeatTimeoutMs, idleTimeoutMs };
77
+ }
63
78
  function toStreamNodeMap(streamNodes, network) {
64
79
  return new Map(Object.entries(streamNodes ?? {}).map(([name, streamNode]) => {
65
80
  const normalized = normalizeListener(streamNode, network);
@@ -67,6 +82,7 @@ function toStreamNodeMap(streamNodes, network) {
67
82
  name,
68
83
  {
69
84
  ...normalized,
85
+ ...normalizeStreamLiveness(streamNode),
70
86
  maxMessageSize: streamNode.maxMessageSize ?? InternalDefaults_1.DEFAULT_STREAM_NODE_MAX_MESSAGE_SIZE
71
87
  }
72
88
  ];
@@ -201,6 +201,9 @@ export interface ZLinkRouteChannelOptions {
201
201
  readonly handlers?: readonly ZLinkRouteChannelHandlerOptions[];
202
202
  }
203
203
  export interface ZLinkStreamNodeOptions {
204
+ readonly heartbeatIntervalMs?: number;
205
+ readonly heartbeatTimeoutMs?: number;
206
+ readonly idleTimeoutMs?: number;
204
207
  readonly bind?: string;
205
208
  readonly bindHost?: string;
206
209
  readonly advertiseHost?: string;
@@ -1,9 +1,9 @@
1
- import { type ZLinkSerialSchedulerOptions, type ZLinkSerialWorkOptions } from '../execution/serial-execution-queue';
1
+ import { ZLinkSerialExecutionQueue, type ZLinkSerialSchedulerOptions, type ZLinkSerialWorkOptions } from '../execution/serial-execution-queue';
2
2
  export declare class ZLinkActorSerialExecutor {
3
3
  private readonly actorId;
4
4
  private readonly sourceSpotId;
5
5
  private readonly scheduler;
6
- constructor(actorId: string, sourceSpotId: unknown, options?: ZLinkSerialSchedulerOptions);
6
+ constructor(actorId: string, sourceSpotId: unknown, options?: ZLinkSerialSchedulerOptions, sharedOwner?: ZLinkSerialExecutionQueue);
7
7
  execute<T>(operation: () => Promise<T> | T, workOptions?: ZLinkSerialWorkOptions): Promise<T>;
8
8
  close(): Promise<void>;
9
9
  private runRecord;
@@ -8,10 +8,10 @@ class ZLinkActorSerialExecutor {
8
8
  actorId;
9
9
  sourceSpotId;
10
10
  scheduler;
11
- constructor(actorId, sourceSpotId, options) {
11
+ constructor(actorId, sourceSpotId, options, sharedOwner) {
12
12
  this.actorId = actorId;
13
13
  this.sourceSpotId = sourceSpotId;
14
- this.scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue((record) => this.runRecord(record), options);
14
+ this.scheduler = new serial_execution_queue_1.ZLinkSerialExecutionQueue((record) => this.runRecord(record), options, sharedOwner);
15
15
  }
16
16
  execute(operation, workOptions) {
17
17
  const boundOperation = (0, application_job_queue_scope_1.bindApplicationJobPermit)(() => (0, actor_execution_context_1.runZLinkActorExecution)(this.actorId, this.sourceSpotId, operation));
@@ -27,6 +27,7 @@ export interface ZLinkActorManagerOptions {
27
27
  readonly actorRefResolver?: ZLinkActorRuntimeLocationLookup;
28
28
  readonly actorCreatedNotifier?: (nodeRid: RoutingId, actor: ZLinkActor, createRequest: ZLinkMessage, signal?: AbortSignal) => Promise<ZLinkActorCreateResponse | undefined>;
29
29
  readonly actorDestroyedCleanup?: (actorId: string) => void;
30
+ readonly terminateActorActivation?: (actor: ZLinkActor, terminal: () => Promise<void>) => Promise<void>;
30
31
  readonly publishActorAuthority?: (actorType: string, actorRef: ActorRef, ownerNodeGeneration: bigint, signal?: AbortSignal) => Promise<void>;
31
32
  readonly locationLifecycle?: ZLinkLocationLifecycle;
32
33
  readonly boundSessionFactory?: ZLinkActorBoundSessionFactory;
@@ -55,6 +55,8 @@ export declare class DefaultZLinkActorManager implements ZLinkActorManager {
55
55
  completeCoreRelocationSource(actorId: string): Promise<void>;
56
56
  private finalizeActorState;
57
57
  private finalizeDestroyedActor;
58
+ private terminateActor;
59
+ private awaitActorDestroy;
58
60
  getOrCreateActor(actorId: string, actorType: string, signal?: AbortSignal): Promise<ZLinkActor>;
59
61
  getOrCreateWithNativeRef(actorId: string, actorType: string, actorRef: ZLinkBackendActorRef, createRequest?: unknown, signal?: AbortSignal): Promise<ZLinkActor>;
60
62
  getOrCreateActorResult(actorId: string, actorType: string, createRequest?: unknown, signal?: AbortSignal): Promise<ZLinkActorLocalCreateResult>;
@@ -11,6 +11,7 @@ const runtime_metrics_1 = require("../diagnostics/runtime-metrics");
11
11
  const execution_1 = require("../execution");
12
12
  const framework_errors_internal_1 = require("../framework-errors-internal");
13
13
  const handler_instance_scope_1 = require("../handlers/handler-instance-scope");
14
+ const actor_execution_context_1 = require("./actor-execution-context");
14
15
  const payload_codec_1 = require("../messaging/payload-codec");
15
16
  const actor_creation_1 = require("./actor-creation");
16
17
  const actor_runtime_state_1 = require("./actor-runtime-state");
@@ -139,7 +140,7 @@ class DefaultZLinkActorManager {
139
140
  const entryNodeRid = state.entryNodeRid ?? current.nodeRid;
140
141
  if (node === undefined || completions === undefined)
141
142
  return false;
142
- const destroyTask = state.getOrStartDestroy(entryNodeRid, async (nativeRef) => {
143
+ const destroyTask = state.getOrStartDestroy(entryNodeRid, (nativeRef) => this.terminateActor(state, async () => {
143
144
  if (nativeRef !== undefined) {
144
145
  const completion = await completions.submit(() => node.destroyActor(nativeRef, 0), signal);
145
146
  try {
@@ -161,9 +162,9 @@ class DefaultZLinkActorManager {
161
162
  await this.options.locationLifecycle?.releaseActor(state.actorType, actor.actorId, current);
162
163
  }
163
164
  await this.finalizeDestroyedActor(actor.actorId, state);
164
- });
165
+ }));
165
166
  try {
166
- await destroyTask;
167
+ await this.awaitActorDestroy(state, destroyTask);
167
168
  }
168
169
  catch (error) {
169
170
  state.clearFailedDestroy(destroyTask);
@@ -381,6 +382,20 @@ class DefaultZLinkActorManager {
381
382
  this.options.metrics?.change(runtime_metrics_1.METRIC_NAMES.ActorCount, -1);
382
383
  }
383
384
  }
385
+ terminateActor(state, terminal) {
386
+ const actor = state.actor;
387
+ const completion = actor === undefined || this.options.terminateActorActivation === undefined
388
+ ? terminal()
389
+ : this.options.terminateActorActivation(actor, terminal);
390
+ return completion;
391
+ }
392
+ awaitActorDestroy(state, completion) {
393
+ if ((0, actor_execution_context_1.currentZLinkActorExecution)()?.actorId === state.actorId) {
394
+ this.detachedTaskRunner.runDetached('Actor terminal cleanup', () => completion);
395
+ return Promise.resolve();
396
+ }
397
+ return completion;
398
+ }
384
399
  async getOrCreateActor(actorId, actorType, signal) {
385
400
  this.rememberActorMeshFromType(actorId, actorType);
386
401
  const result = await this.createOrGet(actorId, actorType, false, undefined, signal);
@@ -429,7 +444,7 @@ class DefaultZLinkActorManager {
429
444
  if (state.isJoined) {
430
445
  throw (0, framework_errors_internal_1.createInternalFrameworkException)(framework_errors_internal_1.ZLinkFrameworkInternalErrorKind.ActorRouteNotFound, `Actor '${actor.context.actorId}' must leave its current SPOT before destroy.`);
431
446
  }
432
- const destroyTask = state.getOrStartDestroy(entryNodeRid, async (actorRef) => {
447
+ const destroyTask = state.getOrStartDestroy(entryNodeRid, (actorRef) => this.terminateActor(state, async () => {
433
448
  const destroyedActorRef = actorRef === undefined ? undefined : (0, actor_runtime_state_1.toFrameworkActorRef)(actorRef, state.meshName ?? '');
434
449
  if (actorRef !== undefined) {
435
450
  await node.destroyActor(actorRef, 0, destroySignal);
@@ -439,9 +454,9 @@ class DefaultZLinkActorManager {
439
454
  await this.options.locationLifecycle?.releaseActor(state.actorType, actor.context.actorId, destroyedActorRef);
440
455
  }
441
456
  await this.finalizeDestroyedActor(actor.context.actorId, state);
442
- });
457
+ }));
443
458
  try {
444
- await destroyTask;
459
+ await this.awaitActorDestroy(state, destroyTask);
445
460
  }
446
461
  catch (error) {
447
462
  state.clearFailedDestroy(destroyTask);
@@ -147,12 +147,10 @@ class ZLinkNodeRawMeshBackend {
147
147
  selectObjectPlacement(stableType) {
148
148
  const runtime = this.requireRuntime();
149
149
  const localNodeRid = runtime.topology.localDescriptor().nodeRoutingId;
150
- const status = runtime.topology.objectPlacementStatus(stableType, (candidate) => candidate.nodeRoutingId === localNodeRid ||
150
+ const { status, descriptor } = runtime.topology.selectObjectPlacementWithStatus(stableType, (candidate) => candidate.nodeRoutingId === localNodeRid ||
151
151
  runtime.isPeerRouteReady(candidate.nodeRoutingId));
152
152
  if (status !== 'available')
153
153
  return { kind: status };
154
- const descriptor = runtime.topology.selectObjectPlacement(stableType, (candidate) => candidate.nodeRoutingId === localNodeRid ||
155
- runtime.isPeerRouteReady(candidate.nodeRoutingId));
156
154
  return descriptor === undefined
157
155
  ? { kind: 'unavailable' }
158
156
  : {
@@ -36,20 +36,23 @@ export interface ZLinkSerialWorkPreparation {
36
36
  */
37
37
  export declare class ZLinkSerialExecutionQueue {
38
38
  private readonly executeRecord;
39
+ private readonly sharedOwner?;
39
40
  private readonly application;
40
41
  private readonly lifecycle;
41
42
  private activeLifecycle?;
43
+ private activeRecord?;
44
+ /** The admitted node itself owns ready membership; no second readiness flag. */
45
+ private ownerMembership?;
42
46
  private readonly ownerTimeBudget;
43
47
  private readonly lifecycleBurstLimit;
44
48
  private nextAcceptedSequence;
45
- private draining;
46
- private drainScheduled;
49
+ private readonly consumer?;
47
50
  private lifecycleStreak;
48
51
  private lifecycleDebt;
49
52
  private claimStartedAt?;
50
53
  private closed;
51
54
  private readonly idleWaiters;
52
- constructor(executeRecord: (record: ZLinkSerialWorkRecord<unknown>) => Promise<void>, options?: ZLinkSerialSchedulerOptions);
55
+ constructor(executeRecord: (record: ZLinkSerialWorkRecord<unknown>) => Promise<void>, options?: ZLinkSerialSchedulerOptions, sharedOwner?: ZLinkSerialExecutionQueue | undefined);
53
56
  submit<T>(operation: () => Promise<T> | T, options?: ZLinkSerialWorkOptions, context?: unknown): Promise<T>;
54
57
  submitDetached<T>(operation: () => Promise<T> | T, onError: (error: unknown) => void, options?: ZLinkSerialWorkOptions, context?: unknown): void;
55
58
  /**
@@ -73,7 +76,13 @@ export declare class ZLinkSerialExecutionQueue {
73
76
  get admissionClosed(): boolean;
74
77
  private admit;
75
78
  private scheduleDrain;
79
+ /** Notifications carry no payload, admission sequence, result Promise, or host permit. */
80
+ private enqueueMailboxTurn;
81
+ /** Called only by the shared scheduler's consumer, never by a producer. */
82
+ private consumeMailbox;
83
+ private finishMailbox;
76
84
  private drain;
85
+ private prepareSelected;
77
86
  private selectNext;
78
87
  private takeSelected;
79
88
  private takeLifecycle;
@@ -13,21 +13,25 @@ exports.ZLINK_DEFAULT_SERIAL_SCHEDULER_OPTIONS = Object.freeze({
13
13
  */
14
14
  class ZLinkSerialExecutionQueue {
15
15
  executeRecord;
16
+ sharedOwner;
16
17
  application;
17
18
  lifecycle;
18
19
  activeLifecycle;
20
+ activeRecord;
21
+ /** The admitted node itself owns ready membership; no second readiness flag. */
22
+ ownerMembership;
19
23
  ownerTimeBudget;
20
24
  lifecycleBurstLimit;
21
25
  nextAcceptedSequence = 1n;
22
- draining = false;
23
- drainScheduled = false;
26
+ consumer;
24
27
  lifecycleStreak = 0;
25
28
  lifecycleDebt = false;
26
29
  claimStartedAt;
27
30
  closed = false;
28
31
  idleWaiters = [];
29
- constructor(executeRecord, options = {}) {
32
+ constructor(executeRecord, options = {}, sharedOwner) {
30
33
  this.executeRecord = executeRecord;
34
+ this.sharedOwner = sharedOwner;
31
35
  const configured = { ...exports.ZLINK_DEFAULT_SERIAL_SCHEDULER_OPTIONS, ...options };
32
36
  validateNonNegative(configured.ownerTimeBudget, 'ownerTimeBudget');
33
37
  validatePositive(configured.lifecycleBurstLimit, 'lifecycleBurstLimit');
@@ -35,6 +39,8 @@ class ZLinkSerialExecutionQueue {
35
39
  this.lifecycle = createLane();
36
40
  this.ownerTimeBudget = configured.ownerTimeBudget;
37
41
  this.lifecycleBurstLimit = configured.lifecycleBurstLimit;
42
+ if (sharedOwner === undefined)
43
+ this.consumer = { running: false, scheduled: false };
38
44
  }
39
45
  submit(operation, options = {}, context) {
40
46
  try {
@@ -79,11 +85,14 @@ class ZLinkSerialExecutionQueue {
79
85
  return this.admit(operation, { lane: 'lifecycle' }, context, undefined, true);
80
86
  }
81
87
  visitPendingApplication(visitor) {
82
- for (const record of this.application.records)
83
- visitor(record);
88
+ for (const record of this.application.records) {
89
+ if (record.mailbox === undefined)
90
+ visitor(record);
91
+ }
84
92
  }
85
93
  get hasPendingWork() {
86
- return (this.draining ||
94
+ return (this.consumer?.running === true ||
95
+ this.activeRecord !== undefined ||
87
96
  this.activeLifecycle !== undefined ||
88
97
  this.application.records.length > 0 ||
89
98
  this.lifecycle.records.length > 0);
@@ -127,7 +136,7 @@ class ZLinkSerialExecutionQueue {
127
136
  if (this.application.records.length > 0 || this.lifecycle.records.length > 0) {
128
137
  this.scheduleDrain();
129
138
  }
130
- else if (!this.draining) {
139
+ else if (this.consumer?.running !== true) {
131
140
  this.resolveIdleWaiters();
132
141
  }
133
142
  }
@@ -176,18 +185,90 @@ class ZLinkSerialExecutionQueue {
176
185
  return result;
177
186
  }
178
187
  scheduleDrain() {
179
- if (this.drainScheduled || this.draining)
188
+ if (this.sharedOwner !== undefined) {
189
+ if (this.activeRecord !== undefined)
190
+ return;
191
+ // Ready membership in the existing owner queue is the only notification state.
192
+ if (this.ownerMembership !== undefined)
193
+ return;
194
+ this.enqueueMailboxTurn(() => this.consumeMailbox());
195
+ return;
196
+ }
197
+ const consumer = this.consumer;
198
+ if (consumer.scheduled || consumer.running)
180
199
  return;
181
- this.drainScheduled = true;
200
+ consumer.scheduled = true;
182
201
  queueMicrotask(() => {
183
- this.drainScheduled = false;
202
+ consumer.scheduled = false;
184
203
  void this.drain();
185
204
  });
186
205
  }
206
+ /** Notifications carry no payload, admission sequence, result Promise, or host permit. */
207
+ enqueueMailboxTurn(operation) {
208
+ const entry = {
209
+ acceptedSequence: 0n,
210
+ lane: 'application',
211
+ mailbox: this,
212
+ operation,
213
+ operationEnvelope: false,
214
+ preparationState: 'ready',
215
+ resolve: unexpectedMailboxSettlement,
216
+ reject: unexpectedMailboxSettlement,
217
+ release: unexpectedMailboxSettlement,
218
+ fail: unexpectedMailboxSettlement
219
+ };
220
+ const owner = this.sharedOwner;
221
+ owner.application.records.push(entry);
222
+ this.ownerMembership = entry;
223
+ owner.scheduleDrain();
224
+ }
225
+ /** Called only by the shared scheduler's consumer, never by a producer. */
226
+ consumeMailbox() {
227
+ const selection = this.selectNext();
228
+ if (selection === undefined) {
229
+ if (!this.hasPendingWork)
230
+ this.resolveIdleWaiters();
231
+ return;
232
+ }
233
+ const record = selection.record;
234
+ if (!this.prepareSelected(selection))
235
+ return;
236
+ this.takeSelected(selection);
237
+ if (record.preparationState === 'failed') {
238
+ record.preparation?.cancel();
239
+ record.fail(record.preparationError);
240
+ this.scheduleDrain();
241
+ return;
242
+ }
243
+ this.activeRecord = record;
244
+ // The operation can submit Spot turns, including Yield continuations. Its
245
+ // Actor claim remains held until terminal settlement returns to this gate.
246
+ let terminal;
247
+ try {
248
+ terminal = Promise.resolve(record.operation());
249
+ }
250
+ catch (error) {
251
+ terminal = Promise.reject(error);
252
+ }
253
+ void terminal.then((value) => this.finishMailbox(record, () => record.resolve(value)), (error) => this.finishMailbox(record, () => record.reject(error)));
254
+ }
255
+ finishMailbox(record, settle) {
256
+ this.enqueueMailboxTurn(() => {
257
+ settle();
258
+ record.release();
259
+ record.preparation?.cancel();
260
+ this.activeRecord = undefined;
261
+ if (this.selectNext() !== undefined)
262
+ this.scheduleDrain();
263
+ else if (!this.hasPendingWork)
264
+ this.resolveIdleWaiters();
265
+ });
266
+ }
187
267
  async drain() {
188
- if (this.draining)
268
+ const consumer = this.consumer;
269
+ if (consumer.running)
189
270
  return;
190
- this.draining = true;
271
+ consumer.running = true;
191
272
  let waitingForPreparation = false;
192
273
  try {
193
274
  for (;;) {
@@ -195,20 +276,7 @@ class ZLinkSerialExecutionQueue {
195
276
  if (selection === undefined)
196
277
  break;
197
278
  const record = selection.record;
198
- if (selection.lane === 'lifecycle') {
199
- const applicationHead = this.application.records[0];
200
- if (this.application.records.length > 0 &&
201
- !this.cancelPreparationForRearbitration(applicationHead)) {
202
- waitingForPreparation = true;
203
- break;
204
- }
205
- }
206
- if (record.preparationState === 'idle') {
207
- this.startPreparation(record);
208
- waitingForPreparation = true;
209
- break;
210
- }
211
- if (record.preparationState === 'pending' || record.preparationState === 'canceling') {
279
+ if (!this.prepareSelected(selection)) {
212
280
  waitingForPreparation = true;
213
281
  break;
214
282
  }
@@ -220,7 +288,10 @@ class ZLinkSerialExecutionQueue {
220
288
  }
221
289
  this.claimStartedAt ??= performance.now();
222
290
  try {
223
- if (record.operationEnvelope) {
291
+ if (record.mailbox !== undefined) {
292
+ record.operation();
293
+ }
294
+ else if (record.operationEnvelope) {
224
295
  try {
225
296
  const outcome = record.operation();
226
297
  void Promise.resolve(outcome).then((value) => {
@@ -240,6 +311,8 @@ class ZLinkSerialExecutionQueue {
240
311
  }
241
312
  }
242
313
  catch (error) {
314
+ if (record.mailbox !== undefined)
315
+ throw error;
243
316
  record.fail(error);
244
317
  }
245
318
  finally {
@@ -252,7 +325,7 @@ class ZLinkSerialExecutionQueue {
252
325
  }
253
326
  }
254
327
  finally {
255
- this.draining = false;
328
+ consumer.running = false;
256
329
  this.claimStartedAt = undefined;
257
330
  if (!waitingForPreparation && this.selectNext() !== undefined) {
258
331
  this.scheduleDrain();
@@ -264,6 +337,16 @@ class ZLinkSerialExecutionQueue {
264
337
  }
265
338
  }
266
339
  }
340
+ prepareSelected(selection) {
341
+ if (selection.lane === 'lifecycle' &&
342
+ this.application.records.length > 0 &&
343
+ !this.cancelPreparationForRearbitration(this.application.records[0]))
344
+ return false;
345
+ const record = selection.record;
346
+ if (record.preparationState === 'idle')
347
+ this.startPreparation(record);
348
+ return record.preparationState !== 'pending' && record.preparationState !== 'canceling';
349
+ }
267
350
  selectNext() {
268
351
  const lifecycleOwner = this.activeLifecycle?.context;
269
352
  const applicationRecord = lifecycleOwner === undefined
@@ -291,6 +374,8 @@ class ZLinkSerialExecutionQueue {
291
374
  if (record !== selection.record) {
292
375
  throw new Error('The selected serial work record changed before owner claim.');
293
376
  }
377
+ if (selection.record.mailbox !== undefined)
378
+ selection.record.mailbox.ownerMembership = undefined;
294
379
  return record;
295
380
  }
296
381
  takeLifecycle() {
@@ -402,6 +487,9 @@ function createLane() {
402
487
  records: []
403
488
  };
404
489
  }
490
+ function unexpectedMailboxSettlement() {
491
+ throw new Error('A mailbox notification has no independent terminal result.');
492
+ }
405
493
  function validatePositive(value, field) {
406
494
  if (!Number.isSafeInteger(value) || value < 1) {
407
495
  throw new RangeError(`${field} must be a positive safe integer.`);
@@ -14,7 +14,7 @@ export interface PendingOperation<T> {
14
14
  readonly promise: Promise<T>;
15
15
  }
16
16
  /** Owns request completion so reply, timeout, cancellation, and shutdown race safely. */
17
- export declare class OperationRegistry<T> {
17
+ export declare class OperationRegistry<T, Context = unknown> {
18
18
  private readonly clock;
19
19
  private readonly onPendingChanged?;
20
20
  private readonly entries;
@@ -22,11 +22,12 @@ export declare class OperationRegistry<T> {
22
22
  private deadlineCursor?;
23
23
  private closed;
24
24
  constructor(clock?: OperationClock, onPendingChanged?: (() => void) | undefined);
25
- register(timeoutMs: number, timeoutOwner?: 'registry' | 'sender'): PendingOperation<T>;
25
+ register(timeoutMs: number, timeoutOwner?: 'registry' | 'sender', context?: Context): PendingOperation<T>;
26
26
  complete(id: bigint, value: T): boolean;
27
27
  fail(id: bigint, reason: unknown): boolean;
28
28
  cancel(id: bigint, message?: string): boolean;
29
29
  isPending(id: bigint): boolean;
30
+ context(id: bigint): Context | undefined;
30
31
  close(reason?: string): void;
31
32
  get size(): number;
32
33
  expire(nowMs: number, turnDeadlineMs: number): number;
@@ -34,7 +34,7 @@ class OperationRegistry {
34
34
  this.clock = clock;
35
35
  this.onPendingChanged = onPendingChanged;
36
36
  }
37
- register(timeoutMs, timeoutOwner = 'registry') {
37
+ register(timeoutMs, timeoutOwner = 'registry', context) {
38
38
  if (this.closed)
39
39
  throw new Error('Operation registry is closed.');
40
40
  if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
@@ -48,6 +48,7 @@ class OperationRegistry {
48
48
  reject = onReject;
49
49
  });
50
50
  const entry = {
51
+ context,
51
52
  deadlineMs: timeoutOwner === 'registry' ? this.clock.now() + timeoutMs : undefined,
52
53
  resolve,
53
54
  reject
@@ -77,6 +78,9 @@ class OperationRegistry {
77
78
  isPending(id) {
78
79
  return this.entries.has(id);
79
80
  }
81
+ context(id) {
82
+ return this.entries.get(id)?.context;
83
+ }
80
84
  close(reason = 'Operation registry closed.') {
81
85
  if (this.closed)
82
86
  return;
@@ -42,8 +42,6 @@ export interface ServiceUserSpotAuthorityPayload {
42
42
  readonly ownerNodeRid: string;
43
43
  readonly ownerNodeGeneration: bigint;
44
44
  }
45
- /** Replaces only authority-relocation-state and preserves the canonical owner payload. */
46
- export declare function replaceServiceAuthorityRelocationState(payload: Uint8Array, relocationState: Uint8Array): Buffer;
47
45
  export declare function encodeServiceUserSpotAuthorityPayload(value: ServiceUserSpotAuthorityPayload): Buffer;
48
46
  export declare function encodeServiceInstanceAuthorityPayload(value: ServiceInstanceAuthorityPayload): Buffer;
49
47
  export declare function decodeServiceReadySpotAuthority(payload: Uint8Array): ServiceReadySpotAuthority | undefined;
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.replaceServiceAuthorityRelocationState = replaceServiceAuthorityRelocationState;
4
3
  exports.encodeServiceUserSpotAuthorityPayload = encodeServiceUserSpotAuthorityPayload;
5
4
  exports.encodeServiceInstanceAuthorityPayload = encodeServiceInstanceAuthorityPayload;
6
5
  exports.decodeServiceReadySpotAuthority = decodeServiceReadySpotAuthority;
@@ -21,48 +20,6 @@ const AUTHORITY_MAGIC = Buffer.from([0x5a, 0x4c, 0x41, 0x55]);
21
20
  const AUTHORITY_FORMAT_VERSION = 1;
22
21
  const AUTHORITY_FLAGS = 0;
23
22
  const FATAL_UTF8 = new TextDecoder('utf-8', { fatal: true });
24
- /** Replaces only authority-relocation-state and preserves the canonical owner payload. */
25
- function replaceServiceAuthorityRelocationState(payload, relocationState) {
26
- const bytes = Buffer.from(payload);
27
- const reader = new AuthorityReader(bytes);
28
- reader.expect(AUTHORITY_MAGIC);
29
- if (reader.u8() !== AUTHORITY_FORMAT_VERSION || reader.u16() !== AUTHORITY_FLAGS) {
30
- throw new TypeError('Authority payload header is invalid.');
31
- }
32
- const bodyLength = reader.u32();
33
- const body = reader.takeReader(bodyLength);
34
- const checksumOffset = reader.offset;
35
- const checksum = reader.u32();
36
- if (!reader.done || (0, service_relocation_runtime_1.crc32c)(bytes.subarray(0, checksumOffset)) !== checksum) {
37
- throw new TypeError('Authority payload checksum is invalid.');
38
- }
39
- body.u8();
40
- body.u8();
41
- body.skip(body.u16());
42
- body.text8();
43
- body.nonZeroU64();
44
- body.text8();
45
- body.rid();
46
- body.nonZeroU64();
47
- const relocationOffset = body.offset;
48
- body.bool8();
49
- body.skip(body.u32());
50
- const relocationEnd = body.offset;
51
- const currentBody = bytes.subarray(11, 11 + bodyLength);
52
- const nextBody = Buffer.concat([
53
- currentBody.subarray(0, relocationOffset),
54
- Buffer.from(relocationState),
55
- currentBody.subarray(relocationEnd)
56
- ]);
57
- const envelope = Buffer.concat([
58
- AUTHORITY_MAGIC,
59
- Buffer.of(AUTHORITY_FORMAT_VERSION),
60
- u16(AUTHORITY_FLAGS),
61
- u32(nextBody.byteLength),
62
- nextBody
63
- ]);
64
- return Buffer.concat([envelope, u32((0, service_relocation_runtime_1.crc32c)(envelope))]);
65
- }
66
23
  function encodeServiceUserSpotAuthorityPayload(value) {
67
24
  const spot = conditional(2, concat(rid(value.spotId, 'spotId'), text8(value.stableType, 'stableType'), Buffer.of(1)));
68
25
  const object = conditional(2, spot);