@zlink-systems/framework 0.11.0 → 0.12.0

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 (119) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/contracts/Configuration/EndpointNotation.d.ts +5 -4
  3. package/dist/contracts/Configuration/EndpointNotation.js +5 -4
  4. package/dist/contracts/Configuration/InternalDefaults.d.ts +0 -1
  5. package/dist/contracts/Configuration/InternalDefaults.js +1 -2
  6. package/dist/contracts/Configuration/RegistrationNormalizers.js +1 -2
  7. package/dist/contracts/Configuration/RegistrationTypes.d.ts +2 -5
  8. package/dist/contracts/Configuration/RegistrationValidators.js +0 -1
  9. package/dist/contracts/Errors/ZLinkFrameworkException.d.ts +6 -7
  10. package/dist/contracts/Errors/ZLinkFrameworkException.js +6 -7
  11. package/dist/runtime/actors/actor-client.js +1 -1
  12. package/dist/runtime/actors/actor-mailbox.js +1 -5
  13. package/dist/runtime/backend/contracts/index.d.ts +2 -0
  14. package/dist/runtime/backend/mesh-completion-table.d.ts +1 -3
  15. package/dist/runtime/backend/mesh-completion-table.js +11 -15
  16. package/dist/runtime/backend/mesh-dispatch-pump.d.ts +11 -4
  17. package/dist/runtime/backend/mesh-dispatch-pump.js +201 -134
  18. package/dist/runtime/backend/node/node-backend-adapter-factory.d.ts +3 -4
  19. package/dist/runtime/backend/node/node-backend-adapter-factory.js +51 -37
  20. package/dist/runtime/backend/node/node-backend-adapter-support.d.ts +2 -2
  21. package/dist/runtime/backend/node/node-backend-adapter-support.js +2 -2
  22. package/dist/runtime/backend/node/node-dealer-channel-client-transport.js +2 -2
  23. package/dist/runtime/backend/node/node-event-loop-poller.d.ts +16 -0
  24. package/dist/runtime/backend/node/node-event-loop-poller.js +49 -0
  25. package/dist/runtime/backend/node/node-raw-binding-port.js +24 -41
  26. package/dist/runtime/backend/node/node-raw-mesh-backend.d.ts +8 -8
  27. package/dist/runtime/backend/node/node-raw-mesh-backend.js +100 -107
  28. package/dist/runtime/backend/node/node-socket-backend-adapter.d.ts +3 -2
  29. package/dist/runtime/backend/node/node-socket-backend-adapter.js +18 -67
  30. package/dist/runtime/backend/raw-binding-port.d.ts +1 -0
  31. package/dist/runtime/backend/runtime-values.d.ts +5 -1
  32. package/dist/runtime/channels/channel-dispatchers.js +2 -2
  33. package/dist/runtime/channels/channel-envelope.js +324 -121
  34. package/dist/runtime/channels/channel-multipart.d.ts +8 -1
  35. package/dist/runtime/channels/channel-outbound-operations.js +0 -2
  36. package/dist/runtime/channels/channel-receive-loops.d.ts +1 -3
  37. package/dist/runtime/channels/channel-receive-loops.js +25 -65
  38. package/dist/runtime/channels/channel-socket-registry.js +3 -1
  39. package/dist/runtime/channels/channel-transports.d.ts +2 -1
  40. package/dist/runtime/channels/channel-transports.js +12 -10
  41. package/dist/runtime/execution/serial-execution-queue.d.ts +5 -18
  42. package/dist/runtime/execution/serial-execution-queue.js +16 -80
  43. package/dist/runtime/foundation/event-loop-resources.d.ts +0 -3
  44. package/dist/runtime/foundation/event-loop-resources.js +2 -11
  45. package/dist/runtime/foundation/operation-registry.d.ts +2 -8
  46. package/dist/runtime/foundation/operation-registry.js +3 -21
  47. package/dist/runtime/foundation/raw-service-mesh-runtime.d.ts +19 -7
  48. package/dist/runtime/foundation/raw-service-mesh-runtime.js +56 -10
  49. package/dist/runtime/foundation/service-mailbox.d.ts +1 -7
  50. package/dist/runtime/foundation/service-mailbox.js +3 -2
  51. package/dist/runtime/foundation/service-runtime-contracts.d.ts +2 -1
  52. package/dist/runtime/foundation/service-stateful-registry.d.ts +1 -1
  53. package/dist/runtime/foundation/service-stateful-registry.js +2 -2
  54. package/dist/runtime/foundation/service-stateful-runtime.js +14 -14
  55. package/dist/runtime/framework-errors-internal.d.ts +4 -4
  56. package/dist/runtime/framework-errors-internal.js +8 -10
  57. package/dist/runtime/host/actor-placement-coordinator.js +1 -1
  58. package/dist/runtime/host/index.js +1 -1
  59. package/dist/runtime/host/service-relocation-host-runtime.js +0 -4
  60. package/dist/runtime/host/spot-address-transport.js +1 -1
  61. package/dist/runtime/host/user-spot-creation-coordinator.js +1 -1
  62. package/dist/runtime/spots/spot-node-runtime-manager.d.ts +0 -12
  63. package/dist/runtime/spots/spot-node-runtime-manager.js +6 -166
  64. package/dist/runtime/spots/spot-serial-turn-executor.js +1 -4
  65. package/dist/runtime/spots/spot-timer.d.ts +3 -0
  66. package/dist/runtime/spots/spot-timer.js +87 -18
  67. package/dist/runtime/streams/session-serial-executor.js +1 -3
  68. package/dist/runtime/streams/stream-session-runtime.d.ts +2 -2
  69. package/dist/runtime/streams/stream-session-runtime.js +13 -19
  70. package/dist/runtime/workers/index.d.ts +0 -1
  71. package/dist/runtime/workers/index.js +1 -9
  72. package/package.json +3 -3
  73. package/src/contracts/Configuration/EndpointNotation.ts +5 -4
  74. package/src/contracts/Configuration/InternalDefaults.ts +0 -1
  75. package/src/contracts/Configuration/RegistrationNormalizers.ts +2 -4
  76. package/src/contracts/Configuration/RegistrationTypes.ts +2 -5
  77. package/src/contracts/Configuration/RegistrationValidators.ts +0 -1
  78. package/src/contracts/Errors/ZLinkFrameworkException.ts +6 -7
  79. package/src/runtime/actors/actor-client.ts +1 -1
  80. package/src/runtime/actors/actor-mailbox.ts +1 -12
  81. package/src/runtime/backend/contracts/index.ts +2 -0
  82. package/src/runtime/backend/mesh-completion-table.ts +12 -23
  83. package/src/runtime/backend/mesh-dispatch-pump.ts +206 -138
  84. package/src/runtime/backend/node/node-backend-adapter-factory.ts +66 -35
  85. package/src/runtime/backend/node/node-backend-adapter-support.ts +4 -4
  86. package/src/runtime/backend/node/node-dealer-channel-client-transport.ts +2 -2
  87. package/src/runtime/backend/node/node-event-loop-poller.ts +55 -0
  88. package/src/runtime/backend/node/node-raw-binding-port.ts +30 -45
  89. package/src/runtime/backend/node/node-raw-mesh-backend.ts +97 -110
  90. package/src/runtime/backend/node/node-socket-backend-adapter.ts +23 -69
  91. package/src/runtime/backend/raw-binding-port.ts +1 -0
  92. package/src/runtime/backend/runtime-values.ts +7 -1
  93. package/src/runtime/channels/channel-dispatchers.ts +2 -2
  94. package/src/runtime/channels/channel-envelope.ts +379 -109
  95. package/src/runtime/channels/channel-multipart.ts +9 -1
  96. package/src/runtime/channels/channel-outbound-operations.ts +0 -1
  97. package/src/runtime/channels/channel-receive-loops.ts +24 -75
  98. package/src/runtime/channels/channel-socket-registry.ts +4 -1
  99. package/src/runtime/channels/channel-transports.ts +20 -10
  100. package/src/runtime/execution/serial-execution-queue.ts +16 -108
  101. package/src/runtime/foundation/event-loop-resources.ts +2 -11
  102. package/src/runtime/foundation/operation-registry.ts +2 -22
  103. package/src/runtime/foundation/raw-service-mesh-runtime.ts +74 -19
  104. package/src/runtime/foundation/service-mailbox.ts +3 -13
  105. package/src/runtime/foundation/service-runtime-contracts.ts +2 -1
  106. package/src/runtime/foundation/service-stateful-registry.ts +2 -2
  107. package/src/runtime/foundation/service-stateful-runtime.ts +14 -14
  108. package/src/runtime/framework-errors-internal.ts +8 -10
  109. package/src/runtime/host/actor-placement-coordinator.ts +1 -1
  110. package/src/runtime/host/index.ts +2 -1
  111. package/src/runtime/host/service-relocation-host-runtime.ts +0 -4
  112. package/src/runtime/host/spot-address-transport.ts +1 -1
  113. package/src/runtime/host/user-spot-creation-coordinator.ts +1 -1
  114. package/src/runtime/spots/spot-node-runtime-manager.ts +13 -233
  115. package/src/runtime/spots/spot-serial-turn-executor.ts +1 -8
  116. package/src/runtime/spots/spot-timer.ts +109 -18
  117. package/src/runtime/streams/session-serial-executor.ts +1 -7
  118. package/src/runtime/streams/stream-session-runtime.ts +13 -17
  119. package/src/runtime/workers/index.ts +2 -17
