knitting 0.1.51 → 0.1.53

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 (69) 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 +29 -16
  20. package/src/api.js +98 -28
  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 +78 -0
  28. package/src/connections/buffer-reference.js +461 -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/process-shared-buffer.d.ts +6 -0
  35. package/src/connections/process-shared-buffer.js +6 -0
  36. package/src/connections/shared-array-buffer-payload.d.ts +36 -0
  37. package/src/connections/shared-array-buffer-payload.js +235 -0
  38. package/src/debug/env-diff.d.ts +26 -0
  39. package/src/debug/env-diff.js +49 -0
  40. package/src/debug/gate.d.ts +18 -0
  41. package/src/debug/gate.js +69 -0
  42. package/src/debug/handle.d.ts +23 -0
  43. package/src/debug/handle.js +48 -0
  44. package/src/ipc/transport/shared-memory.d.ts +1 -3
  45. package/src/knitting_buffer_pointer.cc +425 -0
  46. package/src/memory/lock.d.ts +12 -1
  47. package/src/memory/lock.js +47 -4
  48. package/src/memory/payload-config.d.ts +9 -0
  49. package/src/memory/payloadCodec.js +220 -37
  50. package/src/permission/protocol.d.ts +1 -1
  51. package/src/permission/protocol.js +30 -20
  52. package/src/runtime/pool.d.ts +3 -5
  53. package/src/runtime/pool.js +18 -18
  54. package/src/runtime/process-worker.js +5 -2
  55. package/src/runtime/tx-queue.d.ts +3 -2
  56. package/src/runtime/tx-queue.js +18 -13
  57. package/src/types.d.ts +54 -50
  58. package/src/utils/http.d.ts +29 -0
  59. package/src/utils/http.js +100 -0
  60. package/src/worker/loop.js +76 -14
  61. package/src/worker/rx-queue.d.ts +4 -1
  62. package/src/worker/rx-queue.js +53 -4
  63. package/src/worker/safety/startup.d.ts +2 -3
  64. package/src/worker/safety/startup.js +1 -4
  65. package/src/worker/timers.js +7 -2
  66. package/unsafe.d.ts +1 -0
  67. package/unsafe.js +1 -0
  68. package/utils.d.ts +1 -0
  69. package/utils.js +1 -0
