@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
@@ -52,6 +52,7 @@ export interface ZLinkSerialWorkPreparation {
52
52
  type SerialWorkRecord<T> = Omit<ZLinkSerialWorkRecord<T>, 'acceptedSequence'> & {
53
53
  acceptedSequence: bigint;
54
54
  readonly operationEnvelope: boolean;
55
+ readonly mailbox?: ZLinkSerialExecutionQueue;
55
56
  readonly preparation?: ZLinkSerialWorkPreparation;
56
57
  preparationState: 'idle' | 'pending' | 'canceling' | 'ready' | 'failed';
57
58
  preparationController?: AbortController;
@@ -70,11 +71,13 @@ export class ZLinkSerialExecutionQueue {
70
71
  private readonly application: ZLinkSerialAdmissionLane;
71
72
  private readonly lifecycle: ZLinkSerialAdmissionLane;
72
73
  private activeLifecycle?: ZLinkSerialWorkRecord<unknown>;
74
+ private activeRecord?: ZLinkSerialWorkRecord<unknown>;
75
+ /** The admitted node itself owns ready membership; no second readiness flag. */
76
+ private ownerMembership?: SerialWorkRecord<unknown>;
73
77
  private readonly ownerTimeBudget: number;
74
78
  private readonly lifecycleBurstLimit: number;
75
79
  private nextAcceptedSequence = 1n;
76
- private draining = false;
77
- private drainScheduled = false;
80
+ private readonly consumer?: { running: boolean; scheduled: boolean };
78
81
  private lifecycleStreak = 0;
79
82
  private lifecycleDebt = false;
80
83
  private claimStartedAt?: number;
@@ -83,7 +86,8 @@ export class ZLinkSerialExecutionQueue {
83
86
 
84
87
  constructor(
85
88
  private readonly executeRecord: (record: ZLinkSerialWorkRecord<unknown>) => Promise<void>,
86
- options: ZLinkSerialSchedulerOptions = {}
89
+ options: ZLinkSerialSchedulerOptions = {},
90
+ private readonly sharedOwner?: ZLinkSerialExecutionQueue
87
91
  ) {
88
92
  const configured = { ...ZLINK_DEFAULT_SERIAL_SCHEDULER_OPTIONS, ...options };
89
93
  validateNonNegative(configured.ownerTimeBudget, 'ownerTimeBudget');
@@ -92,6 +96,7 @@ export class ZLinkSerialExecutionQueue {
92
96
  this.lifecycle = createLane();
93
97
  this.ownerTimeBudget = configured.ownerTimeBudget;
94
98
  this.lifecycleBurstLimit = configured.lifecycleBurstLimit;
99
+ if (sharedOwner === undefined) this.consumer = { running: false, scheduled: false };
95
100
  }
96
101
 
97
102
  submit<T>(
@@ -162,12 +167,15 @@ export class ZLinkSerialExecutionQueue {
162
167
  }
163
168
 
164
169
  visitPendingApplication(visitor: (record: ZLinkSerialWorkRecord<unknown>) => void): void {
165
- for (const record of this.application.records) visitor(record);
170
+ for (const record of this.application.records) {
171
+ if (record.mailbox === undefined) visitor(record);
172
+ }
166
173
  }
167
174
 
168
175
  get hasPendingWork(): boolean {
169
176
  return (
170
- this.draining ||
177
+ this.consumer?.running === true ||
178
+ this.activeRecord !== undefined ||
171
179
  this.activeLifecycle !== undefined ||
172
180
  this.application.records.length > 0 ||
173
181
  this.lifecycle.records.length > 0
@@ -219,7 +227,7 @@ export class ZLinkSerialExecutionQueue {
219
227
  this.activeLifecycle = undefined;
220
228
  if (this.application.records.length > 0 || this.lifecycle.records.length > 0) {
221
229
  this.scheduleDrain();
222
- } else if (!this.draining) {
230
+ } else if (this.consumer?.running !== true) {
223
231
  this.resolveIdleWaiters();
224
232
  }
225
233
  }
@@ -266,39 +274,95 @@ export class ZLinkSerialExecutionQueue {
266
274
  }
267
275
 
268
276
  private scheduleDrain(): void {
269
- if (this.drainScheduled || this.draining) return;
270
- this.drainScheduled = true;
277
+ if (this.sharedOwner !== undefined) {
278
+ if (this.activeRecord !== undefined) return;
279
+ // Ready membership in the existing owner queue is the only notification state.
280
+ if (this.ownerMembership !== undefined) return;
281
+ this.enqueueMailboxTurn(() => this.consumeMailbox());
282
+ return;
283
+ }
284
+ const consumer = this.consumer!;
285
+ if (consumer.scheduled || consumer.running) return;
286
+ consumer.scheduled = true;
271
287
  queueMicrotask(() => {
272
- this.drainScheduled = false;
288
+ consumer.scheduled = false;
273
289
  void this.drain();
274
290
  });
275
291
  }
276
292
 
293
+ /** Notifications carry no payload, admission sequence, result Promise, or host permit. */
294
+ private enqueueMailboxTurn(operation: () => void): void {
295
+ const entry: SerialWorkRecord<unknown> = {
296
+ acceptedSequence: 0n,
297
+ lane: 'application',
298
+ mailbox: this,
299
+ operation,
300
+ operationEnvelope: false,
301
+ preparationState: 'ready',
302
+ resolve: unexpectedMailboxSettlement,
303
+ reject: unexpectedMailboxSettlement,
304
+ release: unexpectedMailboxSettlement,
305
+ fail: unexpectedMailboxSettlement
306
+ };
307
+ const owner = this.sharedOwner!;
308
+ owner.application.records.push(entry);
309
+ this.ownerMembership = entry;
310
+ owner.scheduleDrain();
311
+ }
312
+
313
+ /** Called only by the shared scheduler's consumer, never by a producer. */
314
+ private consumeMailbox(): void {
315
+ const selection = this.selectNext();
316
+ if (selection === undefined) {
317
+ if (!this.hasPendingWork) this.resolveIdleWaiters();
318
+ return;
319
+ }
320
+ const record = selection.record;
321
+ if (!this.prepareSelected(selection)) return;
322
+ this.takeSelected(selection);
323
+ if (record.preparationState === 'failed') {
324
+ record.preparation?.cancel();
325
+ record.fail(record.preparationError);
326
+ this.scheduleDrain();
327
+ return;
328
+ }
329
+ this.activeRecord = record;
330
+ // The operation can submit Spot turns, including Yield continuations. Its
331
+ // Actor claim remains held until terminal settlement returns to this gate.
332
+ let terminal: Promise<unknown>;
333
+ try {
334
+ terminal = Promise.resolve(record.operation());
335
+ } catch (error) {
336
+ terminal = Promise.reject(error);
337
+ }
338
+ void terminal.then(
339
+ (value) => this.finishMailbox(record, () => record.resolve(value)),
340
+ (error) => this.finishMailbox(record, () => record.reject(error))
341
+ );
342
+ }
343
+
344
+ private finishMailbox(record: SerialWorkRecord<unknown>, settle: () => void): void {
345
+ this.enqueueMailboxTurn(() => {
346
+ settle();
347
+ record.release();
348
+ record.preparation?.cancel();
349
+ this.activeRecord = undefined;
350
+ if (this.selectNext() !== undefined) this.scheduleDrain();
351
+ else if (!this.hasPendingWork) this.resolveIdleWaiters();
352
+ });
353
+ }
354
+
277
355
  private async drain(): Promise<void> {
278
- if (this.draining) return;
279
- this.draining = true;
356
+ const consumer = this.consumer!;
357
+ if (consumer.running) return;
358
+ consumer.running = true;
280
359
  let waitingForPreparation = false;
281
360
  try {
282
361
  for (;;) {
283
362
  const selection = this.selectNext();
284
363
  if (selection === undefined) break;
285
364
  const record = selection.record;
286
- if (selection.lane === 'lifecycle') {
287
- const applicationHead = this.application.records[0]!;
288
- if (
289
- this.application.records.length > 0 &&
290
- !this.cancelPreparationForRearbitration(applicationHead)
291
- ) {
292
- waitingForPreparation = true;
293
- break;
294
- }
295
- }
296
- if (record.preparationState === 'idle') {
297
- this.startPreparation(record);
298
- waitingForPreparation = true;
299
- break;
300
- }
301
- if (record.preparationState === 'pending' || record.preparationState === 'canceling') {
365
+ if (!this.prepareSelected(selection)) {
302
366
  waitingForPreparation = true;
303
367
  break;
304
368
  }
@@ -310,7 +374,9 @@ export class ZLinkSerialExecutionQueue {
310
374
  }
311
375
  this.claimStartedAt ??= performance.now();
312
376
  try {
313
- if (record.operationEnvelope) {
377
+ if (record.mailbox !== undefined) {
378
+ record.operation();
379
+ } else if (record.operationEnvelope) {
314
380
  try {
315
381
  const outcome = record.operation();
316
382
  void Promise.resolve(outcome).then(
@@ -330,6 +396,7 @@ export class ZLinkSerialExecutionQueue {
330
396
  await this.executeRecord(record);
331
397
  }
332
398
  } catch (error) {
399
+ if (record.mailbox !== undefined) throw error;
333
400
  record.fail(error);
334
401
  } finally {
335
402
  record.preparation?.cancel();
@@ -340,7 +407,7 @@ export class ZLinkSerialExecutionQueue {
340
407
  }
341
408
  }
342
409
  } finally {
343
- this.draining = false;
410
+ consumer.running = false;
344
411
  this.claimStartedAt = undefined;
345
412
  if (!waitingForPreparation && this.selectNext() !== undefined) {
346
413
  this.scheduleDrain();
@@ -352,6 +419,21 @@ export class ZLinkSerialExecutionQueue {
352
419
  }
353
420
  }
354
421
 
422
+ private prepareSelected(selection: {
423
+ readonly lane: ZLinkSerialWorkLane;
424
+ readonly record: SerialWorkRecord<unknown>;
425
+ }): boolean {
426
+ if (
427
+ selection.lane === 'lifecycle' &&
428
+ this.application.records.length > 0 &&
429
+ !this.cancelPreparationForRearbitration(this.application.records[0]!)
430
+ )
431
+ return false;
432
+ const record = selection.record;
433
+ if (record.preparationState === 'idle') this.startPreparation(record);
434
+ return record.preparationState !== 'pending' && record.preparationState !== 'canceling';
435
+ }
436
+
355
437
  private selectNext():
356
438
  | {
357
439
  readonly lane: ZLinkSerialWorkLane;
@@ -389,6 +471,8 @@ export class ZLinkSerialExecutionQueue {
389
471
  if (record !== selection.record) {
390
472
  throw new Error('The selected serial work record changed before owner claim.');
391
473
  }
474
+ if (selection.record.mailbox !== undefined)
475
+ selection.record.mailbox.ownerMembership = undefined;
392
476
  return record;
393
477
  }
394
478
 
@@ -565,6 +649,10 @@ function createLane(): ZLinkSerialAdmissionLane {
565
649
  };
566
650
  }
567
651
 
652
+ function unexpectedMailboxSettlement(): never {
653
+ throw new Error('A mailbox notification has no independent terminal result.');
654
+ }
655
+
568
656
  function validatePositive(value: number, field: string): void {
569
657
  if (!Number.isSafeInteger(value) || value < 1) {
570
658
  throw new RangeError(`${field} must be a positive safe integer.`);
@@ -24,7 +24,8 @@ export interface PendingOperation<T> {
24
24
  readonly promise: Promise<T>;
25
25
  }
26
26
 
27
- interface Entry<T> {
27
+ interface Entry<T, Context> {
28
+ readonly context: Context | undefined;
28
29
  readonly deadlineMs?: number;
29
30
  readonly resolve: (value: T | PromiseLike<T>) => void;
30
31
  readonly reject: (reason: unknown) => void;
@@ -35,10 +36,10 @@ const systemClock: OperationClock = {
35
36
  };
36
37
 
37
38
  /** Owns request completion so reply, timeout, cancellation, and shutdown race safely. */
38
- export class OperationRegistry<T> {
39
- private readonly entries = new Map<bigint, Entry<T>>();
39
+ export class OperationRegistry<T, Context = unknown> {
40
+ private readonly entries = new Map<bigint, Entry<T, Context>>();
40
41
  private nextId = 1n;
41
- private deadlineCursor?: MapIterator<[bigint, Entry<T>]>;
42
+ private deadlineCursor?: MapIterator<[bigint, Entry<T, Context>]>;
42
43
  private closed = false;
43
44
 
44
45
  constructor(
@@ -48,20 +49,22 @@ export class OperationRegistry<T> {
48
49
 
49
50
  register(
50
51
  timeoutMs: number,
51
- timeoutOwner: 'registry' | 'sender' = 'registry'
52
+ timeoutOwner: 'registry' | 'sender' = 'registry',
53
+ context?: Context
52
54
  ): PendingOperation<T> {
53
55
  if (this.closed) throw new Error('Operation registry is closed.');
54
56
  if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
55
57
  throw new RangeError('timeoutMs must be a non-negative finite number.');
56
58
  }
57
59
  const id = this.nextId++;
58
- let resolve!: Entry<T>['resolve'];
59
- let reject!: Entry<T>['reject'];
60
+ let resolve!: Entry<T, Context>['resolve'];
61
+ let reject!: Entry<T, Context>['reject'];
60
62
  const promise = new Promise<T>((onResolve, onReject) => {
61
63
  resolve = onResolve;
62
64
  reject = onReject;
63
65
  });
64
- const entry: Entry<T> = {
66
+ const entry: Entry<T, Context> = {
67
+ context,
65
68
  deadlineMs: timeoutOwner === 'registry' ? this.clock.now() + timeoutMs : undefined,
66
69
  resolve,
67
70
  reject
@@ -93,6 +96,10 @@ export class OperationRegistry<T> {
93
96
  return this.entries.has(id);
94
97
  }
95
98
 
99
+ context(id: bigint): Context | undefined {
100
+ return this.entries.get(id)?.context;
101
+ }
102
+
96
103
  close(reason = 'Operation registry closed.'): void {
97
104
  if (this.closed) return;
98
105
  this.closed = true;
@@ -121,7 +128,7 @@ export class OperationRegistry<T> {
121
128
  return expired;
122
129
  }
123
130
 
124
- private take(id: bigint): Entry<T> | undefined {
131
+ private take(id: bigint): Entry<T, Context> | undefined {
125
132
  const entry = this.entries.get(id);
126
133
  if (entry === undefined) return undefined;
127
134
  this.entries.delete(id);
@@ -62,52 +62,6 @@ export interface ServiceUserSpotAuthorityPayload {
62
62
  readonly ownerNodeGeneration: bigint;
63
63
  }
64
64
 
65
- /** Replaces only authority-relocation-state and preserves the canonical owner payload. */
66
- export function replaceServiceAuthorityRelocationState(
67
- payload: Uint8Array,
68
- relocationState: Uint8Array
69
- ): Buffer {
70
- const bytes = Buffer.from(payload);
71
- const reader = new AuthorityReader(bytes);
72
- reader.expect(AUTHORITY_MAGIC);
73
- if (reader.u8() !== AUTHORITY_FORMAT_VERSION || reader.u16() !== AUTHORITY_FLAGS) {
74
- throw new TypeError('Authority payload header is invalid.');
75
- }
76
- const bodyLength = reader.u32();
77
- const body = reader.takeReader(bodyLength);
78
- const checksumOffset = reader.offset;
79
- const checksum = reader.u32();
80
- if (!reader.done || crc32c(bytes.subarray(0, checksumOffset)) !== checksum) {
81
- throw new TypeError('Authority payload checksum is invalid.');
82
- }
83
- body.u8();
84
- body.u8();
85
- body.skip(body.u16());
86
- body.text8();
87
- body.nonZeroU64();
88
- body.text8();
89
- body.rid();
90
- body.nonZeroU64();
91
- const relocationOffset = body.offset;
92
- body.bool8();
93
- body.skip(body.u32());
94
- const relocationEnd = body.offset;
95
- const currentBody = bytes.subarray(11, 11 + bodyLength);
96
- const nextBody = Buffer.concat([
97
- currentBody.subarray(0, relocationOffset),
98
- Buffer.from(relocationState),
99
- currentBody.subarray(relocationEnd)
100
- ]);
101
- const envelope = Buffer.concat([
102
- AUTHORITY_MAGIC,
103
- Buffer.of(AUTHORITY_FORMAT_VERSION),
104
- u16(AUTHORITY_FLAGS),
105
- u32(nextBody.byteLength),
106
- nextBody
107
- ]);
108
- return Buffer.concat([envelope, u32(crc32c(envelope))]);
109
- }
110
-
111
65
  export function encodeServiceUserSpotAuthorityPayload(
112
66
  value: ServiceUserSpotAuthorityPayload
113
67
  ): Buffer {
@@ -621,18 +621,19 @@ function createSpotState(
621
621
  }
622
622
  }
623
623
 
624
- export class ServiceTerminalOperationRegistry<T> {
625
- private readonly operations: OperationRegistry<T>;
624
+ export class ServiceTerminalOperationRegistry<T, Context = unknown> {
625
+ private readonly operations: OperationRegistry<T, Context>;
626
626
 
627
- constructor(operations = new OperationRegistry<T>()) {
627
+ constructor(operations = new OperationRegistry<T, Context>()) {
628
628
  this.operations = operations;
629
629
  }
630
630
 
631
631
  register(
632
632
  timeoutMs: number,
633
- timeoutOwner: 'registry' | 'sender' = 'registry'
633
+ timeoutOwner: 'registry' | 'sender' = 'registry',
634
+ context?: Context
634
635
  ): PendingOperation<T> {
635
- return this.operations.register(timeoutMs, timeoutOwner);
636
+ return this.operations.register(timeoutMs, timeoutOwner, context);
636
637
  }
637
638
 
638
639
  reply(id: bigint, value: T): boolean {
@@ -651,6 +652,10 @@ export class ServiceTerminalOperationRegistry<T> {
651
652
  return this.operations.isPending(id);
652
653
  }
653
654
 
655
+ context(id: bigint): Context | undefined {
656
+ return this.operations.context(id);
657
+ }
658
+
654
659
  get size(): number {
655
660
  return this.operations.size;
656
661
  }