@@ -96,22 +96,21 @@ export function encodeChannelEnvelopeParts(
96
96
  const encoded = encodePayload(payload, codecsForFrameworkPacket(messageName, codecs), messageName, 'payload');
97
97
  const flow = createFlow ? currentOrCreateFlow('Application') : undefined;
98
98
  const envelopeCorrelationId = correlationIdForOutboundKind(kind, correlationId);
99
- const header: ZLinkChannelEnvelopeHeader = {
100
- formatMarker: ZLINK_CHANNEL_FORMAT_MARKER,
99
+ return [encodeChannelHeader(
101
100
  kind,
102
101
  channelName,
103
102
  messageName,
104
- contentType: encoded.contentType,
105
- correlationId: envelopeCorrelationId,
106
- deadline: timeoutMs === undefined ? null : new Date(Date.now() + timeoutMs).toISOString(),
107
- topic: topic ?? null,
108
- errorCode: null,
109
- errorMessage: null,
110
- metadata: applicationMetadataRecord(metadata),
111
- flowId: flow?.flowId,
112
- flowOrigin: flow?.flowOrigin
113
- };
114
- return [encodeChannelHeader(header), encoded.message];
103
+ encoded.contentType,
104
+ envelopeCorrelationId,
105
+ timeoutMs === undefined ? null : new Date(Date.now() + timeoutMs).toISOString(),
106
+ topic ?? null,
107
+ null,
108
+ null,
109
+ undefined,
110
+ applicationMetadataRecord(metadata),
111
+ flow?.flowId,
112
+ flow?.flowOrigin
113
+ ), encoded.message];
115
114
  }
