Offload functions to worker threads with shared memory primitives for Node.js.
1import type { Transferable } from 'node:worker_threads';
2import { MessagePort } from 'node:worker_threads';
3
4const TRANSFER = Symbol.for('moroutine.transfer');
5
6export interface Transferred<T> {
7 readonly [TRANSFER]: true;
8 readonly value: T;
9}
10
11/**
12 * Marks a value for zero-copy transfer via postMessage. The original becomes detached after sending.
13 * @param value - The value to transfer (ArrayBuffer, TypedArray, MessagePort, or stream).
14 * @returns The same value, typed unchanged for transparent use as a moroutine argument.
15 */
16export function transfer<T>(value: T): T {
17 return { [TRANSFER]: true as const, value } as unknown as T;
18}
19
20export function extractTransferables(args: unknown[]): { args: unknown[]; transfer: Transferable[] } {
21 const transferList: Transferable[] = [];
22 const processedArgs = args.map((arg) => {
23 if (typeof arg === 'object' && arg !== null && TRANSFER in arg) {
24 const value = (arg as Transferred<unknown>).value;
25 collectTransferables(value, transferList);
26 return value;
27 }
28 return arg;
29 });
30 return { args: processedArgs, transfer: transferList };
31}
32
33export function collectTransferables(value: unknown, list: Transferable[]): void {
34 if (!(typeof value === 'object' && value !== null)) return;
35
36 // Directly transferable
37 if (
38 value instanceof ArrayBuffer ||
39 value instanceof MessagePort ||
40 value instanceof ReadableStream ||
41 value instanceof WritableStream
42 ) {
43 list.push(value);
44 return;
45 }
46
47 // TypedArray or DataView — extract .buffer
48 if (ArrayBuffer.isView(value) && value.buffer instanceof ArrayBuffer) {
49 list.push(value.buffer);
50 return;
51 }
52
53 // TransformStream — extract both streams
54 if (value instanceof TransformStream) {
55 list.push(value.readable, value.writable);
56 return;
57 }
58}