@@ -0,0 +1,425 @@
1
+ #if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
2
+ #error "knitting_buffer_pointer.cc currently supports Linux, macOS, and Windows."
3
+ #endif
4
+
5
+ #include <node.h>
6
+ #include <v8.h>
7
+
8
+ #include <cstdint>
9
+ #include <memory>
10
+ #include <mutex>
11
+ #include <unordered_map>
12
+
13
+ namespace knitting_buffer_pointer {
14
+
15
+ struct RetainedReference {
16
+ v8::Isolate* isolate;
17
+ v8::Global<v8::Value> value;
18
+ };
19
+
20
+ std::mutex retained_mutex;
21
+ uint64_t next_retained_id = 1;
22
+ std::unordered_map<uint64_t, std::unique_ptr<RetainedReference>> retained_refs;
23
+
24
+ // BackingStore is isolate-independent, unlike v8::Global<Value>.
25
+ // Consumers can co-own moved bytes while the registry keeps producer liveness.
26
+ struct RetainedBackingStore {
27
+ std::shared_ptr<v8::BackingStore> store;
28
+ size_t byte_offset;
29
+ size_t byte_length;
30
+ };
31
+
32
+ std::mutex backing_mutex;
33
+ uint64_t next_backing_id = 1;
34
+ std::unordered_map<uint64_t, RetainedBackingStore> retained_backings;
35
+
36
+ void ThrowType(v8::Isolate* isolate, const char* message) {
37
+ isolate->ThrowException(v8::Exception::TypeError(
38
+ v8::String::NewFromUtf8(isolate, message).ToLocalChecked()
39
+ ));
40
+ }
41
+
42
+ v8::Local<v8::String> DetachKey(v8::Isolate* isolate) {
43
+ return v8::String::NewFromUtf8Literal(
44
+ isolate,
45
+ "knitting.bufferReference.detachKey"
46
+ );
47
+ }
48
+
49
+ bool DetachWithKey(
50
+ v8::Local<v8::ArrayBuffer> buffer,
51
+ v8::Local<v8::Value> key
52
+ ) {
53
+ v8::Maybe<bool> detached = buffer->Detach(key);
54
+ return (detached.IsJust() && detached.FromJust()) || buffer->WasDetached();
55
+ }
56
+
57
+ bool DetachDefaultArrayBuffer(v8::Local<v8::ArrayBuffer> buffer) {
58
+ if (buffer->WasDetached()) return true;
59
+ if (!buffer->IsDetachable()) return false;
60
+ return DetachWithKey(buffer, v8::Local<v8::Value>());
61
+ }
62
+
63
+ bool DetachKnittingArrayBuffer(
64
+ v8::Isolate* isolate,
65
+ v8::Local<v8::ArrayBuffer> buffer
66
+ ) {
67
+ if (buffer->WasDetached()) return true;
68
+ if (!buffer->IsDetachable()) return false;
69
+ if (DetachWithKey(buffer, DetachKey(isolate))) return true;
70
+ return DetachWithKey(buffer, v8::Local<v8::Value>());
71
+ }
72
+
73
+ bool ReadPointerInfo(
74
+ v8::Isolate* isolate,
75
+ v8::Local<v8::Value> value,
76
+ void** data,
77
+ size_t* byte_length
78
+ ) {
79
+ size_t byte_offset = 0;
80
+
81
+ if (value->IsArrayBufferView()) {
82
+ v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
83
+ *data = view->Buffer()->Data();
84
+ byte_offset = view->ByteOffset();
85
+ *byte_length = view->ByteLength();
86
+ } else if (value->IsArrayBuffer()) {
87
+ v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>();
88
+ *data = buffer->Data();
89
+ *byte_length = buffer->ByteLength();
90
+ } else if (value->IsSharedArrayBuffer()) {
91
+ v8::Local<v8::SharedArrayBuffer> buffer = value.As<v8::SharedArrayBuffer>();
92
+ *data = buffer->Data();
93
+ *byte_length = buffer->ByteLength();
94
+ } else {
95
+ ThrowType(
96
+ isolate,
97
+ "getPointer expects an ArrayBuffer, SharedArrayBuffer, or typed array"
98
+ );
99
+ return false;
100
+ }
101
+
102
+ *data = static_cast<uint8_t*>(*data) + byte_offset;
103
+ return true;
104
+ }
105
+
106
+ uint64_t ReadToken(
107
+ const v8::FunctionCallbackInfo<v8::Value>& args,
108
+ int index,
109
+ const char* message
110
+ ) {
111
+ v8::Isolate* isolate = args.GetIsolate();
112
+ if (args.Length() <= index || !args[index]->IsBigInt()) {
113
+ ThrowType(isolate, message);
114
+ return 0;
115
+ }
116
+
117
+ bool lossless = false;
118
+ uint64_t token = args[index].As<v8::BigInt>()->Uint64Value(&lossless);
119
+ if (token == 0) {
120
+ ThrowType(isolate, "retained pointer token must be non-zero");
121
+ return 0;
122
+ }
123
+ return token;
124
+ }
125
+
126
+ void GetPointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
127
+ v8::Isolate* isolate = args.GetIsolate();
128
+
129
+ if (args.Length() < 1) {
130
+ ThrowType(isolate, "getPointer(buffer) requires an ArrayBuffer or view");
131
+ return;
132
+ }
133
+
134
+ void* data = nullptr;
135
+ size_t byte_length = 0;
136
+ if (!ReadPointerInfo(isolate, args[0], &data, &byte_length)) return;
137
+
138
+ uintptr_t address = reinterpret_cast<uintptr_t>(data);
139
+ args.GetReturnValue().Set(
140
+ v8::BigInt::NewFromUnsigned(isolate, static_cast<uint64_t>(address))
141
+ );
142
+ }
143
+
144
+ void RetainPointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
145
+ v8::Isolate* isolate = args.GetIsolate();
146
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
147
+
148
+ if (args.Length() < 1) {
149
+ ThrowType(isolate, "retainPointer(buffer) requires an ArrayBuffer or view");
150
+ return;
151
+ }
152
+
153
+ void* data = nullptr;
154
+ size_t byte_length = 0;
155
+ if (!ReadPointerInfo(isolate, args[0], &data, &byte_length)) return;
156
+
157
+ auto retained = std::make_unique<RetainedReference>();
158
+ retained->isolate = isolate;
159
+ retained->value.Reset(isolate, args[0]);
160
+
161
+ uint64_t token = 0;
162
+ {
163
+ std::lock_guard<std::mutex> lock(retained_mutex);
164
+ token = next_retained_id++;
165
+ if (token == 0) token = next_retained_id++;
166
+ retained_refs.emplace(token, std::move(retained));
167
+ }
168
+
169
+ v8::Local<v8::Object> result = v8::Object::New(isolate);
170
+ result->Set(
171
+ context,
172
+ v8::String::NewFromUtf8Literal(isolate, "pointer"),
173
+ v8::BigInt::NewFromUnsigned(
174
+ isolate,
175
+ static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data))
176
+ )
177
+ ).ToChecked();
178
+ result->Set(
179
+ context,
180
+ v8::String::NewFromUtf8Literal(isolate, "byteLength"),
181
+ v8::Number::New(isolate, static_cast<double>(byte_length))
182
+ ).ToChecked();
183
+ result->Set(
184
+ context,
185
+ v8::String::NewFromUtf8Literal(isolate, "token"),
186
+ v8::BigInt::NewFromUnsigned(isolate, token)
187
+ ).ToChecked();
188
+ args.GetReturnValue().Set(result);
189
+ }
190
+
191
+ void ReleasePointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
192
+ uint64_t token = ReadToken(args, 0, "releasePointer(token) requires a bigint");
193
+ if (token == 0) return;
194
+
195
+ std::unique_ptr<RetainedReference> retained;
196
+ {
197
+ std::lock_guard<std::mutex> lock(retained_mutex);
198
+ auto found = retained_refs.find(token);
199
+ if (found == retained_refs.end()) {
200
+ args.GetReturnValue().Set(false);
201
+ return;
202
+ }
203
+ retained = std::move(found->second);
204
+ retained_refs.erase(found);
205
+ }
206
+
207
+ retained->value.Reset();
208
+ args.GetReturnValue().Set(true);
209
+ }
210
+
211
+ void NoopDeleter(void*, size_t, void*) {}
212
+
213
+ void WrapPointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
214
+ v8::Isolate* isolate = args.GetIsolate();
215
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
216
+
217
+ if (args.Length() < 2 || !args[0]->IsBigInt() || !args[1]->IsNumber()) {
218
+ ThrowType(
219
+ isolate,
220
+ "wrapPointer(pointer: bigint, byteLength: number) requires both arguments"
221
+ );
222
+ return;
223
+ }
224
+
225
+ bool lossless = false;
226
+ uint64_t address = args[0].As<v8::BigInt>()->Uint64Value(&lossless);
227
+ if (address == 0) {
228
+ ThrowType(isolate, "wrapPointer received a null pointer");
229
+ return;
230
+ }
231
+
232
+ v8::Maybe<int64_t> maybe_length = args[1]->IntegerValue(context);
233
+ if (maybe_length.IsNothing() || maybe_length.FromJust() < 0) {
234
+ ThrowType(isolate, "byteLength must be a non-negative integer");
235
+ return;
236
+ }
237
+ size_t byte_length = static_cast<size_t>(maybe_length.FromJust());
238
+
239
+ void* data = reinterpret_cast<void*>(static_cast<uintptr_t>(address));
240
+ std::unique_ptr<v8::BackingStore> backing = v8::ArrayBuffer::NewBackingStore(
241
+ data,
242
+ byte_length,
243
+ NoopDeleter,
244
+ nullptr
245
+ );
246
+ std::shared_ptr<v8::BackingStore> shared(std::move(backing));
247
+ v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, shared);
248
+ buffer->SetDetachKey(DetachKey(isolate));
249
+ args.GetReturnValue().Set(buffer);
250
+ }
251
+
252
+ void DetachArrayBuffer(const v8::FunctionCallbackInfo<v8::Value>& args) {
253
+ v8::Isolate* isolate = args.GetIsolate();
254
+
255
+ if (args.Length() < 1 || !args[0]->IsArrayBuffer()) {
256
+ ThrowType(isolate, "detachArrayBuffer(buffer) requires an ArrayBuffer");
257
+ return;
258
+ }
259
+
260
+ v8::Local<v8::ArrayBuffer> buffer = args[0].As<v8::ArrayBuffer>();
261
+ if (buffer->WasDetached()) {
262
+ args.GetReturnValue().Set(true);
263
+ return;
264
+ }
265
+ args.GetReturnValue().Set(DetachKnittingArrayBuffer(isolate, buffer));
266
+ }
267
+
268
+ // Read the underlying ArrayBuffer + region of an ArrayBuffer or typed-array view.
269
+ // SharedArrayBuffer is rejected: it is already shareable and must not be detached.
270
+ bool ReadArrayBufferRegion(
271
+ v8::Isolate* isolate,
272
+ v8::Local<v8::Value> value,
273
+ v8::Local<v8::ArrayBuffer>* buffer,
274
+ size_t* byte_offset,
275
+ size_t* byte_length
276
+ ) {
277
+ if (value->IsArrayBufferView()) {
278
+ v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
279
+ *buffer = view->Buffer();
280
+ *byte_offset = view->ByteOffset();
281
+ *byte_length = view->ByteLength();
282
+ return true;
283
+ }
284
+ if (value->IsArrayBuffer()) {
285
+ v8::Local<v8::ArrayBuffer> ab = value.As<v8::ArrayBuffer>();
286
+ *buffer = ab;
287
+ *byte_offset = 0;
288
+ *byte_length = ab->ByteLength();
289
+ return true;
290
+ }
291
+ ThrowType(
292
+ isolate,
293
+ "retainBackingStore expects an ArrayBuffer or typed array (not a SharedArrayBuffer)"
294
+ );
295
+ return false;
296
+ }
297
+
298
+ // retainBackingStore(buffer) -> { pointer, byteOffset, byteLength, token }
299
+ // Registry-pins the backing store, then detaches the source for the move.
300
+ void RetainBackingStore(const v8::FunctionCallbackInfo<v8::Value>& args) {
301
+ v8::Isolate* isolate = args.GetIsolate();
302
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
303
+
304
+ if (args.Length() < 1) {
305
+ ThrowType(isolate, "retainBackingStore(buffer) requires an ArrayBuffer or view");
306
+ return;
307
+ }
308
+
309
+ v8::Local<v8::ArrayBuffer> buffer;
310
+ size_t byte_offset = 0;
311
+ size_t byte_length = 0;
312
+ if (!ReadArrayBufferRegion(isolate, args[0], &buffer, &byte_offset, &byte_length)) {
313
+ return;
314
+ }
315
+
316
+ std::shared_ptr<v8::BackingStore> store = buffer->GetBackingStore();
317
+ uint8_t* base = static_cast<uint8_t*>(store->Data());
318
+ uintptr_t address = reinterpret_cast<uintptr_t>(base + byte_offset);
319
+
320
+ // Detach only after taking the shared_ptr; ordinary JS buffers use no key.
321
+ DetachDefaultArrayBuffer(buffer);
322
+
323
+ uint64_t token = 0;
324
+ {
325
+ std::lock_guard<std::mutex> lock(backing_mutex);
326
+ token = next_backing_id++;
327
+ if (token == 0) token = next_backing_id++;
328
+ retained_backings.emplace(
329
+ token,
330
+ RetainedBackingStore{ std::move(store), byte_offset, byte_length }
331
+ );
332
+ }
333
+
334
+ v8::Local<v8::Object> result = v8::Object::New(isolate);
335
+ result->Set(
336
+ context,
337
+ v8::String::NewFromUtf8Literal(isolate, "pointer"),
338
+ v8::BigInt::NewFromUnsigned(isolate, static_cast<uint64_t>(address))
339
+ ).ToChecked();
340
+ result->Set(
341
+ context,
342
+ v8::String::NewFromUtf8Literal(isolate, "byteOffset"),
343
+ v8::Number::New(isolate, static_cast<double>(byte_offset))
344
+ ).ToChecked();
345
+ result->Set(
346
+ context,
347
+ v8::String::NewFromUtf8Literal(isolate, "byteLength"),
348
+ v8::Number::New(isolate, static_cast<double>(byte_length))
349
+ ).ToChecked();
350
+ result->Set(
351
+ context,
352
+ v8::String::NewFromUtf8Literal(isolate, "token"),
353
+ v8::BigInt::NewFromUnsigned(isolate, token)
354
+ ).ToChecked();
355
+ args.GetReturnValue().Set(result);
356
+ }
357
+
358
+ // adoptBackingStore(token) -> ArrayBuffer
359
+ // Returns an ArrayBuffer in the caller's isolate that co-owns the registry store.
360
+ // The registry entry remains until producer release.
361
+ void AdoptBackingStore(const v8::FunctionCallbackInfo<v8::Value>& args) {
362
+ v8::Isolate* isolate = args.GetIsolate();
363
+ uint64_t token = ReadToken(args, 0, "adoptBackingStore(token) requires a bigint");
364
+ if (token == 0) return;
365
+
366
+ std::shared_ptr<v8::BackingStore> store;
367
+ {
368
+ std::lock_guard<std::mutex> lock(backing_mutex);
369
+ auto found = retained_backings.find(token);
370
+ if (found == retained_backings.end()) {
371
+ ThrowType(isolate, "adoptBackingStore: unknown or released token");
372
+ return;
373
+ }
374
+ store = found->second.store;
375
+ }
376
+
377
+ v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, store);
378
+ buffer->SetDetachKey(DetachKey(isolate));
379
+ args.GetReturnValue().Set(buffer);
380
+ }
381
+
382
+ // releaseBackingStore(token) -> boolean
383
+ // Drops the registry's reference. The store is freed only if no consumer adopted.
384
+ void ReleaseBackingStore(const v8::FunctionCallbackInfo<v8::Value>& args) {
385
+ uint64_t token =
386
+ ReadToken(args, 0, "releaseBackingStore(token) requires a bigint");
387
+ if (token == 0) return;
388
+
389
+ RetainedBackingStore released;
390
+ bool found = false;
391
+ {
392
+ std::lock_guard<std::mutex> lock(backing_mutex);
393
+ auto it = retained_backings.find(token);
394
+ if (it != retained_backings.end()) {
395
+ released = std::move(it->second);
396
+ retained_backings.erase(it);
397
+ found = true;
398
+ }
399
+ }
400
+ // released.store drops here, outside the lock.
401
+ args.GetReturnValue().Set(found);
402
+ }
403
+
404
+ // SAB transport avoids SharedArrayBuffer::New across isolates: worker teardown
405
+ // can corrupt the shared store. JS pins plus non-owning aliases keep native
406
+ // handles from outliving their isolate.
407
+
408
+ void Initialize(
409
+ v8::Local<v8::Object> exports,
410
+ v8::Local<v8::Value>,
411
+ v8::Local<v8::Context>
412
+ ) {
413
+ NODE_SET_METHOD(exports, "getPointer", GetPointer);
414
+ NODE_SET_METHOD(exports, "retainPointer", RetainPointer);
415
+ NODE_SET_METHOD(exports, "releasePointer", ReleasePointer);
416
+ NODE_SET_METHOD(exports, "wrapPointer", WrapPointer);
417
+ NODE_SET_METHOD(exports, "detachArrayBuffer", DetachArrayBuffer);
418
+ NODE_SET_METHOD(exports, "retainBackingStore", RetainBackingStore);
419
+ NODE_SET_METHOD(exports, "adoptBackingStore", AdoptBackingStore);
420
+ NODE_SET_METHOD(exports, "releaseBackingStore", ReleaseBackingStore);
421
+ }
422
+
423
+ NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
424
+
425
+ }
@@ -49,7 +49,13 @@ export declare enum PayloadBuffer {
49
49
  EnvelopeDynamicHeaderString = 43,
50
50
  ExternalPayload = 44,
51
51
  StaticExternalPayload = 45,
52
- ProcessSharedBuffer = 46
52
+ ProcessSharedBuffer = 46,
53
+ BufferReference = 47,
54
+ SharedArrayBuffer = 48,
55
+ EnvelopeStaticHeaderExternal = 49,
56
+ EnvelopeDynamicHeaderExternal = 50,
57
+ EnvelopeStaticHeaderStringExternal = 51,
58
+ EnvelopeDynamicHeaderStringExternal = 52
53
59
  }