116
115
 
117
116
  /**
@@ -139,22 +138,21 @@ export function encodeChannelEnvelopePartsAtDeadline(
139
138
  const encoded = encodePayload(payload, codecsForFrameworkPacket(messageName, codecs), messageName, 'payload');
140
139
  const flow = createFlow ? currentOrCreateFlow('Application') : undefined;
141
140
  const envelopeCorrelationId = correlationIdForOutboundKind(kind, correlationId);
142
- const header: ZLinkChannelEnvelopeHeader = {
143
- formatMarker: ZLINK_CHANNEL_FORMAT_MARKER,
141
+ return [encodeChannelHeader(
144
142
  kind,
145
143
  channelName,
146
144
  messageName,
147
- contentType: encoded.contentType,
148
- correlationId: envelopeCorrelationId,
149
- deadline: new Date(deadlineUnixMs).toISOString(),
150
- topic: topic ?? null,
151
- errorCode: null,
152
- errorMessage: null,
153
- metadata: applicationMetadataRecord(metadata),
154
- flowId: flow?.flowId,
155
- flowOrigin: flow?.flowOrigin
156
- };
157
- return [encodeChannelHeader(header), encoded.message];
145
+ encoded.contentType,
146
+ envelopeCorrelationId,
147
+ new Date(deadlineUnixMs).toISOString(),
148
+ topic ?? null,
149
+ null,
150
+ null,
151
+ undefined,
152
+ applicationMetadataRecord(metadata),
153
+ flow?.flowId,
154
+ flow?.flowOrigin
155
+ ), encoded.message];
158
156
  }
159
157
 
160
158
  export function encodeChannelPublishEnvelopeParts(
@@ -169,22 +167,21 @@ export function encodeChannelPublishEnvelopeParts(
169
167
  const messageName = resolveFrameworkPacketName(payload, packetName, 'Channel');
170
168
  const encoded = encodePayload(payload, codecsForFrameworkPacket(messageName, codecs), messageName, 'payload');
171
169
  const flow = createFlow ? currentOrCreateFlow('Application') : undefined;
172
- const header: ZLinkChannelEnvelopeHeader = {
173
- formatMarker: ZLINK_CHANNEL_FORMAT_MARKER,
174
- kind: ZLinkChannelMessageKind.Publish,
170
+ return [encodeChannelHeader(
171
+ ZLinkChannelMessageKind.Publish,
175
172
  channelName,
176
173
  messageName,
177
- contentType: encoded.contentType,
178
- correlationId: null,
179
- deadline: null,
174
+ encoded.contentType,
175
+ null,
176
+ null,
180
177
  topic,
181
- errorCode: null,
182
- errorMessage: null,
183
- metadata: applicationMetadataRecord(metadata),
184
- flowId: flow?.flowId,
185
- flowOrigin: flow?.flowOrigin
186
- };
187
- return [encodeChannelHeader(header), encoded.message];
178
+ null,
179
+ null,
180
+ undefined,
181
+ applicationMetadataRecord(metadata),
182
+ flow?.flowId,
183
+ flow?.flowOrigin
184
+ ), encoded.message];
188
185
  }
189
186
 
190
187
  export function encodeChannelReplyParts(
@@ -198,20 +195,21 @@ export function encodeChannelReplyParts(
198
195
  request.messageName,
199
196
  'reply'
200
197
  );
201
- const header: ZLinkChannelEnvelopeHeader = {
202
- formatMarker: ZLINK_CHANNEL_FORMAT_MARKER,
203
- kind: ZLinkChannelMessageKind.Response,
204
- channelName: request.channelName,
205
- messageName: request.messageName,
206
- contentType: encoded.contentType,
207
- correlationId: request.correlationId,
208
- deadline: null,
209
- topic: null,
210
- metadata: {},
211
- flowId: request.flowId,
212
- flowOrigin: request.flowOrigin
213
- };
214
- return [encodeChannelHeader(header), encoded.message];
198
+ return [encodeChannelHeader(
199
+ ZLinkChannelMessageKind.Response,
200
+ request.channelName,
201
+ request.messageName,
202
+ encoded.contentType,
203
+ request.correlationId,
204
+ null,
205
+ null,
206
+ undefined,
207
+ undefined,
208
+ undefined,
209
+ EMPTY_APPLICATION_METADATA,
210
+ request.flowId,
211
+ request.flowOrigin
212
+ ), encoded.message];
215
213
  }
216
214
 
217
215
  /**
@@ -236,13 +234,12 @@ const ERROR_KIND_WIRE_NAMES: readonly string[] = [
236
234
  'not_configured', // 3
237
235
  'rejected', // 4
238
236
  'unavailable', // 5
239
- 'capacity_exceeded', // 6
240
- 'deadline_exceeded', // 7
241
- 'shutting_down', // 8
242
- 'protocol_error', // 9
243
- 'invalid_operation', // 10
244
- 'data_lost', // 11
245
- 'internal_failure' // 12
237
+ 'deadline_exceeded', // 6
238
+ 'shutting_down', // 7
239
+ 'protocol_error', // 8
240
+ 'invalid_operation', // 9
241
+ 'data_lost', // 10
242
+ 'internal_failure' // 11
246
243
  ];
247
244
 
248
245
  const ERROR_KIND_BY_WIRE_NAME: ReadonlyMap<string, ZLinkFrameworkErrorKind> = new Map(
@@ -275,22 +272,21 @@ export function encodeChannelErrorReplyParts(
275
272
  : ZLinkFrameworkErrorKind.InternalFailure
276
273
  );
277
274
  const errorMessage = error instanceof Error ? error.message : String(error);
278
- const header: ZLinkChannelEnvelopeHeader = {
279
- formatMarker: ZLINK_CHANNEL_FORMAT_MARKER,
280
- kind: ZLinkChannelMessageKind.Error,
281
- channelName: request.channelName,
282
- messageName: request.messageName,
283
- contentType: JSON_CONTENT_TYPE,
284
- correlationId: request.correlationId,
285
- deadline: null,
286
- topic: null,
275
+ return [encodeChannelHeader(
276
+ ZLinkChannelMessageKind.Error,
277
+ request.channelName,
278
+ request.messageName,
279
+ JSON_CONTENT_TYPE,
280
+ request.correlationId,
281
+ null,
282
+ null,
287
283
  errorCode,
288
284
  errorMessage,
285
+ undefined,
289
286
  metadata,
290
- flowId: request.flowId,
291
- flowOrigin: request.flowOrigin
292
- };
293
- return [encodeChannelHeader(header), encodeJsonBytes(null)];
287
+ request.flowId,
288
+ request.flowOrigin
289
+ ), encodeJsonBytes(null)];
294
290
  }
295
291
 
296
292
  export function decodeChannelReply<TReply>(
@@ -491,25 +487,162 @@ function encodeJsonBytes(value: unknown, schema?: ZLinkJsonSchema): Buffer {
491
487
  return Buffer.from(stringifyFrameworkJsonV1(value, schema));
492
488
  }
493
489
 
494
- function encodeChannelHeader(header: ZLinkChannelEnvelopeHeader): Buffer {
495
- // Explicit construction: this runs for every outbound envelope, so avoid a
496
- // per-message spread of the whole header.
497
- return encodeJsonBytes({
498
- formatMarker: header.formatMarker,
499
- kind: header.kind,
500
- channelName: header.channelName,
501
- messageName: header.messageName,
502
- contentType: header.contentType,
503
- correlationId: header.correlationId,
504
- deadline: header.deadline,
505
- topic: header.topic,
506
- errorCode: header.errorCode,
507
- errorMessage: header.errorMessage,
508
- source: header.source,
509
- metadata: header.metadata,
510
- flowId: header.flowId,
511
- flowOrigin: header.flowOrigin === undefined ? undefined : encodeFlowOrigin(header.flowOrigin)
512
- });
490
+ function encodeChannelHeader(
491
+ kind: ZLinkChannelMessageKind,
492
+ channelName: string,
493
+ messageName: string,
494
+ contentType: string,
495
+ correlationId: string | null,
496
+ deadline: string | null,
497
+ topic: string | null,
498
+ errorCode: string | null | undefined,
499
+ errorMessage: string | null | undefined,
500
+ source: string | null | undefined,
501
+ metadata: Readonly<Record<string, string>>,
502
+ flowId: string | undefined,
503
+ flowOrigin: ZLinkFlowOrigin | undefined
504
+ ): Buffer {
505
+ // The wire has a fixed field order. Measure escaped UTF-8 first, then write
506
+ // each field once into the only header Buffer: no header object, whole JSON
507
+ // string, or post-encode byte copy exists on the outbound path.
508
+ const measure = new ChannelHeaderWriter();
509
+ writeChannelHeader(
510
+ measure, kind, channelName, messageName, contentType, correlationId,
511
+ deadline, topic, errorCode, errorMessage, source, metadata, flowId, flowOrigin
512
+ );
513
+ const result = Buffer.alloc(measure.byteLength);
514
+ const writer = new ChannelHeaderWriter(result);
515
+ writeChannelHeader(
516
+ writer, kind, channelName, messageName, contentType, correlationId,
517
+ deadline, topic, errorCode, errorMessage, source, metadata, flowId, flowOrigin
518
+ );
519
+ return result;
520
+ }
521
+
522
+ function writeChannelHeader(
523
+ writer: ChannelHeaderWriter,
524
+ kind: ZLinkChannelMessageKind,
525
+ channelName: string,
526
+ messageName: string,
527
+ contentType: string,
528
+ correlationId: string | null,
529
+ deadline: string | null,
530
+ topic: string | null,
531
+ errorCode: string | null | undefined,
532
+ errorMessage: string | null | undefined,
533
+ source: string | null | undefined,
534
+ metadata: Readonly<Record<string, string>>,
535
+ flowId: string | undefined,
536
+ flowOrigin: ZLinkFlowOrigin | undefined
537
+ ): void {
538
+ writer.append('{"formatMarker":');
539
+ writer.append(String(ZLINK_CHANNEL_FORMAT_MARKER));
540
+ writer.append(',"kind":');
541
+ writer.append(String(kind));
542
+ writer.append(',"channelName":');
543
+ writer.string(channelName);
544
+ writer.append(',"messageName":');
545
+ writer.string(messageName);
546
+ writer.append(',"contentType":');
547
+ writer.string(contentType);
548
+ writer.append(',"correlationId":');
549
+ writer.nullableString(correlationId);
550
+ writer.append(',"deadline":');
551
+ writer.nullableString(deadline);
552
+ writer.append(',"topic":');
553
+ writer.nullableString(topic);
554
+ if (errorCode !== undefined) {
555
+ writer.append(',"errorCode":');
556
+ writer.nullableString(errorCode);
557
+ }
558
+ if (errorMessage !== undefined) {
559
+ writer.append(',"errorMessage":');
560
+ writer.nullableString(errorMessage);
561
+ }
562
+ if (source !== undefined) {
563
+ writer.append(',"source":');
564
+ writer.nullableString(source);
565
+ }
566
+ writer.append(',"metadata":{');
567
+ let first = true;
568
+ for (const name in metadata) {
569
+ if (!Object.prototype.hasOwnProperty.call(metadata, name)) continue;
570
+ if (!first) writer.append(',');
571
+ first = false;
572
+ writer.string(name);
573
+ writer.append(':');
574
+ writer.string(metadata[name]!);
575
+ }
576
+ writer.append('}');
577
+ if (flowId !== undefined) {
578
+ writer.append(',"flowId":');
579
+ writer.string(flowId);
580
+ }
581
+ if (flowOrigin !== undefined) {
582
+ writer.append(',"flowOrigin":');
583
+ writer.append(String(encodeFlowOrigin(flowOrigin)));
584
+ }
585
+ writer.append('}');
586
+ }
587
+
588
+ class ChannelHeaderWriter {
589
+ private offset = 0;
590
+
591
+ constructor(private readonly output?: Buffer) {}
592
+
593
+ get byteLength(): number {
594
+ return this.offset;
595
+ }
596
+
597
+ append(value: string): void {
598
+ if (this.output === undefined) {
599
+ this.offset += Buffer.byteLength(value);
600
+ return;
601
+ }
602
+ this.offset += this.output.write(value, this.offset, 'utf8');
603
+ }
604
+
605
+ nullableString(value: string | null): void {
606
+ if (value === null) this.append('null');
607
+ else this.string(value);
608
+ }
609
+
610
+ string(value: string): void {
611
+ this.append('"');
612
+ let start = 0;
613
+ for (let index = 0; index < value.length; index += 1) {
614
+ const code = value.charCodeAt(index);
615
+ let escaped: string | undefined;
616
+ switch (code) {
617
+ case 0x08: escaped = '\\b'; break;
618
+ case 0x09: escaped = '\\t'; break;
619
+ case 0x0a: escaped = '\\n'; break;
620
+ case 0x0c: escaped = '\\f'; break;
621
+ case 0x0d: escaped = '\\r'; break;
622
+ case 0x22: escaped = '\\"'; break;
623
+ case 0x5c: escaped = '\\\\'; break;
624
+ default:
625
+ if (code < 0x20) {
626
+ escaped = `\\u00${code.toString(16).padStart(2, '0')}`;
627
+ } else if (code >= 0xd800 && code <= 0xdbff) {
628
+ const next = value.charCodeAt(index + 1);
629
+ if (next >= 0xdc00 && next <= 0xdfff) {
630
+ index += 1;
631
+ } else {
632
+ escaped = `\\u${code.toString(16)}`;
633
+ }
634
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
635
+ escaped = `\\u${code.toString(16)}`;
636
+ }
637
+ }
638
+ if (escaped === undefined) continue;
639
+ this.append(value.slice(start, index));
640
+ this.append(escaped);
641
+ start = index + 1;
642
+ }
643
+ this.append(value.slice(start));
644
+ this.append('"');
645
+ }
513
646
  }
514
647
 
515
648
  function encodeFlowOrigin(origin: ZLinkFlowOrigin): number {
@@ -528,7 +661,7 @@ export function decodeChannelHeader(
528
661
  if (parts.length === 0) {
529
662
  throw new ZLinkConfigurationException('Channel envelope header part is missing.');
530
663
  }
531
- return validateChannelHeader(parseWireJson(parts[0].data().toString()), flowEnabled);
664
+ return validateChannelHeader(parseChannelHeaderBytes(parts[0].data()), flowEnabled);
532
665
  }
533
666
 
534
667
  function parseWireJson(payload: string, schema?: ZLinkJsonSchema): unknown {
@@ -537,6 +670,146 @@ function parseWireJson(payload: string, schema?: ZLinkJsonSchema): unknown {
537
670
  }, schema);
538
671
  }
539
672
 
673
+ function parseChannelHeaderBytes(payload: Buffer): unknown {
674
+ return tryParseCanonicalChannelHeader(payload) ?? parseWireJson(payload.toString());
675
+ }
676
+
677
+ /**
678
+ * Accept the byte layout emitted by writeChannelHeader without materializing
679
+ * the enclosing JSON text. Any variation falls through to the established
680
+ * framework-json-v1 parser, which remains the sole authority for JSON
681
+ * failures, unknown properties, and prototype-key rejection.
682
+ */
683
+ function tryParseCanonicalChannelHeader(payload: Buffer): Record<string, unknown> | undefined {
684
+ const reader = new CanonicalChannelHeaderReader(payload);
685
+ if (!reader.consume('{"formatMarker":242,"kind":')) return undefined;
686
+ const kind = reader.readSingleDigit();
687
+ if (kind === undefined || !reader.consume(',"channelName":')) return undefined;
688
+ const channelName = reader.readString();
689
+ if (channelName === undefined || !reader.consume(',"messageName":')) return undefined;
690
+ const messageName = reader.readString();
691
+ if (messageName === undefined || !reader.consume(',"contentType":')) return undefined;
692
+ const contentType = reader.readString();
693
+ if (contentType === undefined || !reader.consume(',"correlationId":')) return undefined;
694
+ const correlationId = reader.readNullableString();
695
+ if (correlationId === undefined || !reader.consume(',"deadline":')) return undefined;
696
+ const deadline = reader.readNullableString();
697
+ if (deadline === undefined || !reader.consume(',"topic":')) return undefined;
698
+ const topic = reader.readNullableString();
699
+ if (topic === undefined) return undefined;
700
+
701
+ const header: Record<string, unknown> = {
702
+ formatMarker: ZLINK_CHANNEL_FORMAT_MARKER,
703
+ kind,
704
+ channelName,
705
+ messageName,
706
+ contentType,
707
+ correlationId: correlationId === NULL_JSON_STRING ? null : correlationId,
708
+ deadline: deadline === NULL_JSON_STRING ? null : deadline,
709
+ topic: topic === NULL_JSON_STRING ? null : topic
710
+ };
711
+ if (!reader.readOptionalNullableString('errorCode', header)) return undefined;
712
+ if (!reader.readOptionalNullableString('errorMessage', header)) return undefined;
713
+ if (!reader.readOptionalNullableString('source', header)) return undefined;
714
+ if (!reader.consume(',"metadata":{}')) return undefined;
715
+ header.metadata = {};
716
+ if (!reader.readOptionalString('flowId', header)) return undefined;
717
+ if (!reader.readOptionalSingleDigit('flowOrigin', header)) return undefined;
718
+ return reader.consume('}') && reader.atEnd() ? header : undefined;
719
+ }
720
+
721
+ const NULL_JSON_STRING = Symbol('null JSON string');
722
+
723
+ class CanonicalChannelHeaderReader {
724
+ private index = 0;
725
+
726
+ constructor(private readonly bytes: Buffer) {}
727
+
728
+ atEnd(): boolean {
729
+ return this.index === this.bytes.length;
730
+ }
731
+
732
+ consume(value: string): boolean {
733
+ if (this.index + value.length > this.bytes.length) return false;
734
+ for (let offset = 0; offset < value.length; offset += 1) {
735
+ if (this.bytes[this.index + offset] !== value.charCodeAt(offset)) return false;
736
+ }
737
+ this.index += value.length;
738
+ return true;
739
+ }
740
+
741
+ readSingleDigit(): number | undefined {
742
+ if (this.index === this.bytes.length) return undefined;
743
+ const value = this.bytes[this.index];
744
+ if (value < 0x30 || value > 0x39) return undefined;
745
+ this.index += 1;
746
+ return value - 0x30;
747
+ }
748
+
749
+ readString(): string | undefined {
750
+ if (this.bytes[this.index] !== 0x22) return undefined;
751
+ const start = this.index;
752
+ this.index += 1;
753
+ while (this.index < this.bytes.length) {
754
+ const value = this.bytes[this.index++]!;
755
+ if (value === 0x22) {
756
+ return JSON.parse(this.bytes.toString('utf8', start, this.index)) as string;
757
+ }
758
+ if (value < 0x20) return undefined;
759
+ if (value !== 0x5c) continue;
760
+ if (this.index === this.bytes.length) return undefined;
761
+ const escaped = this.bytes[this.index++]!;
762
+ if (escaped === 0x75) {
763
+ for (let count = 0; count < 4; count += 1) {
764
+ if (this.index === this.bytes.length) return undefined;
765
+ const digit = this.bytes[this.index++];
766
+ if (!isHexDigit(digit)) return undefined;
767
+ }
768
+ } else if (escaped !== 0x22 && escaped !== 0x5c && escaped !== 0x2f
769
+ && escaped !== 0x62 && escaped !== 0x66 && escaped !== 0x6e
770
+ && escaped !== 0x72 && escaped !== 0x74) {
771
+ return undefined;
772
+ }
773
+ }
774
+ return undefined;
775
+ }
776
+
777
+ readNullableString(): string | symbol | undefined {
778
+ if (this.consume('null')) return NULL_JSON_STRING;
779
+ return this.readString();
780
+ }
781
+
782
+ readOptionalNullableString(name: string, target: Record<string, unknown>): boolean {
783
+ if (!this.consume(`,\"${name}\":`)) return true;
784
+ const value = this.readNullableString();
785
+ if (value === undefined) return false;
786
+ target[name] = value === NULL_JSON_STRING ? null : value;
787
+ return true;
788
+ }
789
+
790
+ readOptionalString(name: string, target: Record<string, unknown>): boolean {
791
+ if (!this.consume(`,\"${name}\":`)) return true;
792
+ const value = this.readString();
793
+ if (value === undefined) return false;
794
+ target[name] = value;
795
+ return true;
796
+ }
797
+
798
+ readOptionalSingleDigit(name: string, target: Record<string, unknown>): boolean {
799
+ if (!this.consume(`,\"${name}\":`)) return true;
800
+ const value = this.readSingleDigit();
801
+ if (value === undefined) return false;
802
+ target[name] = value;
803
+ return true;
804
+ }
805
+ }
806
+
807
+ function isHexDigit(value: number): boolean {
808
+ return (value >= 0x30 && value <= 0x39)
809
+ || (value >= 0x41 && value <= 0x46)
810
+ || (value >= 0x61 && value <= 0x66);
811
+ }
812
+
540
813
  function schemaForInboundChannelEnvelope(header: ZLinkChannelEnvelopeHeader): ZLinkJsonSchema | undefined {
541
814
  const contract = readZLinkPacketJsonContract(header.messageName);
542
815
  return header.kind === ZLinkChannelMessageKind.Response ? contract?.reply : contract?.payload;
@@ -565,22 +838,19 @@ function validateChannelHeader(value: unknown, flowEnabled = true): ZLinkChannel
565
838
  }
