@zlink-systems/framework 0.30.0 → 0.30.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 (245) hide show
  1. package/LICENSE +150 -68
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/contracts/Actors/ZLinkActorClient.d.ts +2 -1
  4. package/dist/contracts/Channels/Calls.d.ts +2 -1
  5. package/dist/contracts/Common/ZLinkMessageMetadata.js +11 -5
  6. package/dist/contracts/Handlers/Attributes.d.ts +5 -13
  7. package/dist/contracts/Handlers/Attributes.js +7 -11
  8. package/dist/contracts/Handlers/JsonContract.d.ts +2 -46
  9. package/dist/contracts/Handlers/JsonContract.js +4 -153
  10. package/dist/contracts/Spots/Contracts.d.ts +2 -1
  11. package/dist/nest-integration.d.ts +1 -1
  12. package/dist/nest-integration.js +2 -1
  13. package/dist/runtime/actors/actor-client.d.ts +1 -1
  14. package/dist/runtime/actors/actor-client.js +14 -12
  15. package/dist/runtime/actors/actor-handoff.d.ts +1 -1
  16. package/dist/runtime/actors/actor-handoff.js +7 -4
  17. package/dist/runtime/actors/actor-join-relocation.d.ts +1 -0
  18. package/dist/runtime/actors/actor-local-native-join.js +9 -12
  19. package/dist/runtime/actors/actor-remote-wire.js +15 -0
  20. package/dist/runtime/actors/actor-routed-join-transport.d.ts +6 -2
  21. package/dist/runtime/actors/actor-routed-json-request.d.ts +2 -0
  22. package/dist/runtime/actors/index.d.ts +2 -1
  23. package/dist/runtime/actors/index.js +18 -6
  24. package/dist/runtime/actors/spot-actor-dispatch.js +2 -2
  25. package/dist/runtime/admission.d.ts +1 -1
  26. package/dist/runtime/admission.js +15 -5
  27. package/dist/runtime/application-jobs/application-ingress-record-owner.d.ts +6 -8
  28. package/dist/runtime/application-jobs/application-ingress-record-owner.js +39 -22
  29. package/dist/runtime/application-jobs/application-job-queue-scope.d.ts +2 -1
  30. package/dist/runtime/application-jobs/application-job-queue-scope.js +37 -22
  31. package/dist/runtime/application-jobs/contracts.d.ts +10 -1
  32. package/dist/runtime/backend/contracts/index.d.ts +18 -9
  33. package/dist/runtime/backend/mesh-completion-table.d.ts +9 -1
  34. package/dist/runtime/backend/mesh-completion-table.js +149 -49
  35. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +3 -0
  36. package/dist/runtime/backend/mesh-dispatch-pump.js +32 -11
  37. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
  38. package/dist/runtime/backend/node/node-backend-adapter-support.js +16 -9
  39. package/dist/runtime/backend/node/node-dealer-channel-client-transport.d.ts +1 -1
  40. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +5 -2
  41. package/dist/runtime/backend/node/node-raw-binding-port.js +42 -22
  42. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +13 -5
  43. package/dist/runtime/backend/node/node-raw-mesh-backend.js +69 -51
  44. package/dist/runtime/backend/node/node-socket-backend-adapter.js +6 -6
  45. package/dist/runtime/backend/raw-binding-port.d.ts +2 -2
  46. package/dist/runtime/backend/runtime-message.d.ts +1 -0
  47. package/dist/runtime/backend/runtime-message.js +5 -0
  48. package/dist/runtime/backend/runtime-values.d.ts +1 -0
  49. package/dist/runtime/backend/runtime-values.js +2 -1
  50. package/dist/runtime/channels/channel-clients.d.ts +1 -0
  51. package/dist/runtime/channels/channel-clients.js +58 -21
  52. package/dist/runtime/channels/channel-dispatch-pipeline.d.ts +3 -2
  53. package/dist/runtime/channels/channel-dispatch-pipeline.js +20 -11
  54. package/dist/runtime/channels/channel-dispatch-services.d.ts +1 -0
  55. package/dist/runtime/channels/channel-dispatch-services.js +45 -0
  56. package/dist/runtime/channels/channel-dispatchers.d.ts +1 -1
  57. package/dist/runtime/channels/channel-dispatchers.js +27 -26
  58. package/dist/runtime/channels/channel-envelope-inspection.d.ts +1 -2
  59. package/dist/runtime/channels/channel-envelope-inspection.js +1 -16
  60. package/dist/runtime/channels/channel-envelope.d.ts +1 -0
  61. package/dist/runtime/channels/channel-envelope.js +83 -8
  62. package/dist/runtime/channels/channel-outbound-operations.d.ts +2 -2
  63. package/dist/runtime/channels/channel-outbound-operations.js +8 -8
  64. package/dist/runtime/channels/channel-runtime-manager.d.ts +6 -6
  65. package/dist/runtime/channels/channel-runtime-manager.js +12 -12
  66. package/dist/runtime/channels/channel-transports.d.ts +18 -15
  67. package/dist/runtime/channels/channel-transports.js +48 -31
  68. package/dist/runtime/channels/local-spot-route-transport.d.ts +1 -1
  69. package/dist/runtime/channels/local-spot-route-transport.js +2 -1
  70. package/dist/runtime/channels/router-socket-spot-route-transport.d.ts +2 -2
  71. package/dist/runtime/channels/router-socket-spot-route-transport.js +4 -4
  72. package/dist/runtime/channels/source-spot-router.d.ts +2 -2
  73. package/dist/runtime/channels/source-spot-router.js +4 -4
  74. package/dist/runtime/channels/spot-node-route-transport.d.ts +2 -2
  75. package/dist/runtime/channels/spot-node-route-transport.js +4 -4
  76. package/dist/runtime/channels/spot-route-bridge-transport.d.ts +2 -2
  77. package/dist/runtime/channels/spot-route-bridge-transport.js +4 -4
  78. package/dist/runtime/channels/spot-route-dispatch-strategy.d.ts +5 -4
  79. package/dist/runtime/channels/spot-route-dispatch-strategy.js +18 -18
  80. package/dist/runtime/execution/serial-execution-queue.d.ts +1 -0
  81. package/dist/runtime/execution/serial-execution-queue.js +1 -0
  82. package/dist/runtime/foundation/actor-join-recovery-codec.js +2 -2
  83. package/dist/runtime/foundation/durable-service-request.d.ts +14 -0
  84. package/dist/runtime/foundation/durable-service-request.js +98 -0
  85. package/dist/runtime/foundation/operation-identity.d.ts +1 -0
  86. package/dist/runtime/foundation/operation-identity.js +5 -1
  87. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +22 -5
  88. package/dist/runtime/foundation/raw-service-mesh-runtime.js +356 -232
  89. package/dist/runtime/foundation/service-relocation-object-owner.d.ts +1 -0
  90. package/dist/runtime/foundation/service-relocation-object-owner.js +1 -0
  91. package/dist/runtime/foundation/service-relocation-runtime.d.ts +2 -0
  92. package/dist/runtime/foundation/service-relocation-runtime.js +13 -1
  93. package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -0
  94. package/dist/runtime/foundation/service-stateful-runtime.d.ts +4 -4
  95. package/dist/runtime/foundation/service-stateful-runtime.js +44 -103
  96. package/dist/runtime/foundation/service-stateful-wire-codec.d.ts +0 -5
  97. package/dist/runtime/foundation/service-topology-registry.d.ts +12 -2
  98. package/dist/runtime/foundation/service-topology-registry.js +61 -27
  99. package/dist/runtime/foundation/service-wire-m6a-codec.d.ts +3 -3
  100. package/dist/runtime/foundation/service-wire-m6a-codec.js +36 -38
  101. package/dist/runtime/handlers/handler-instance-scope.d.ts +8 -0
  102. package/dist/runtime/handlers/handler-instance-scope.js +54 -11
  103. package/dist/runtime/host/actor-runtime-options-factory.js +2 -2
  104. package/dist/runtime/host/application-job-queue.d.ts +19 -7
  105. package/dist/runtime/host/application-job-queue.js +150 -42
  106. package/dist/runtime/host/entry-actor-runtime.d.ts +1 -0
  107. package/dist/runtime/host/entry-actor-runtime.js +3 -0
  108. package/dist/runtime/host/index.d.ts +7 -1
  109. package/dist/runtime/host/index.js +142 -118
  110. package/dist/runtime/host/remote-bound-session-relay.js +2 -18
  111. package/dist/runtime/host/route-mesh-runtime.js +1 -1
  112. package/dist/runtime/host/service-relocation-control.d.ts +1 -0
  113. package/dist/runtime/host/service-relocation-control.js +10 -4
  114. package/dist/runtime/host/service-relocation-host-runtime.d.ts +6 -8
  115. package/dist/runtime/host/service-relocation-host-runtime.js +213 -241
  116. package/dist/runtime/host/spot-address-transport.js +3 -2
  117. package/dist/runtime/host/spot-runtime-options-factory.js +1 -0
  118. package/dist/runtime/locations/actor-location-claims.d.ts +2 -2
  119. package/dist/runtime/locations/actor-location-claims.js +34 -19
  120. package/dist/runtime/locations/lifecycle.d.ts +1 -2
  121. package/dist/runtime/locations/lifecycle.js +2 -5
  122. package/dist/runtime/locations/runtime.d.ts +1 -1
  123. package/dist/runtime/locations/runtime.js +24 -23
  124. package/dist/runtime/messaging/framework-json-v1.d.ts +4 -6
  125. package/dist/runtime/messaging/framework-json-v1.js +38 -294
  126. package/dist/runtime/messaging/packet-name.d.ts +1 -1
  127. package/dist/runtime/messaging/packet-name.js +8 -24
  128. package/dist/runtime/messaging/request-result.d.ts +2 -0
  129. package/dist/runtime/messaging/request-result.js +54 -0
  130. package/dist/runtime/messaging/submission-result.d.ts +1 -0
  131. package/dist/runtime/messaging/submission-result.js +5 -2
  132. package/dist/runtime/protocol/service_wire_codec.generated.d.ts +15 -0
  133. package/dist/runtime/protocol/service_wire_codec.generated.js +144 -0
  134. package/dist/runtime/protocol/service_wire_pilot_codec.generated.d.ts +9 -0
  135. package/dist/runtime/protocol/service_wire_pilot_codec.generated.js +14 -1
  136. package/dist/runtime/spots/formal-remote-actor-admission-registry.d.ts +7 -2
  137. package/dist/runtime/spots/formal-remote-actor-admission-registry.js +33 -19
  138. package/dist/runtime/spots/index.d.ts +3 -0
  139. package/dist/runtime/spots/index.js +9 -2
  140. package/dist/runtime/spots/spot-activation.d.ts +1 -0
  141. package/dist/runtime/spots/spot-activation.js +17 -8
  142. package/dist/runtime/spots/spot-entry-activation.js +6 -2
  143. package/dist/runtime/spots/spot-handler-registry.d.ts +3 -1
  144. package/dist/runtime/spots/spot-handler-registry.js +18 -8
  145. package/dist/runtime/spots/spot-node-autoconnect.js +4 -3
  146. package/dist/runtime/spots/spot-outbound.d.ts +3 -0
  147. package/dist/runtime/spots/spot-outbound.js +24 -15
  148. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.d.ts +1 -0
  149. package/dist/runtime/spots/spot-routed-spot-packet-dispatch.js +1 -1
  150. package/dist/runtime/spots/spot-runtime-ports.d.ts +1 -0
  151. package/dist/runtime/spots/spot-subscription-dispatch.js +0 -3
  152. package/dist/runtime/spots/spot-timer.js +1 -0
  153. package/dist/runtime/streams/stream-session-runtime.d.ts +1 -1
  154. package/dist/runtime/streams/stream-session-runtime.js +13 -27
  155. package/package.json +2 -2
  156. package/src/contracts/Actors/ZLinkActorClient.ts +2 -1
  157. package/src/contracts/Channels/Calls.ts +2 -1
  158. package/src/contracts/Common/ZLinkMessageMetadata.ts +13 -8
  159. package/src/contracts/Handlers/Attributes.ts +14 -21
  160. package/src/contracts/Handlers/JsonContract.ts +8 -216
  161. package/src/contracts/Spots/Contracts.ts +2 -1
  162. package/src/nest-integration.ts +1 -0
  163. package/src/runtime/actors/actor-client.ts +40 -16
  164. package/src/runtime/actors/actor-handoff.ts +9 -4
  165. package/src/runtime/actors/actor-join-relocation.ts +1 -0
  166. package/src/runtime/actors/actor-local-native-join.ts +17 -26
  167. package/src/runtime/actors/actor-remote-wire.ts +17 -0
  168. package/src/runtime/actors/actor-routed-join-transport.ts +14 -4
  169. package/src/runtime/actors/actor-routed-json-request.ts +10 -2
  170. package/src/runtime/actors/index.ts +19 -5
  171. package/src/runtime/actors/spot-actor-dispatch.ts +4 -2
  172. package/src/runtime/admission.ts +18 -5
  173. package/src/runtime/application-jobs/application-ingress-record-owner.ts +41 -25
  174. package/src/runtime/application-jobs/application-job-queue-scope.ts +51 -22
  175. package/src/runtime/application-jobs/contracts.ts +14 -1
  176. package/src/runtime/backend/contracts/index.ts +53 -14
  177. package/src/runtime/backend/mesh-completion-table.ts +156 -57
  178. package/src/runtime/backend/mesh-dispatch-pump.ts +40 -10
  179. package/src/runtime/backend/node/node-backend-adapter-support.ts +17 -8
  180. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +12 -2
  181. package/src/runtime/backend/node/node-raw-binding-port.ts +57 -30
  182. package/src/runtime/backend/node/node-raw-mesh-backend.ts +128 -65
  183. package/src/runtime/backend/node/node-socket-backend-adapter.ts +21 -6
  184. package/src/runtime/backend/raw-binding-port.ts +7 -2
  185. package/src/runtime/backend/runtime-message.ts +6 -0
  186. package/src/runtime/backend/runtime-values.ts +1 -0
  187. package/src/runtime/channels/channel-clients.ts +90 -41
  188. package/src/runtime/channels/channel-dispatch-pipeline.ts +69 -34
  189. package/src/runtime/channels/channel-dispatch-services.ts +69 -1
  190. package/src/runtime/channels/channel-dispatchers.ts +29 -27
  191. package/src/runtime/channels/channel-envelope-inspection.ts +2 -16
  192. package/src/runtime/channels/channel-envelope.ts +133 -8
  193. package/src/runtime/channels/channel-outbound-operations.ts +22 -8
  194. package/src/runtime/channels/channel-runtime-manager.ts +29 -12
  195. package/src/runtime/channels/channel-transports.ts +128 -60
  196. package/src/runtime/channels/local-spot-route-transport.ts +3 -1
  197. package/src/runtime/channels/router-socket-spot-route-transport.ts +13 -4
  198. package/src/runtime/channels/source-spot-router.ts +8 -4
  199. package/src/runtime/channels/spot-node-route-transport.ts +8 -4
  200. package/src/runtime/channels/spot-route-bridge-transport.ts +6 -4
  201. package/src/runtime/channels/spot-route-dispatch-strategy.ts +47 -18
  202. package/src/runtime/execution/serial-execution-queue.ts +2 -0
  203. package/src/runtime/foundation/actor-join-recovery-codec.ts +2 -2
  204. package/src/runtime/foundation/durable-service-request.ts +137 -0
  205. package/src/runtime/foundation/operation-identity.ts +5 -1
  206. package/src/runtime/foundation/raw-service-mesh-runtime.ts +484 -293
  207. package/src/runtime/foundation/service-relocation-object-owner.ts +2 -0
  208. package/src/runtime/foundation/service-relocation-runtime.ts +18 -1
  209. package/src/runtime/foundation/service-runtime-contracts.ts +7 -1
  210. package/src/runtime/foundation/service-stateful-runtime.ts +67 -125
  211. package/src/runtime/foundation/service-stateful-wire-codec.ts +0 -5
  212. package/src/runtime/foundation/service-topology-registry.ts +91 -36
  213. package/src/runtime/foundation/service-wire-m6a-codec.ts +57 -38
  214. package/src/runtime/handlers/handler-instance-scope.ts +67 -16
  215. package/src/runtime/host/actor-runtime-options-factory.ts +13 -2
  216. package/src/runtime/host/application-job-queue.ts +175 -41
  217. package/src/runtime/host/entry-actor-runtime.ts +4 -0
  218. package/src/runtime/host/index.ts +201 -156
  219. package/src/runtime/host/remote-bound-session-relay.ts +2 -23
  220. package/src/runtime/host/route-mesh-runtime.ts +1 -1
  221. package/src/runtime/host/service-relocation-control.ts +11 -5
  222. package/src/runtime/host/service-relocation-host-runtime.ts +315 -291
  223. package/src/runtime/host/spot-address-transport.ts +4 -2
  224. package/src/runtime/host/spot-runtime-options-factory.ts +2 -0
  225. package/src/runtime/locations/actor-location-claims.ts +52 -30
  226. package/src/runtime/locations/lifecycle.ts +8 -26
  227. package/src/runtime/locations/runtime.ts +28 -24
  228. package/src/runtime/messaging/framework-json-v1.ts +5 -325
  229. package/src/runtime/messaging/packet-name.ts +5 -25
  230. package/src/runtime/messaging/request-result.ts +58 -0
  231. package/src/runtime/messaging/submission-result.ts +5 -1
  232. package/src/runtime/protocol/service_wire_codec.generated.ts +131 -1
  233. package/src/runtime/protocol/service_wire_pilot_codec.generated.ts +4 -4
  234. package/src/runtime/spots/formal-remote-actor-admission-registry.ts +43 -23
  235. package/src/runtime/spots/index.ts +14 -2
  236. package/src/runtime/spots/spot-activation.ts +28 -10
  237. package/src/runtime/spots/spot-entry-activation.ts +9 -2
  238. package/src/runtime/spots/spot-handler-registry.ts +41 -8
  239. package/src/runtime/spots/spot-node-autoconnect.ts +6 -2
  240. package/src/runtime/spots/spot-outbound.ts +36 -16
  241. package/src/runtime/spots/spot-routed-spot-packet-dispatch.ts +2 -1
  242. package/src/runtime/spots/spot-runtime-ports.ts +1 -0
  243. package/src/runtime/spots/spot-subscription-dispatch.ts +0 -3
  244. package/src/runtime/spots/spot-timer.ts +5 -1
  245. package/src/runtime/streams/stream-session-runtime.ts +12 -29