54
60
  export declare enum LockBound {
55
61
  paddingLock = 0,
@@ -70,15 +76,20 @@ export type Task = [
70
76
  number
71
77
  ] & {
72
78
  value: unknown;
79
+ finalize?: (() => void) | undefined;
73
80
  resolve: (value?: unknown) => void;
74
81
  reject: (reason?: unknown) => void;
75
82
  };
83
+ export declare const PayloadTransportFinalizer: unique symbol;
76
84
  export declare const PromisePayloadMarker: unique symbol;
77
85
  export type PromisePayloadHandler = (task: Task, isRejected: boolean, value: unknown) => void;
78
86
  export declare const beginPromisePayload: (task: Task) => boolean;
79
87
  export declare const finishPromisePayload: (task: Task) => void;
80
88
  export declare const isPromisePayloadPending: (task: Task) => boolean;
81
89
  export declare const resetTaskLocalFlags: (task: Task) => void;
90
+ export declare const addTaskFinalizer: (task: Task, finalizer: () => void) => void;
91
+ export declare const attachPayloadTransportFinalizer: (task: Task, value: unknown) => void;
92
+ export declare const runTaskFinalizers: (task: Task) => void;
82
93
  export declare enum TaskIndex {
83
94
  /**
84
95
  * Worker -> host response flags word.
@@ -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,11 +1,20 @@
1
1
  import { type SharedBufferSource } from "../common/shared-buffer-region.js";
2
2
  export declare const PAYLOAD_DEFAULT_MAX_BYTE_LENGTH: number;
3
3
  export declare const PAYLOAD_DEFAULT_INITIAL_BYTES: number;
4
+ /** Payload backing mode. Growable uses SAB growth when the runtime supports it. */
4
5
  export type PayloadBufferMode = "growable" | "fixed";
5
6
  export type PayloadBufferOptions = {
7
+ /** Shared payload buffer mode. Defaults to growable when available. */
6
8
  mode?: PayloadBufferMode;
9
+ /** Initial shared payload bytes per direction. Defaults to 4 MiB when growable. */
7
10
  payloadInitialBytes?: number;
11
+ /** Shared payload growth cap. Defaults to 64 MiB. */
8
12
  payloadMaxByteLength?: number;
13
+ /**
14
+ * Max dynamic payload bytes for one call. Defaults to `payloadMaxByteLength >> 3`
15
+ * (about 8 MiB by default) and must stay `<= payloadMaxByteLength >> 3`.
16
+ * Over-cap payloads reject with `KNT_ERROR_3`.
17
+ */
9
18
  maxPayloadBytes?: number;
10
19
  };
11
20
  export type ResolvedPayloadBufferOptions = {