566
839
  const correlationId = requireNullableString(header.correlationId, 'correlationId');
567
840
  validateCorrelationForKind(kind, correlationId);
568
- return {
569
- formatMarker: ZLINK_CHANNEL_FORMAT_MARKER,
570
- kind,
571
- channelName: requireString(header.channelName, 'channelName'),
572
- messageName: requireString(header.messageName, 'messageName'),
573
- contentType,
574
- correlationId,
575
- deadline: requireNullableString(header.deadline, 'deadline'),
576
- topic: requireNullableString(header.topic, 'topic'),
577
- errorCode: header.errorCode === undefined ? null : requireNullableString(header.errorCode, 'errorCode'),
578
- errorMessage: header.errorMessage === undefined ? null : requireNullableString(header.errorMessage, 'errorMessage'),
579
- source: header.source === undefined ? undefined : requireNullableString(header.source, 'source'),
580
- metadata: requireApplicationMetadata(header.metadata),
581
- flowId,
582
- flowOrigin
583
- };
841
+ requireString(header.channelName, 'channelName');
842
+ requireString(header.messageName, 'messageName');
843
+ requireNullableString(header.deadline, 'deadline');
844
+ requireNullableString(header.topic, 'topic');
845
+ // Parsing already gives this receive operation an owned object. Validate
846
+ // and normalize it here instead of allocating a second header per record.
847
+ header.errorCode = header.errorCode === undefined ? null : requireNullableString(header.errorCode, 'errorCode');
848
+ header.errorMessage = header.errorMessage === undefined ? null : requireNullableString(header.errorMessage, 'errorMessage');
849
+ header.source = header.source === undefined ? undefined : requireNullableString(header.source, 'source');
850
+ header.metadata = requireApplicationMetadata(header.metadata);
851
+ header.flowId = flowId;
852
+ header.flowOrigin = flowOrigin;
853
+ return header as unknown as ZLinkChannelEnvelopeHeader;
584
854
  }
