knitting 0.1.51 → 0.1.52

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 (54) hide show
  1. package/README.md +213 -54
  2. package/knitting.d.ts +1 -0
  3. package/map.md +15 -3
  4. package/package.json +14 -2
  5. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  6. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  7. package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
  8. package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
  9. package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
  11. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  12. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  13. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  14. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  15. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  16. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  17. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  18. package/scripts/build-native-addons.ts +5 -0
  19. package/src/api.d.ts +3 -10
  20. package/src/api.js +25 -21
  21. package/src/common/envelope.d.ts +9 -3
  22. package/src/common/envelope.js +14 -0
  23. package/src/common/worker-runtime.d.ts +2 -0
  24. package/src/common/worker-runtime.js +9 -0
  25. package/src/connections/buffer-reference-native.d.ts +56 -0
  26. package/src/connections/buffer-reference-native.js +217 -0
  27. package/src/connections/buffer-reference.d.ts +76 -0
  28. package/src/connections/buffer-reference.js +459 -0
  29. package/src/connections/index.d.ts +1 -0
  30. package/src/connections/index.js +1 -0
  31. package/src/connections/node-addons.d.ts +1 -1
  32. package/src/connections/node-buffer-pointer.d.ts +20 -0
  33. package/src/connections/node-buffer-pointer.js +16 -0
  34. package/src/connections/shared-array-buffer-payload.d.ts +36 -0
  35. package/src/connections/shared-array-buffer-payload.js +235 -0
  36. package/src/knitting_buffer_pointer.cc +425 -0
  37. package/src/memory/lock.d.ts +12 -1
  38. package/src/memory/lock.js +47 -4
  39. package/src/memory/payloadCodec.js +220 -37
  40. package/src/runtime/pool.d.ts +2 -1
  41. package/src/runtime/pool.js +8 -1
  42. package/src/runtime/process-worker.js +3 -1
  43. package/src/runtime/tx-queue.d.ts +3 -2
  44. package/src/runtime/tx-queue.js +18 -13
  45. package/src/types.d.ts +26 -18
  46. package/src/utils/http.d.ts +21 -0
  47. package/src/utils/http.js +93 -0
  48. package/src/worker/loop.js +23 -3
  49. package/src/worker/rx-queue.d.ts +4 -1
  50. package/src/worker/rx-queue.js +53 -4
  51. package/unsafe.d.ts +1 -0
  52. package/unsafe.js +1 -0
  53. package/utils.d.ts +1 -0
  54. package/utils.js +1 -0
@@ -54,6 +54,12 @@ export var PayloadBuffer;
54
54
  PayloadBuffer[PayloadBuffer["ExternalPayload"] = 44] = "ExternalPayload";
55
55
  PayloadBuffer[PayloadBuffer["StaticExternalPayload"] = 45] = "StaticExternalPayload";
56
56
  PayloadBuffer[PayloadBuffer["ProcessSharedBuffer"] = 46] = "ProcessSharedBuffer";
57
+ PayloadBuffer[PayloadBuffer["BufferReference"] = 47] = "BufferReference";
58
+ PayloadBuffer[PayloadBuffer["SharedArrayBuffer"] = 48] = "SharedArrayBuffer";
59
+ PayloadBuffer[PayloadBuffer["EnvelopeStaticHeaderExternal"] = 49] = "EnvelopeStaticHeaderExternal";
60
+ PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeaderExternal"] = 50] = "EnvelopeDynamicHeaderExternal";
61
+ PayloadBuffer[PayloadBuffer["EnvelopeStaticHeaderStringExternal"] = 51] = "EnvelopeStaticHeaderStringExternal";
62
+ PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeaderStringExternal"] = 52] = "EnvelopeDynamicHeaderStringExternal";
57
63
  })(PayloadBuffer || (PayloadBuffer = {}));
58
64
  export var LockBound;
59
65
  (function (LockBound) {
@@ -64,6 +70,7 @@ export var LockBound;
64
70
  })(LockBound || (LockBound = {}));
65
71
  export const LOCK_CACHE_LINE_BYTES = 64;
66
72
  export const LOCK_SECTOR_BYTES = 256;
73
+ export const PayloadTransportFinalizer = Symbol.for("knitting.payloadCodec.transportFinalizer");
67
74
  export const PromisePayloadMarker = Symbol.for("knitting.promise.payload");
68
75
  const TASK_LOCAL_FLAGS_INDEX = 7;
69
76
  const TASK_LOCAL_PROMISE_PENDING_FLAG = 1 << 0;