@@ -5,7 +5,7 @@ import type {
5
5
  ReceiveKindData,
6
6
  ReceiveRecord
7
7
  } from '../foundation/service-runtime-contracts';
8
- import { operationIdentityKey } from '../foundation/operation-identity';
8
+ import { operationIdentityKey, validateOperationIdentity } from '../foundation/operation-identity';
9
9
 
10
10
  export interface ZLinkMeshCompletionDiagnostic {
11
11
  readonly kind: 'unknownOrLate';
@@ -21,10 +21,23 @@ export interface ZLinkMeshCompletion {
21
21
  readonly parts: Message[];
22
22
  }
23
23
 
24
- interface PendingCompletion {
25
- readonly resolve: (completion: ZLinkMeshCompletion) => void;
26
- readonly reject: (error: unknown) => void;
27
- readonly removeAbort?: () => void;
24
+ const COMPLETION_SLOT_BLOCK_SIZE = 64;
25
+ const COMPLETION_HASH_BUCKETS_PER_SLOT = 2;
26
+ const COMPLETION_HASH_WORD_SCALE = 0x1_0000;
27
+ const COMPLETION_HASH_MULTIPLIER = 0x9e37_79b1;
28
+ const COMPLETION_ABORT_OPTIONS = Object.freeze({ once: true });
29
+
30
+ class CompletionSlot {
31
+ generation = 0;
32
+ next = -1;
33
+ high?: bigint;
34
+ low?: bigint;
35
+ hashNext = -1;
36
+ resolve?: (completion: ZLinkMeshCompletion) => void;
37
+ reject?: (error: unknown) => void;
38
+ signal?: AbortSignal;
39
+
40
+ constructor(readonly abort: () => void) {}
28
41
  }
29
42
 
30
43
  export type ZLinkMeshCompletionTerminal = Pick<ReceiveRecord, 'operationId' | 'terminalResult'>;
@@ -34,12 +47,97 @@ export type ZLinkMeshCompletionHandler = (
34
47
  ) => void;
35
48
 
36
49
  export class ZLinkMeshCompletionTable {
37
- private readonly pending = new Map<string | symbol, PendingCompletion>();
50
+ private buckets = new Int32Array(0);
51
+ private readonly slots: CompletionSlot[] = [];
52
+ private free = -1;
53
+ private activeCount = 0;
38
54
  private disposed = false;
39
55
 
40
- constructor(
41
- private readonly onDiagnostic?: (diagnostic: ZLinkMeshCompletionDiagnostic) => void
42
- ) {}
56
+ constructor(private readonly onDiagnostic?: (diagnostic: ZLinkMeshCompletionDiagnostic) => void) {
57
+ this.prepareSlots();
58
+ }
59
+
60
+ private prepareSlots(): void {
61
+ const start = this.slots.length;
62
+ for (let index = start; index < start + COMPLETION_SLOT_BLOCK_SIZE; index++) {
63
+ const slot = new CompletionSlot(() => {
64
+ const reject = slot.reject;
65
+ const signal = slot.signal;
66
+ if (reject === undefined) return;
67
+ this.releaseSlot(index);
68
+ reject(signal?.reason ?? new DOMException('The operation was aborted.', 'AbortError'));
69
+ });
70
+ slot.next = this.free;
71
+ this.slots.push(slot);
72
+ this.free = index;
73
+ }
74
+ const minimumBuckets = this.slots.length * COMPLETION_HASH_BUCKETS_PER_SLOT;
75
+ this.buckets = new Int32Array(2 ** Math.ceil(Math.log2(minimumBuckets))).fill(-1);
76
+ for (let index = 0; index < this.slots.length; index++) {
77
+ const slot = this.slots[index]!;
78
+ if (slot.high === undefined) continue;
79
+ this.indexSlot(index);
80
+ }
81
+ }
82
+
83
+ private bucket(high: bigint, low: bigint): number {
84
+ const highWord = Number(high);
85
+ const lowWord = Number(low);
86
+ // Bucket selection may collide; the stored 128-bit identity is compared exactly.
87
+ const hash =
88
+ Math.imul(
89
+ highWord ^ Math.trunc(highWord / COMPLETION_HASH_WORD_SCALE),
90
+ COMPLETION_HASH_MULTIPLIER
91
+ ) ^
92
+ lowWord ^
93
+ Math.trunc(lowWord / COMPLETION_HASH_WORD_SCALE);
94
+ return hash & (this.buckets.length - 1);
95
+ }
96
+
97
+ private indexSlot(index: number): void {
98
+ const slot = this.slots[index]!;
99
+ const bucket = this.bucket(slot.high!, slot.low!);
100
+ slot.hashNext = this.buckets[bucket]!;
101
+ this.buckets[bucket] = index;
102
+ }
103
+
104
+ private findSlot(operationId: MeshOperationId): number {
105
+ validateOperationIdentity(operationId);
106
+ for (
107
+ let index = this.buckets[this.bucket(operationId.high, operationId.low)]!;
108
+ index !== -1;
109
+ index = this.slots[index]!.hashNext
110
+ ) {
111
+ const slot = this.slots[index]!;
112
+ if (slot.high === operationId.high && slot.low === operationId.low) return index;
113
+ }
114
+ return -1;
115
+ }
116
+
117
+ private releaseSlot(index: number): void {
118
+ const slot = this.slots[index]!;
119
+ if (slot.high !== undefined) {
120
+ const bucket = this.bucket(slot.high, slot.low!);
121
+ let previous = -1;
122
+ let current = this.buckets[bucket]!;
123
+ while (current !== index) {
124
+ previous = current;
125
+ current = this.slots[current]!.hashNext;
126
+ }
127
+ if (previous === -1) this.buckets[bucket] = slot.hashNext;
128
+ else this.slots[previous]!.hashNext = slot.hashNext;
129
+ }
130
+ slot.signal?.removeEventListener('abort', slot.abort);
131
+ slot.high = undefined;
132
+ slot.low = undefined;
133
+ slot.hashNext = -1;
134
+ slot.signal = undefined;
135
+ slot.resolve = undefined;
136
+ slot.reject = undefined;
137
+ slot.next = this.free;
138
+ this.free = index;
139
+ this.activeCount--;
140
+ }
43
141
 
44
142
  /**
45
143
  * Reserves the dispatcher slot before transport submission. The backend
@@ -56,37 +154,30 @@ export class ZLinkMeshCompletionTable {
56
154
  );
57
155
  }
58
156
  return new Promise((resolve, reject) => {
59
- let key: string | symbol = Symbol();
60
- const abort = () => {
61
- this.pending.delete(key);
62
- reject(signal?.reason ?? new DOMException('The operation was aborted.', 'AbortError'));
63
- };
64
- if (signal?.aborted === true) {
65
- abort();
66
- return;
67
- }
68
- signal?.addEventListener('abort', abort, { once: true });
69
- const entry: PendingCompletion = {
70
- resolve,
71
- reject,
72
- removeAbort:
73
- signal === undefined ? undefined : () => signal.removeEventListener('abort', abort)
74
- };
75
- this.pending.set(key, entry);
157
+ if (this.free === -1) this.prepareSlots();
158
+ const index = this.free;
159
+ const slot = this.slots[index]!;
160
+ this.free = slot.next;
161
+ const generation = ++slot.generation;
162
+ slot.resolve = resolve;
163
+ slot.reject = reject;
164
+ slot.signal = signal;
165
+ this.activeCount++;
76
166
  try {
167
+ signal?.addEventListener('abort', slot.abort, COMPLETION_ABORT_OPTIONS);
77
168
  const operationId = operation();
78
- // dispose/abort can consume the reserved slot during submission.
79
- if (!this.pending.delete(key)) return;
80
- key = operationIdentityKey(operationId);
81
- if (this.pending.has(key)) {
82
- throw new Error(`Mesh operation '${key}' is already pending.`);
169
+ // A reentrant abort may release and reuse this slot during submission.
170
+ if (slot.generation !== generation || slot.resolve !== resolve) return;
171
+ if (this.findSlot(operationId) !== -1) {
172
+ throw new Error(
173
+ `Mesh operation '${operationIdentityKey(operationId)}' is already pending.`
174
+ );
83
175
  }
84
- this.pending.set(key, entry);
176
+ slot.high = operationId.high;
177
+ slot.low = operationId.low;
178
+ this.indexSlot(index);
85
179
  } catch (error) {
86
- // Remove only this submission's slot; a duplicate identity retains
87
- // the original waiter.
88
- if (this.pending.get(key) === entry) this.pending.delete(key);
89
- entry.removeAbort?.();
180
+ if (slot.generation === generation && slot.resolve === resolve) this.releaseSlot(index);
90
181
  reject(error);
91
182
  }
92
183
  });
@@ -99,9 +190,8 @@ export class ZLinkMeshCompletionTable {
99
190
  complete(record: ReceiveRecord): void;
100
191
  complete(record: ZLinkMeshCompletionTerminal, materialize: () => ReceiveRecord): void;
101
192
  complete(record: ZLinkMeshCompletionTerminal, materialize?: () => ReceiveRecord): void {
102
- const key = operationIdentityKey(record.operationId);
103
- const pending = this.pending.get(key);
104
- if (pending === undefined) {
193
+ const index = this.findSlot(record.operationId);
194
+ if (index === -1) {
105
195
  this.onDiagnostic?.({
106
196
  kind: 'unknownOrLate',
107
197
  operationId: Object.freeze({ ...record.operationId }),
@@ -109,14 +199,16 @@ export class ZLinkMeshCompletionTable {
109
199
  });
110
200
  return;
111
201
  }
112
- this.pending.delete(key);
113
- pending.removeAbort?.();
202
+ const slot = this.slots[index]!;
203
+ const resolve = slot.resolve!;
204
+ const reject = slot.reject!;
205
+ this.releaseSlot(index);
114
206
  try {
115
- pending.resolve(
207
+ resolve(
116
208
  retainCompletion(materialize === undefined ? (record as ReceiveRecord) : materialize())
117
209
  );
118
210
  } catch (error) {
119
- pending.reject(error);
211
+ reject(error);
120
212
  }
121
213
  }
122
214
 
@@ -125,36 +217,43 @@ export class ZLinkMeshCompletionTable {
125
217
  return;
126
218
  }
127
219
  this.disposed = true;
128
- const pending = [...this.pending.values()];
129
- this.pending.clear();
130
- for (const entry of pending) {
131
- entry.removeAbort?.();
132
- }
133
- for (const entry of pending) {
134
- entry.reject(reason);
220
+ for (let index = 0; index < this.slots.length; index++) {
221
+ const reject = this.slots[index]!.reject;
222
+ if (reject === undefined) continue;
223
+ this.releaseSlot(index);
224
+ reject(reason);
135
225
  }
136
226
  }
137
227
 
138
228
  get pendingCount(): number {
139
- return this.pending.size;
229
+ return this.activeCount;
140
230
  }
141
231
  }
142
232
 
143
233
  function retainCompletion(record: ReceiveRecord): ZLinkMeshCompletion {
234
+ const parts = retainCompletionParts(record.parts);
235
+ // The retention pass verifies every part is already a managed runtime message.
236
+ if (parts === record.parts) return record as unknown as ZLinkMeshCompletion;
144
237
  return {
145
238
  terminalResult: record.terminalResult,
146
239
  failureErrno: record.failureErrno,
147
240
  operationKind: record.operationKind,
148
241
  kindData: record.kindData,
149
- parts: record.parts.map(retainCompletionPart)
242
+ parts
150
243
  };
151
244
  }
152
245
 
153
- function retainCompletionPart(part: ZLinkMessage): Message {
154
- // A runtime message already owns a managed Buffer and close() is a no-op.
155
- // Binding-native messages can expose native storage through data(), which
156
- // becomes invalid when their owner closes; materialize that one boundary copy.
157
- return part instanceof Message ? part : Message.fromOwned(Buffer.from(part.data()));
246
+ function retainCompletionParts(parts: readonly ZLinkMessage[]): Message[] {
247
+ // Reuse managed messages and wrap binding payloads in the runtime representation.
248
+ let retained: Message[] | undefined;
249
+ for (let index = 0; index < parts.length; index++) {
250
+ const part = parts[index]!;
251
+ if (!(part instanceof Message)) {
252
+ retained ??= parts.slice() as Message[];
253
+ retained[index] = Message.fromOwned(Message.retainPayload(part));
254
+ }
255
+ }
256
+ return retained ?? (parts as Message[]);
158
257
  }
159
258
 
160
259
  export function closeMeshCompletion(completion: ZLinkMeshCompletion): void {
@@ -58,6 +58,7 @@ export interface ZLinkMeshDispatchFailureContext {
58
58
  }
59
59
 
60
60
  export class ZLinkMeshDispatchPump {
61
+ private readonly dispatchOwnedRecord = this.dispatchRecord.bind(this);
61
62
  private pendingDomains: number = ReadyDomain.None;
62
63
  private infrastructureScheduled = false;
63
64
  private disposed = false;
@@ -180,6 +181,36 @@ export class ZLinkMeshDispatchPump {
180
181
  return new Promise((resolve) => this.idleApplicationWorkers.add(resolve));
181
182
  }
182
183
 
184
+ private dispatchRecord(owner: ReadyRecord, record: ReceiveRecord): void | Promise<void> {
185
+ let result: void | Promise<void>;
186
+ try {
187
+ result = this.options.dispatch(owner, record);
188
+ } catch (error) {
189
+ this.reportDispatchError(error, meshDispatchFailureContext(record));
190
+ return;
191
+ }
192
+ return result === undefined
193
+ ? this.finishRecord(record)
194
+ : Promise.resolve(result).then(
195
+ () => this.finishRecord(record),
196
+ (error) => this.reportDispatchError(error, meshDispatchFailureContext(record))
197
+ );
198
+ }
199
+
200
+ private finishRecord(record: ReceiveRecord): void | Promise<void> {
201
+ let result: void | Promise<void>;
202
+ try {
203
+ result = record.onTerminalCompletion?.();
204
+ } catch (error) {
205
+ this.reportDispatchError(error, meshDispatchFailureContext(record));
206
+ return;
207
+ }
208
+ if (result !== undefined)
209
+ return Promise.resolve(result).catch((error) =>
210
+ this.reportDispatchError(error, meshDispatchFailureContext(record))
211
+ );
212
+ }
213
+
183
214
  private reportDispatchError(error: unknown, context?: ZLinkMeshDispatchFailureContext): void {
184
215
  this.options.reportError?.(error, context);
185
216
  }
@@ -304,16 +335,9 @@ export class ZLinkMeshDispatchPump {
304
335
  );
305
336
  }
306
337
  this.recordsSinceYield += 1;
307
- const dispatch = async () => {
308
- try {
309
- await this.options.dispatch(owner, record);
310
- await record.onTerminalCompletion?.();
311
- } catch (error) {
312
- this.reportDispatchError(error, meshDispatchFailureContext(record));
313
- }
314
- };
315
338
  if (permit === undefined) {
316
- await dispatch();
339
+ const dispatched = this.dispatchRecord(owner, record);
340
+ if (dispatched !== undefined) await dispatched;
317
341
  } else {
318
342
  if (
319
343
  domain === ReadyDomain.Application &&
@@ -324,7 +348,13 @@ export class ZLinkMeshDispatchPump {
324
348
  // and detached handler handoff. Only unused permits remain
325
349
  // owned by the receive attempt's finally block.
326
350
  if (permit === claimPermit) claimPermit = undefined;
327
- await runWithApplicationJobPermit(permit, dispatch);
351
+ const dispatched = runWithApplicationJobPermit(
352
+ permit,
353
+ this.dispatchOwnedRecord,
354
+ owner,
355
+ record
356
+ );
357
+ if (dispatched !== undefined) await dispatched;
328
358
  }
329
359
  }
330
360
  } finally {
@@ -7,6 +7,7 @@ import {
7
7
  import { loadBinding } from '../node-backend-adapter';
8
8
  import { SubmitResult, ZLinkBackendResultError } from '../runtime-values';
9
9
  const nativeErrnoValues = osConstants.errno;
10
+ const completedSend: Promise<void> = Promise.resolve();
10
11
 
11
12
  export type ZLinkBindingModule = typeof import('@zlink-systems/zlink');
12
13
  export const zlink = loadBinding() as ZLinkBindingModule;
@@ -119,12 +120,16 @@ export function submitBindingReply(operation: ZLinkBindingReplyOperation, payloa
119
120
  }
120
121
  }
121
122
 
122
- export async function submitBindingAsyncSend(
123
+ export function submitBindingAsyncSend(
123
124
  operation: ZLinkBindingAsyncSendOperation,
124
125
  payload: unknown
125
126
  ): Promise<void> {
126
- const submission = submitBindingSend(operation, payload);
127
- if (submission.result === SubmitResult.Backpressured) await submission.admitted;
127
+ try {
128
+ const submission = submitBindingSend(operation, payload);
129
+ return submission.result === SubmitResult.Backpressured ? submission.admitted : completedSend;
130
+ } catch (error) {
131
+ return Promise.reject(error);
132
+ }
128
133
  }
129
134
 
130
135
  export function submitBindingSend(
@@ -152,7 +157,8 @@ export function submitBindingSend(
152
157
  })
153
158
  };
154
159
  }
155
- return { result: SubmitResult.Ok, admitted: submission.admitted };
160
+ // Binding SendSubmission is the closed OK | BACKPRESSURED snapshot.
161
+ return submission as import('../raw-binding-port').ZLinkRawSendSubmission;
156
162
  }
157
163
 
158
164
  export function submitBindingSyncSend(
@@ -177,7 +183,8 @@ export async function submitBindingRequest(
177
183
  operation: ZLinkBindingRequestOperation,
178
184
  payload: unknown,
179
185
  timeoutMs: number | undefined,
180
- recordReceived?: () => void
186
+ recordReceived?: () => void,
187
+ onAdmitted?: () => void
181
188
  ): Promise<readonly unknown[]> {
182
189
  const deadlineMs = timeoutMs === undefined ? undefined : performance.now() + timeoutMs;
183
190
  try {
@@ -195,7 +202,7 @@ export async function submitBindingRequest(
195
202
  if (timeoutMs !== undefined) {
196
203
  current = current.timeout(timeoutMs);
197
204
  }
198
- return await bindingRequestReply(current.submit(), deadlineMs, recordReceived);
205
+ return await bindingRequestReply(current.submit(), deadlineMs, recordReceived, onAdmitted);
199
206
  } catch (error) {
200
207
  throw translateBindingResultError(error);
201
208
  }
@@ -204,13 +211,14 @@ export async function submitBindingRequest(
204
211
  export async function bindingRequestReply(
205
212
  submission: import('@zlink-systems/zlink').RequestSubmission,
206
213
  deadlineMs: number | undefined,
207
- recordReceived?: () => void
214
+ recordReceived?: () => void,
215
+ onAdmitted?: () => void
208
216
  ): Promise<import('@zlink-systems/zlink').Message[]> {
209
217
  const reply = submission.reply;
210
218
  if (submission.result === SubmitResult.Backpressured) {
211
219
  try {
212
220
  const [, replies] = await waitRequestReply(
213
- Promise.all([submission.admitted, reply]),
221
+ Promise.all([submission.admitted.then(() => onAdmitted?.()), reply]),
214
222
  'Request',
215
223
  deadlineMs
216
224
  );
@@ -227,6 +235,7 @@ export async function bindingRequestReply(
227
235
  throw error;
228
236
  }
229
237
  }
238
+ onAdmitted?.();
230
239
  const replies = await reply;
231
240
  recordReceived?.();
232
241
  return replies;
@@ -1,3 +1,4 @@
1
+ import { bindingRequestReply } from './node-backend-adapter-support';
1
2
  import type { DealerSocket, Message, PubSocket } from '@zlink-systems/zlink';
2
3
  import { ZLinkConfigurationException } from '../../configuration';
3
4
  import { ZLinkSubmitStatus, type ZLinkSubmitResult } from '../../messaging/submission-result';
@@ -56,7 +57,8 @@ export class ZLinkDealerChannelClientTransport implements ZLinkChannelClientTran
56
57
  request: Message,
57
58
  timeoutMs: number | undefined,
58
59
  signal?: AbortSignal,
59
- metadata: ReadonlyMap<string, string> = EMPTY_METADATA
60
+ metadata: ReadonlyMap<string, string> = EMPTY_METADATA,
61
+ onAdmitted?: () => void
60
62
  ): Promise<TReply> {
61
63
  throwIfAborted(signal);
62
64
  const operation = runWithOutboundFlow(true, () =>
@@ -78,7 +80,15 @@ export class ZLinkDealerChannelClientTransport implements ZLinkChannelClientTran
78
80
  );
79
81
  if (timeoutMs !== undefined) operation.timeout(timeoutMs);
80
82
  const reply = await submitRequestOperation(
81
- { submit: () => operation.submit().reply },
83
+ {
84
+ submit: () =>
85
+ bindingRequestReply(
86
+ operation.submit(),
87
+ timeoutMs === undefined ? undefined : performance.now() + timeoutMs,
88
+ undefined,
89
+ onAdmitted
90
+ )
91
+ },
82
92
  'channel request'
83
93
  );
84
94
  try {
@@ -27,6 +27,7 @@ import {
27
27
  } from './node-backend-adapter-support';
28
28
  import { isEndpointCloseIgnorableError } from './node-socket-backend-adapter';
29
29
  import { ZLinkNodeEventLoopPoller } from './node-event-loop-poller';
30
+ import { ZLinkBufferMessage } from '../runtime-message';
30
31
  import type {
31
32
  ZLinkRawBindingPort,
32
33
  ZLinkRawDealerPort,
@@ -226,6 +227,42 @@ abstract class NodeRawSocketPort<TSocket extends Socket> implements ZLinkRawSock
226
227
 
227
228
  class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLinkRawRouterPort {
228
229
  private nextIncomingRequestSequence = 1n;
230
+ private readonly routingIds = new Map<string, BindingRoutingId>();
231
+ private readonly nextRequestSequence = () => {
232
+ const current = this.nextIncomingRequestSequence;
233
+ this.nextIncomingRequestSequence =
234
+ current === BigInt(Number.MAX_SAFE_INTEGER) ? 1n : current + 1n;
235
+ return current;
236
+ };
237
+ private readonly replyParts = (
238
+ targetRid: string | Uint8Array,
239
+ replyToken: unknown,
240
+ parts: readonly Uint8Array[]
241
+ ) => {
242
+ appendReplyParts(
243
+ this.socket.reply(
244
+ this.preparedRoutingId(targetRid),
245
+ replyToken as Parameters<RouterSocket['reply']>[1]
246
+ ),
247
+ parts
248
+ ).submit();
249
+ };
250
+
251
+ private preparedRoutingId(routingId: string | Uint8Array): BindingRoutingId {
252
+ if (typeof routingId !== 'string') return bindingRoutingId(routingId);
253
+ let prepared = this.routingIds.get(routingId);
254
+ if (prepared === undefined)
255
+ this.routingIds.set(routingId, (prepared = bindingRoutingId(routingId)));
256
+ return prepared;
257
+ }
258
+
259
+ override close(): void {
260
+ try {
261
+ super.close();
262
+ } finally {
263
+ this.routingIds.clear();
264
+ }
265
+ }
229
266
 
230
267
  setReceiveFlowState(state: 'running' | 'paused'): void {
231
268
  this.requireOpen();
@@ -240,7 +277,7 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
240
277
  this.socket as RouterSocket & {
241
278
  disconnectRid(value: BindingRoutingId): void;
242
279
  }
243
- ).disconnectRid(bindingRoutingId(routingId));
280
+ ).disconnectRid(this.preparedRoutingId(routingId));
244
281
  }
245
282
 
246
283
  constructor(socket: RouterSocket, receiveTimeoutMs?: number) {
@@ -260,46 +297,47 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
260
297
 
261
298
  setRoutingId(routingId: string): void {
262
299
  this.requireOpen();
263
- this.socket.setRoutingId(bindingRoutingId(routingId));
300
+ this.socket.setRoutingId(this.preparedRoutingId(routingId));
264
301
  }
265
302
 
266
303
  connectToRoutingId(routingId: string, endpoint: string): void {
267
304
  this.requireOpen();
268
- this.socket.options.setConnectRoutingId(bindingRoutingId(routingId));
305
+ this.socket.options.setConnectRoutingId(this.preparedRoutingId(routingId));
269
306
  this.connect(endpoint);
270
307
  }
271
308
 
272
309
  send(targetRid: string, parts: readonly Uint8Array[]): Promise<void> {
273
310
  this.requireOpen();
274
311
  requireParts(parts);
275
- return submitBindingAsyncSend(this.socket.send(bindingRoutingId(targetRid)), parts);
312
+ return submitBindingAsyncSend(this.socket.send(this.preparedRoutingId(targetRid)), parts);
276
313
  }
277
314
 
278
315
  submitSend(targetRid: string, parts: readonly Uint8Array[]) {
279
316
  this.requireOpen();
280
317
  requireParts(parts);
281
- return submitBindingSend(this.socket.send(bindingRoutingId(targetRid)), parts);
318
+ return submitBindingSend(this.socket.send(this.preparedRoutingId(targetRid)), parts);
282
319
  }
283
320
 
284
321
  async request(
285
322
  targetRid: string,
286
323
  parts: readonly Uint8Array[],
287
324
  timeoutMs: number,
288
- recordReceived?: () => void
325
+ recordReceived?: () => void,
326
+ onAdmitted?: () => void
289
327
  ): Promise<readonly Buffer[]> {
290
328
  this.requireOpen();
291
329
  const deadlineMs = performance.now() + timeoutMs;
292
330
  let submission: ReturnType<RequestSubmitOperation['submit']>;
293
331
  try {
294
- submission = appendRequestParts(this.socket.request(bindingRoutingId(targetRid)), parts)
332
+ submission = appendRequestParts(this.socket.request(this.preparedRoutingId(targetRid)), parts)
295
333
  .timeout(timeoutMs)
296
334
  .submit();
297
335
  } catch (error) {
298
336
  throw translateBindingResultError(error, 'submit');
299
337
  }
300
338
  try {
301
- const replies = await bindingRequestReply(submission, deadlineMs, recordReceived);
302
- return copyAndClose(replies);
339
+ const replies = await bindingRequestReply(submission, deadlineMs, recordReceived, onAdmitted);
340
+ return retainAndClose(replies);
303
341
  } catch (error) {
304
342
  throw translateBindingResultError(error, 'completion');
305
343
  }
@@ -315,22 +353,7 @@ class NodeRawRouterPort extends NodeRawSocketPort<RouterSocket> implements ZLink
315
353
 
316
354
  receive(dontWait = false): ZLinkRawReceivedRecord | undefined {
317
355
  this.requireOpen();
318
- return receiveRecord(
319
- this.socket,
320
- dontWait,
321
- () => {
322
- const current = this.nextIncomingRequestSequence;
323
- this.nextIncomingRequestSequence =
324
- current === BigInt(Number.MAX_SAFE_INTEGER) ? 1n : current + 1n;
325
- return current;
326
- },
327
- (targetRid, replyToken, parts) => {
328
- appendReplyParts(
329
- this.socket.reply(bindingRoutingId(targetRid), replyToken),
330
- parts
331
- ).submit();
332
- }
333
- );
356
+ return receiveRecord(this.socket, dontWait, this.nextRequestSequence, this.replyParts);
334
357
  }
335
358
  }
336
359
 
@@ -350,7 +373,11 @@ class NodeRawDealerPort extends NodeRawSocketPort<DealerSocket> implements ZLink
350
373
  return submitBindingAsyncSend(this.socket.send(), parts);
351
374
  }
352
375
 
353
- async request(parts: readonly Uint8Array[], timeoutMs: number): Promise<readonly Buffer[]> {
376
+ async request(
377
+ parts: readonly Uint8Array[],
378
+ timeoutMs: number,
379
+ onAdmitted?: () => void
380
+ ): Promise<readonly Buffer[]> {
354
381
  this.requireOpen();
355
382
  const deadlineMs = performance.now() + timeoutMs;
356
383
  let submission: ReturnType<RequestSubmitOperation['submit']>;
@@ -360,8 +387,8 @@ class NodeRawDealerPort extends NodeRawSocketPort<DealerSocket> implements ZLink
360
387
  throw translateBindingResultError(error, 'submit');
361
388
  }
362
389
  try {
363
- const replies = await bindingRequestReply(submission, deadlineMs);
364
- return copyAndClose(replies);
390
+ const replies = await bindingRequestReply(submission, deadlineMs, undefined, onAdmitted);
391
+ return retainAndClose(replies);
365
392
  } catch (error) {
366
393
  throw translateBindingResultError(error, 'completion');
367
394
  }
@@ -493,9 +520,9 @@ function receiveRecord(
493
520
  }
494
521
  }
495
522
 
496
- function copyAndClose(messages: readonly Message[]): readonly Buffer[] {
523
+ function retainAndClose(messages: readonly Message[]): readonly Buffer[] {
497
524
  try {
498
- return messages.map((message) => message.toBytes());
525
+ return messages.map((message) => ZLinkBufferMessage.retainPayload(message));
499
526
  } finally {
500
527
  for (const message of messages) message.close();
501
528
  }