585
855
 
586
856
  function correlationIdForOutboundKind(
@@ -16,8 +16,16 @@ export interface ZLinkMultipartSubmitOperation extends ZLinkMultipartOperation<Z
16
16
  submit(): unknown;
17
17
  }
18
18
 
19
+ /**
20
+ * Binding submit()은 제출 스냅샷을 돌려준다. admission을 기다리려면 `.admitted`를
21
+ * await 한다. binding 타입과 구조적으로 호환되므로 import 하지 않는다.
22
+ */
23
+ export interface ZLinkMultipartSubmission {
24
+ readonly admitted: Promise<void>;
25
+ }
26
+
19
27
  export interface ZLinkMultipartAsyncSubmitOperation extends ZLinkMultipartOperation<ZLinkMultipartAsyncSubmitOperation> {
20
- submit(): Promise<void>;
28
+ submit(): ZLinkMultipartSubmission;
21
29
  }
22
30
 
23
31
  export type ZLinkMultipartReplyOperation = ZLinkMultipartSubmitOperation;
@@ -572,7 +572,6 @@ function requestTerminalResult(
572
572
  if (signal?.aborted === true) return 'cancelled';
573
573
  if (error instanceof ZLinkFrameworkException) {
574
574
  if (error.kind === ZLinkFrameworkErrorKind.ShuttingDown) return 'shutdown';
575
- if (error.kind === ZLinkFrameworkErrorKind.CapacityExceeded) return 'backpressured';
576
575
  }
577
576
  return 'failed';
578
577
  }