@@ -72,7 +79,8 @@ export const beginPromisePayload = (task) => {
72
79
  const flags = task[TASK_LOCAL_FLAGS_INDEX];
73
80
  if ((flags & TASK_LOCAL_PROMISE_PENDING_FLAG) !== 0)
74
81
  return false;
75
- task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_PENDING_FLAG) >>> 0;
82
+ task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_PENDING_FLAG) >>>
83
+ 0;
76
84
  return true;
77
85
  };
78
86
  export const finishPromisePayload = (task) => {
@@ -83,6 +91,36 @@ export const isPromisePayloadPending = (task) => (task[TASK_LOCAL_FLAGS_INDEX] &
83
91
  export const resetTaskLocalFlags = (task) => {
84
92
  task[TASK_LOCAL_FLAGS_INDEX] = 0;
85
93
  };
94
+ export const addTaskFinalizer = (task, finalizer) => {
95
+ const previous = task.finalize;
96
+ task.finalize = previous === undefined ? finalizer : () => {
97
+ try {
98
+ previous();
99
+ }
100
+ finally {
101
+ finalizer();
102
+ }
103
+ };
104
+ };
105
+ export const attachPayloadTransportFinalizer = (task, value) => {
106
+ if (task.finalize !== undefined || value === null || typeof value !== "object") {
107
+ return;
108
+ }
109
+ const finalizer = value[PayloadTransportFinalizer]?.();
110
+ if (typeof finalizer === "function")
111
+ addTaskFinalizer(task, finalizer);
112
+ };
113
+ export const runTaskFinalizers = (task) => {
114
+ const finalizer = task.finalize;
115
+ task.finalize = undefined;
116
+ if (finalizer !== undefined) {
117
+ try {
118
+ finalizer();
119
+ }
120
+ catch {
121
+ }
122
+ }
123
+ };
86
124
  export var TaskIndex;
87
125
  (function (TaskIndex) {
88
126
  /**
@@ -186,6 +224,7 @@ const def = (_) => { };
186
224
  const createTaskShell = () => {
187
225
  const task = new Uint32Array(TaskIndex.Size);
188
226
  task.value = null;
227
+ task.finalize = undefined;
189
228
  task.resolve = def;
190
229
  task.reject = def;
191
230
  task[TASK_LOCAL_FLAGS_INDEX] = 0;
@@ -266,10 +305,12 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
266
305
  lockSector: payloadLockRegion,
267
306
  textCompat: resolvedTextCompat,
268
307
  onPromise: (task, isRejected, value) => {
269
- if ((task[TASK_LOCAL_FLAGS_INDEX] & TASK_LOCAL_PROMISE_TRACKED_FLAG) !== 0 &&
308
+ if ((task[TASK_LOCAL_FLAGS_INDEX] & TASK_LOCAL_PROMISE_TRACKED_FLAG) !==
309
+ 0 &&
270
310
  pendingPromiseCount > 0) {
271
311
  task[TASK_LOCAL_FLAGS_INDEX] =
272
- (task[TASK_LOCAL_FLAGS_INDEX] & ~TASK_LOCAL_PROMISE_TRACKED_FLAG) >>> 0;
312
+ (task[TASK_LOCAL_FLAGS_INDEX] & ~TASK_LOCAL_PROMISE_TRACKED_FLAG) >>>
313
+ 0;
273
314
  pendingPromiseCount = (pendingPromiseCount - 1) | 0;
274
315
  }
275
316
  promiseHandler(task, isRejected, value);
@@ -322,7 +363,8 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
322
363
  const flags = task[TASK_LOCAL_FLAGS_INDEX];
323
364
  if ((flags & TASK_LOCAL_PROMISE_TRACKED_FLAG) !== 0)
324
365
  return;
325
- task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_TRACKED_FLAG) >>> 0;
366
+ task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_TRACKED_FLAG) >>>
367
+ 0;
326
368
  pendingPromiseCount = (pendingPromiseCount + 1) | 0;
327
369
  };
328
370
  const encodeTaskValue = (task, slotIndex) => encodeTask(task, slotIndex);
@@ -633,6 +675,7 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
633
675
  if (recycled) {
634
676
  fillTaskFrom(recycled, headersBuffer, off);
635
677
  recycled.value = null;
678
+ recycled.finalize = undefined;
636
679
  recycled.resolve = def;
637
680
  recycled.reject = def;
638
681
  task = recycled;
@@ -1,10 +1,12 @@
1
- import { beginPromisePayload, finishPromisePayload, getTaskSlotIndex, HEADER_BYTE_LENGTH, HEADER_SLOT_STRIDE_U32, HEADER_STATIC_PAYLOAD_U32, LockBound, PayloadBuffer, PayloadSignal, TaskIndex, } from "./lock.js";
1
+ import { attachPayloadTransportFinalizer, beginPromisePayload, finishPromisePayload, getTaskSlotIndex, HEADER_BYTE_LENGTH, HEADER_SLOT_STRIDE_U32, HEADER_STATIC_PAYLOAD_U32, LockBound, PayloadBuffer, PayloadSignal, TaskIndex, } from "./lock.js";
2
2
  import { register } from "./regionRegistry.js";
3
3
  import { createSharedDynamicBufferIO, createSharedStaticBufferIO, } from "./shared-buffer-io.js";
4
4
  import { getStridedRegionSpanBytes } from "./byte-carpet.js";
5
5
  import { encoderError, ErrorKnitting } from "../error.js";
6
6
  import { Envelope } from "../common/envelope.js";
7
7
  import { resolvePayloadBufferOptions, } from "./payload-config.js";
8
+ import { getSharedArrayBufferPayload, SHARED_ARRAY_BUFFER_CODEC_ID, SHARED_ARRAY_BUFFER_NUMERIC_TRANSFER, SHARED_ARRAY_BUFFER_NUMERIC_WORDS, } from "../connections/shared-array-buffer-payload.js";
9
+ const BUFFER_REFERENCE_NUMERIC_TRANSFER = Symbol.for("knitting.bufferReference.numericTransfer");
8
10
  const memory = new ArrayBuffer(8);
9
11
  const Float64View = new Float64Array(memory);
10
12
  const BigInt64View = new BigInt64Array(memory);
@@ -28,6 +30,7 @@ const BIGINT64_MAX = (1n << 63n) - 1n;
28
30
  const { parse: parseJSON, stringify: stringifyJSON } = JSON;
29
31
  const { for: symbolFor, keyFor: symbolKeyFor } = Symbol;
30
32
  const EXTERNAL_PAYLOAD_BRAND = symbolFor("knitting.payloadCodec");
33
+ const BUFFER_REFERENCE_CODEC_ID = "knitting.bufferReference";
31
34
  const PROCESS_SHARED_BUFFER_CODEC_ID = "knitting.processSharedBuffer";
32
35
  const externalPayloadGlobal = globalThis;
33
36
  const objectGetPrototypeOf = Object.getPrototypeOf;
@@ -35,7 +38,8 @@ const objectHasOwn = Object.prototype.hasOwnProperty;
35
38
  const arrayIsArray = Array.isArray;
36
39
  const objectPrototype = Object.prototype;
37
40
  const UNSUPPORTED_OBJECT_DETAIL = "Unsupported object type. Allowed: plain object, array, Error, Date, Envelope, Buffer, ArrayBuffer, DataView, typed arrays, and registered external payloads. Serialize it yourself.";
38
- const ENVELOPE_PAYLOAD_DETAIL = "Envelope payload must be an ArrayBuffer.";
41
+ const ENVELOPE_PAYLOAD_DETAIL = "Envelope payload must be an ArrayBuffer, SharedArrayBuffer, " +
42
+ "ProcessSharedBuffer, or BufferReference.";
39
43
  const ENVELOPE_HEADER_DETAIL = "Envelope header must be a JSON-like value or string.";
40
44
  const ENVELOPE_PROMISE_DETAIL = "Envelope header cannot contain Promise values.";
41
45
  const DYNAMIC_PAYLOAD_LIMIT_DETAIL = "Dynamic payload exceeds maxPayloadBytes.";
@@ -92,15 +96,18 @@ const decodeExternalPayload = (raw) => {
92
96
  : { codec: codecId, metadata };
93
97
  };
94
98
  const PROCESS_SHARED_BUFFER_NUMERIC_WORDS = 8;
99
+ const BUFFER_REFERENCE_NUMERIC_WORDS = 8;
95
100
  const NUMERIC_SENTINEL = 0xffffffff;
96
- const decodeProcessSharedBufferNumericWords = (words) => {
97
- const codec = externalPayloadGlobal.__KNITTING_PAYLOAD_CODECS__?.[PROCESS_SHARED_BUFFER_CODEC_ID];
98
- if (typeof codec?.decodeNumeric === "function")
101
+ const decodeNumericExternalPayload = (codecId, words) => {
102
+ const codec = externalPayloadGlobal.__KNITTING_PAYLOAD_CODECS__?.[codecId];
103
+ if (typeof codec?.decodeNumeric === "function") {
99
104
  return codec.decodeNumeric(words);
100
- // `words` is a reusable scratch; copy it on the (unreachable in practice)
101
- // codec-missing diagnostic path so the escaped value never aliases it.
102
- return { codec: PROCESS_SHARED_BUFFER_CODEC_ID, metadata: Array.from(words) };
105
+ }
106
+ return { codec: codecId, metadata: Array.from(words) };
103
107
  };
108
+ const decodeProcessSharedBufferNumericWords = (words) => decodeNumericExternalPayload(PROCESS_SHARED_BUFFER_CODEC_ID, words);
109
+ const decodeBufferReferenceNumericWords = (words) => decodeNumericExternalPayload(BUFFER_REFERENCE_CODEC_ID, words);
110
+ const decodeSharedArrayBufferNumericWords = (words) => decodeNumericExternalPayload(SHARED_ARRAY_BUFFER_CODEC_ID, words);
104
111
  const tryEncodePrimitiveTask = (task) => {
105
112
  const value = task.value;
106
113
  switch (typeof value) {
@@ -516,6 +523,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
516
523
  return true;
517
524
  };
518
525
  const processSharedBufferWords = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
526
+ const sharedArrayBufferWords = new Uint32Array(SHARED_ARRAY_BUFFER_NUMERIC_WORDS);
519
527
  const tryEncodeProcessSharedBufferNumeric = (task, slotIndex, value) => {
520
528
  const descriptor = value.descriptor;
521
529
  if (descriptor === undefined ||
@@ -549,6 +557,33 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
549
557
  task.value = null;
550
558
  return true;
551
559
  };
560
+ const tryEncodeBufferReferenceNumeric = (task, slotIndex, value) => {
561
+ const words = value[BUFFER_REFERENCE_NUMERIC_TRANSFER]?.();
562
+ if (words === undefined)
563
+ return false;
564
+ task[TaskIndex.Type] = PayloadBuffer.BufferReference;
565
+ task[TaskIndex.PayloadLen] = writeStaticU32Words(words, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex);
566
+ attachPayloadTransportFinalizer(task, value);
567
+ task.value = null;
568
+ return true;
569
+ };
570
+ const tryEncodeSharedArrayBufferNumeric = (task, slotIndex, value) => {
571
+ const words = value[SHARED_ARRAY_BUFFER_NUMERIC_TRANSFER]?.(lockSector);
572
+ if (words === undefined)
573
+ return false;
574
+ sharedArrayBufferWords[0] = words[0] ?? 0;
575
+ sharedArrayBufferWords[1] = words[1] ?? 0;
576
+ sharedArrayBufferWords[2] = words[2] ?? 0;
577
+ sharedArrayBufferWords[3] = words[3] ?? 0;
578
+ sharedArrayBufferWords[4] = words[4] ?? 0;
579
+ sharedArrayBufferWords[5] = words[5] ?? 0;
580
+ sharedArrayBufferWords[6] = words[6] ?? 0;
581
+ sharedArrayBufferWords[7] = words[7] ?? 0;
582
+ task[TaskIndex.Type] = PayloadBuffer.SharedArrayBuffer;
583
+ task[TaskIndex.PayloadLen] = writeStaticU32Words(sharedArrayBufferWords, words.length, slotIndex);
584
+ task.value = null;
585
+ return true;
586
+ };
552
587
  const encodeObjectExternalPayload = (task, slotIndex, externalPayload) => {
553
588
  const codecId = readExternalPayloadCodecId(externalPayload);
554
589
  if (codecId === undefined) {
@@ -559,6 +594,14 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
559
594
  detail: UNSUPPORTED_OBJECT_DETAIL,
560
595
  });
561
596
  }
597
+ if (codecId === SHARED_ARRAY_BUFFER_CODEC_ID &&
598
+ tryEncodeSharedArrayBufferNumeric(task, slotIndex, externalPayload)) {
599
+ return true;
600
+ }
601
+ if (codecId === BUFFER_REFERENCE_CODEC_ID &&
602
+ tryEncodeBufferReferenceNumeric(task, slotIndex, externalPayload)) {
603
+ return true;
604
+ }
562
605
  if (codecId === PROCESS_SHARED_BUFFER_CODEC_ID &&
563
606
  tryEncodeProcessSharedBufferNumeric(task, slotIndex, externalPayload)) {
564
607
  return true;
@@ -589,6 +632,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
589
632
  if (written !== -1) {
590
633
  task[TaskIndex.Type] = PayloadBuffer.StaticExternalPayload;
591
634
  task[TaskIndex.PayloadLen] = written;
635
+ attachPayloadTransportFinalizer(task, externalPayload);
592
636
  task.value = null;
593
637
  return true;
594
638
  }
@@ -603,6 +647,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
603
647
  return failDynamicWriteAfterReserve(task, reservedSlot);
604
648
  task[TaskIndex.PayloadLen] = written;
605
649
  setSlotLength(reservedSlot, written);
650
+ attachPayloadTransportFinalizer(task, externalPayload);
606
651
  task.value = null;
607
652
  return true;
608
653
  };
@@ -614,52 +659,50 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
614
659
  task.value = null;
615
660
  return true;
616
661
  };
617
- const encodeObjectEnvelope = (task, slotIndex, envelope) => {
618
- const header = envelope.header;
619
- const payload = envelope.payload;
620
- const headerIsString = typeof header === "string";
621
- if (!(payload instanceof ArrayBuffer)) {
622
- return encoderError({
623
- task,
624
- type: ErrorKnitting.Serializable,
625
- onPromise,
626
- detail: ENVELOPE_PAYLOAD_DETAIL,
627
- });
628
- }
662
+ const encodeEnvelopeHeaderText = (task, header, headerIsString) => {
629
663
  if (hasPromiseInEnvelopeHeader(header)) {
630
- return encoderError({
664
+ encoderError({
631
665
  task,
632
666
  type: ErrorKnitting.Serializable,
633
667
  onPromise,
634
668
  detail: ENVELOPE_PROMISE_DETAIL,
635
669
  });
670
+ return undefined;
636
671
  }
672
+ if (headerIsString)
673
+ return header;
637
674
  let headerText;
638
- if (headerIsString) {
639
- headerText = header;
675
+ try {
676
+ headerText = stringifyJSON(header);
640
677
  }
641
- else {
642
- try {
643
- headerText = stringifyJSON(header);
644
- }
645
- catch (error) {
646
- const detail = error instanceof Error ? error.message : String(error);
647
- return encoderError({
648
- task,
649
- type: ErrorKnitting.Json,
650
- onPromise,
651
- detail,
652
- });
653
- }
678
+ catch (error) {
679
+ const detail = error instanceof Error ? error.message : String(error);
680
+ encoderError({ task, type: ErrorKnitting.Json, onPromise, detail });
681
+ return undefined;
654
682
  }
655
683
  if (typeof headerText !== "string") {
656
- return encoderError({
684
+ encoderError({
657
685
  task,
658
686
  type: ErrorKnitting.Serializable,
659
687
  onPromise,
660
688
  detail: ENVELOPE_HEADER_DETAIL,
661
689
  });
690
+ return undefined;
691
+ }
692
+ return headerText;
693
+ };
694
+ const resolveEnvelopeExternalBody = (body) => {
695
+ if (body === null || typeof body !== "object")
696
+ return undefined;
697
+ const sharedArrayBuffer = getSharedArrayBufferPayload(body);
698
+ if (sharedArrayBuffer !== undefined)
699
+ return sharedArrayBuffer;
700
+ if (isExternalPayloadLike(body)) {
701
+ return body;
662
702
  }
703
+ return undefined;
704
+ };
705
+ const encodeEnvelopeArrayBufferBody = (task, slotIndex, headerText, headerIsString, payload) => {
663
706
  const payloadBytes = new Uint8Array(payload);
664
707
  const payloadLength = payloadBytes.byteLength;
665
708
  const payloadReserveBytes = payloadLength > 0 ? payloadLength : 1;
@@ -707,6 +750,107 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
707
750
  task.value = null;
708
751
  return true;
709
752
  };
753
+ const encodeEnvelopeExternalBody = (task, slotIndex, headerText, headerIsString, externalBody) => {
754
+ const codecId = readExternalPayloadCodecId(externalBody);
755
+ if (codecId === undefined) {
756
+ return encoderError({
757
+ task,
758
+ type: ErrorKnitting.Serializable,
759
+ onPromise,
760
+ detail: ENVELOPE_PAYLOAD_DETAIL,
761
+ });
762
+ }
763
+ let bodyText;
764
+ try {
765
+ bodyText = stringifyJSON([codecId, externalBody.toMetadata()]);
766
+ }
767
+ catch (error) {
768
+ const detail = error instanceof Error ? error.message : String(error);
769
+ return encoderError({
770
+ task,
771
+ type: ErrorKnitting.Serializable,
772
+ onPromise,
773
+ detail,
774
+ });
775
+ }
776
+ if (typeof bodyText !== "string") {
777
+ return encoderError({
778
+ task,
779
+ type: ErrorKnitting.Serializable,
780
+ onPromise,
781
+ detail: "Envelope body metadata must be JSON serializable.",
782
+ });
783
+ }
784
+ const bodyBytes = textEncode.encode(bodyText);
785
+ const bodyLength = bodyBytes.byteLength;
786
+ const staticHeaderWritten = writeStaticUtf8(headerText, slotIndex);
787
+ if (staticHeaderWritten !== -1) {
788
+ if (!ensureWithinDynamicLimit(task, bodyLength, "EnvelopeStaticHeaderExternal"))
789
+ return false;
790
+ const reservedSlot = reserveDynamicObject(task, bodyLength);
791
+ task[TaskIndex.Type] = headerIsString
792
+ ? PayloadBuffer.EnvelopeStaticHeaderStringExternal
793
+ : PayloadBuffer.EnvelopeStaticHeaderExternal;
794
+ task[TaskIndex.PayloadLen] = staticHeaderWritten;
795
+ task[TaskIndex.End] = bodyLength;
796
+ const bodyWritten = writeDynamicBinary(bodyBytes, task[TaskIndex.Start]);
797
+ if (bodyWritten < 0) {
798
+ return failDynamicWriteAfterReserve(task, reservedSlot);
799
+ }
800
+ setSlotLength(reservedSlot, bodyWritten);
801
+ attachPayloadTransportFinalizer(task, externalBody);
802
+ task.value = null;
803
+ return true;
804
+ }
805
+ const headerReserveBytes = dynamicUtf8ReserveBytesWithExtra(task, headerText, bodyLength, headerIsString
806
+ ? "EnvelopeDynamicHeaderStringExternal"
807
+ : "EnvelopeDynamicHeaderExternal");
808
+ if (headerReserveBytes < 0)
809
+ return false;
810
+ task[TaskIndex.Type] = headerIsString
811
+ ? PayloadBuffer.EnvelopeDynamicHeaderStringExternal
812
+ : PayloadBuffer.EnvelopeDynamicHeaderExternal;
813
+ const reservedSlot = reserveDynamicObject(task, headerReserveBytes + bodyLength);
814
+ const baseStart = task[TaskIndex.Start];
815
+ const writtenHeaderBytes = writeDynamicUtf8(headerText, baseStart, headerReserveBytes);
816
+ if (writtenHeaderBytes < 0) {
817
+ return failDynamicWriteAfterReserve(task, reservedSlot);
818
+ }
819
+ const bodyWritten = writeDynamicBinary(bodyBytes, baseStart + writtenHeaderBytes);
820
+ if (bodyWritten < 0) {
821
+ return failDynamicWriteAfterReserve(task, reservedSlot);
822
+ }
823
+ task[TaskIndex.PayloadLen] = writtenHeaderBytes;
824
+ task[TaskIndex.End] = bodyLength;
825
+ setSlotLength(reservedSlot, writtenHeaderBytes + bodyLength);
826
+ attachPayloadTransportFinalizer(task, externalBody);
827
+ task.value = null;
828
+ return true;
829
+ };
830
+ const encodeObjectEnvelope = (task, slotIndex, envelope) => {
831
+ const header = envelope.header;
832
+ const payload = envelope.payload;
833
+ const headerIsString = typeof header === "string";
834
+ if (payload instanceof ArrayBuffer) {
835
+ const headerText = encodeEnvelopeHeaderText(task, header, headerIsString);
836
+ if (headerText === undefined)
837
+ return false;
838
+ return encodeEnvelopeArrayBufferBody(task, slotIndex, headerText, headerIsString, payload);
839
+ }
840
+ const externalBody = resolveEnvelopeExternalBody(payload);
841
+ if (externalBody !== undefined) {
842
+ const headerText = encodeEnvelopeHeaderText(task, header, headerIsString);
843
+ if (headerText === undefined)
844
+ return false;
845
+ return encodeEnvelopeExternalBody(task, slotIndex, headerText, headerIsString, externalBody);
846
+ }
847
+ return encoderError({
848
+ task,
849
+ type: ErrorKnitting.Serializable,
850
+ onPromise,
851
+ detail: ENVELOPE_PAYLOAD_DETAIL,
852
+ });
853
+ };
710
854
  const encodeObjectPromise = (task, promise) => {
711
855
  if (beginPromisePayload(task)) {
712
856
  promise.then((value) => {
@@ -769,6 +913,10 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
769
913
  objectDynamicSlot = -1;
770
914
  try {
771
915
  const objectValue = args;
916
+ const sharedArrayBufferPayload = getSharedArrayBufferPayload(objectValue);
917
+ if (sharedArrayBufferPayload !== undefined) {
918
+ return encodeObjectExternalPayload(task, slotIndex, sharedArrayBufferPayload);
919
+ }
772
920
  const objectProto = objectGetPrototypeOf(objectValue);
773
921
  if (isRuntimeUint8Array(objectValue)) {
774
922
  return encodeObjectUint8Array(task, slotIndex, objectValue);
@@ -963,6 +1111,8 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
963
1111
  // decode is single-consumer and not re-entrant, and the words are consumed
964
1112
  // synchronously when building the ProcessSharedBuffer.
965
1113
  const processSharedBufferWords = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
1114
+ const sharedArrayBufferWords = new Uint32Array(SHARED_ARRAY_BUFFER_NUMERIC_WORDS);
1115
+ const bufferReferenceWords = new Uint32Array(BUFFER_REFERENCE_NUMERIC_WORDS);
966
1116
  // TODO: remove slotIndex and make that all their callers
967
1117
  // store the slot in their Task, to just get it when it comes
968
1118
  // to the static versions of decoding
@@ -1037,6 +1187,33 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1037
1187
  freeTaskSlot(task);
1038
1188
  return;
1039
1189
  }
1190
+ case PayloadBuffer.EnvelopeStaticHeaderExternal:
1191
+ case PayloadBuffer.EnvelopeStaticHeaderStringExternal: {
1192
+ const rawHeader = readStaticUtf8(0, task[TaskIndex.PayloadLen], slotIndex);
1193
+ const header = task[TaskIndex.Type] ===
1194
+ PayloadBuffer.EnvelopeStaticHeaderStringExternal
1195
+ ? rawHeader
1196
+ : parseJSON(rawHeader);
1197
+ const bodyStart = task[TaskIndex.Start];
1198
+ const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + task[TaskIndex.End]));
1199
+ task.value = new Envelope(header, body);
1200
+ freeTaskSlot(task);
1201
+ return;
1202
+ }
1203
+ case PayloadBuffer.EnvelopeDynamicHeaderExternal:
1204
+ case PayloadBuffer.EnvelopeDynamicHeaderStringExternal: {
1205
+ const headerStart = task[TaskIndex.Start];
1206
+ const bodyStart = headerStart + task[TaskIndex.PayloadLen];
1207
+ const rawHeader = readDynamicUtf8(headerStart, bodyStart);
1208
+ const header = task[TaskIndex.Type] ===
1209
+ PayloadBuffer.EnvelopeDynamicHeaderStringExternal
1210
+ ? rawHeader
1211
+ : parseJSON(rawHeader);
1212
+ const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + task[TaskIndex.End]));
1213
+ task.value = new Envelope(header, body);
1214
+ freeTaskSlot(task);
1215
+ return;
1216
+ }
1040
1217
  case PayloadBuffer.BigInt:
1041
1218
  task.value = decodeBigIntBinary(readDynamicBufferCopy(task[TaskIndex.Start], task[TaskIndex.Start] + task[TaskIndex.PayloadLen]));
1042
1219
  freeTaskSlot(task);
@@ -1113,6 +1290,12 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1113
1290
  case PayloadBuffer.ProcessSharedBuffer:
1114
1291
  task.value = decodeProcessSharedBufferNumericWords(readStaticU32Words(processSharedBufferWords, PROCESS_SHARED_BUFFER_NUMERIC_WORDS, slotIndex));
1115
1292
  return;
1293
+ case PayloadBuffer.SharedArrayBuffer:
1294
+ task.value = decodeSharedArrayBufferNumericWords(readStaticU32Words(sharedArrayBufferWords, task[TaskIndex.PayloadLen] >>> 2, slotIndex));
1295
+ return;
1296
+ case PayloadBuffer.BufferReference:
1297
+ task.value = decodeBufferReferenceNumericWords(readStaticU32Words(bufferReferenceWords, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex));
1298
+ return;
1116
1299
  case PayloadBuffer.Date:
1117
1300
  Uint32View[0] = task[TaskIndex.Start];
1118
1301
  Uint32View[1] = task[TaskIndex.End];
@@ -4,7 +4,7 @@ import { lock2 } from "../memory/lock.js";
4
4
  import type { DebugOptions, DispatcherSettings, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
5
5
  import "../worker/loop.js";
6
6
  import { type PayloadBufferOptions } from "../memory/payload-config.js";
7
- export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }: {
7
+ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }: {
8
8
  list: string[];
9
9
  ids: number[];
10
10
  names: string[];
@@ -19,6 +19,7 @@ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug
19
19
  permission?: WorkerData["permission"];
20
20
  host?: DispatcherSettings;
21
21
  payload?: PayloadBufferOptions;
22
+ bufferReferenceReturn?: "copy" | "borrow";
22
23
  payloadInitialBytes?: number;
23
24
  payloadMaxBytes?: number;
24
25
  bufferMode?: PayloadBufferOptions["mode"];
@@ -10,6 +10,7 @@ import { probeLockBufferTextCompat } from "../common/shared-buffer-text.js";
10
10
  import { signalAbortFactory } from "../shared/abortSignal.js";
11
11
  import { createLockControlCarpet } from "../memory/byte-carpet.js";
12
12
  import { resolvePayloadBufferOptions, } from "../memory/payload-config.js";
13
+ import { createBufferReferenceReturnReleaseMessage } from "../connections/buffer-reference.js";
13
14
  const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
14
15
  const isWorkerFatalMessage = (value) => !!value &&
15
16
  typeof value === "object" &&
@@ -33,7 +34,7 @@ const withFixedPayloadConfig = (config) => ({
33
34
  mode: "fixed",
34
35
  payloadInitialBytes: config.payloadMaxByteLength,
35
36
  });
36
- export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
37
+ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
37
38
  const tsFileUrl = new URL(import.meta.url);
38
39
  const poliWorker = RUNTIME_WORKER;
39
40
  const resolvedWorkerOptions = serializeWorkerBootstrapData(withDefaultWorkerTimers(workerOptions));
@@ -193,6 +194,11 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
193
194
  lock,
194
195
  returnLock,
195
196
  abortSignals,
197
+ releaseBufferReferenceReturn: bufferReferenceReturn === "borrow"
198
+ ? (token) => {
199
+ worker?.postMessage?.(createBufferReferenceReturnReleaseMessage(token));
200
+ }
201
+ : undefined,
196
202
  });
197
203
  const { enqueue, rejectAll, txIdle, } = queue;
198
204
  const channelHandler = new ChannelHandler();
@@ -227,6 +233,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
227
233
  lock: lockBuffers,
228
234
  returnLock: returnLockBuffers,
229
235
  payloadConfig: resolvedPayloadConfig,
236
+ bufferReferenceReturn,
230
237
  permission,
231
238
  };
232
239
  const baseWorkerOptions = {
@@ -2,7 +2,7 @@ import { fileURLToPath as fileURLToPathCompat } from "node:url";
2
2
  import { HEADER_SLOT_STRIDE_U32, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
3
3
  import { createByteCarpet, getHeaderBlockByteLength, makeSharedBufferRegion, } from "../memory/byte-carpet.js";
4
4
  import { RUNTIME } from "../common/runtime.js";
5
- import { RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, RUNTIME_PROCESS_WORKER_ENV, } from "../common/worker-runtime.js";
5
+ import { RUNTIME_POOL_DEPTH, RUNTIME_POOL_DEPTH_ENV, RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, RUNTIME_PROCESS_WORKER_ENV, } from "../common/worker-runtime.js";
6
6
  import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
7
7
  import { toSharedBufferRegion, } from "../common/shared-buffer-region.js";
8
8
  import { createBunConnectionPrimitives } from "../connections/bun.js";
@@ -100,6 +100,7 @@ const DEFAULT_NODE_BINARY = "node";
100
100
  const DENO_PROCESS_WORKER_BOOT_ENV_ALLOW = [
101
101
  RUNTIME_PROCESS_WORKER_ENV,
102
102
  RUNTIME_PROCESS_WORKER_BOOT_ENV,
103
+ RUNTIME_POOL_DEPTH_ENV,
103
104
  ].join(",");
104
105
  const DENO_PROCESS_WORKER_INTERNAL_FLAGS = [
105
106
  `--allow-env=${DENO_PROCESS_WORKER_BOOT_ENV_ALLOW}`,
@@ -354,6 +355,7 @@ const currentProcessEnv = () => ({
354
355
  const processWorkerEnv = (extra) => ({
355
356
  ...currentProcessEnv(),
356
357
  [RUNTIME_PROCESS_WORKER_ENV]: "1",
358
+ [RUNTIME_POOL_DEPTH_ENV]: String(RUNTIME_POOL_DEPTH + 1),
357
359
  ...extra,
358
360
  });
359
361
  const processWorkerBootEnv = (bootPayload) => processWorkerEnv({
@@ -1,4 +1,4 @@
1
- import { type Task, type Lock2 } from "../memory/lock.js";
1
+ import { type Lock2, type Task } from "../memory/lock.js";
2
2
  import type { AbortSignalOption, TaskTimeout } from "../types.js";
3
3
  import { type SignalAbortStore } from "../shared/abortSignal.js";
4
4
  type RawArguments = unknown;
@@ -9,10 +9,11 @@ type CreateHostTxQueueArgs = {
9
9
  max?: number;
10
10
  lock: Lock2;
11
11
  returnLock: Lock2;
12
+ releaseBufferReferenceReturn?: (token: bigint) => void;
12
13
  abortSignals?: Pick<SignalAbortStore, "getSignal" | "setSignal" | "resetSignal" | "closeNow">;
13
14
  now?: () => number;
14
15
  };
15
- export declare function createHostTxQueue({ max, lock, returnLock, abortSignals, now, }: CreateHostTxQueueArgs): {
16
+ export declare function createHostTxQueue({ max, lock, returnLock, releaseBufferReferenceReturn, abortSignals, now, }: CreateHostTxQueueArgs): {
16
17
  rejectAll: (reason: string) => void;
17
18
  hasPendingFrames: () => boolean;
18
19
  txIdle: () => boolean;