Offload functions to worker threads with shared memory primitives for Node.js.
1import { SharedStruct } from './shared-struct.ts';
2import { Bytes } from './bytes.ts';
3import { SharedString } from './string.ts';
4import { Tuple } from './tuple.ts';
5
6const SHARED = Symbol.for('moroutine.shared');
7
8const registry = new Map<string, new (buffer: SharedArrayBuffer, byteOffset: number) => unknown>();
9
10export function registerSync(tag: string, ctor: new (buffer: SharedArrayBuffer, byteOffset: number) => unknown): void {
11 registry.set(tag, ctor);
12}
13
14export function serializeArg(arg: unknown): unknown {
15 if (typeof arg === 'object' && arg !== null && SHARED in arg) {
16 const data = (arg as any)[SHARED]();
17 if (data.tag === 'SharedStruct') {
18 const serializedFields: Record<string, unknown> = {};
19 for (const key in data.fields) {
20 serializedFields[key] = serializeStructField(data.fields[key]);
21 }
22 return { __shared__: 'SharedStruct', fields: serializedFields };
23 }
24 if (data.tag === 'Tuple') {
25 const serializedElements = (data.elements as any[]).map((el: any) => serializeStructField(el));
26 return { __shared__: 'Tuple', elements: serializedElements };
27 }
28 return {
29 __shared__: data.tag,
30 buffer: data.buffer,
31 byteOffset: data.byteOffset,
32 ...(data.size !== undefined && { size: data.size }),
33 ...(data.maxBytes !== undefined && { maxBytes: data.maxBytes }),
34 };
35 }
36 return arg;
37}
38
39function serializeStructField(data: { tag: string; [key: string]: unknown }): unknown {
40 if (data.tag === 'SharedStruct') {
41 const serializedFields: Record<string, unknown> = {};
42 for (const key in data.fields as Record<string, unknown>) {
43 serializedFields[key] = serializeStructField((data.fields as any)[key]);
44 }
45 return { __shared__: 'SharedStruct', fields: serializedFields };
46 }
47 if (data.tag === 'Tuple') {
48 const serializedElements = (data.elements as any[]).map((el: any) => serializeStructField(el));
49 return { __shared__: 'Tuple', elements: serializedElements };
50 }
51 return {
52 __shared__: data.tag,
53 buffer: data.buffer,
54 byteOffset: data.byteOffset,
55 ...(data.size !== undefined && { size: data.size }),
56 ...(data.maxBytes !== undefined && { maxBytes: data.maxBytes }),
57 };
58}
59
60export function deserializeArg(arg: unknown): unknown {
61 if (typeof arg === 'object' && arg !== null && '__shared__' in arg) {
62 const data = arg as { __shared__: string; [key: string]: unknown };
63 if (data.__shared__ === 'Bytes') {
64 const typedData = data as { __shared__: string; buffer: SharedArrayBuffer; byteOffset: number; size: number };
65 return new Bytes(typedData.size, typedData.buffer, typedData.byteOffset);
66 }
67 if (data.__shared__ === 'SharedString') {
68 const typedData = data as { __shared__: string; buffer: SharedArrayBuffer; byteOffset: number; maxBytes: number };
69 return new SharedString(typedData.maxBytes, typedData.buffer, typedData.byteOffset);
70 }
71 if (data.__shared__ === 'SharedStruct') {
72 const fields = data.fields as Record<string, unknown>;
73 const reconstructed: Record<string, unknown> = {};
74 for (const key in fields) {
75 reconstructed[key] = deserializeArg(fields[key]);
76 }
77 return new SharedStruct(reconstructed as any);
78 }
79 if (data.__shared__ === 'Tuple') {
80 const elements = (data.elements as unknown[]).map((el) => deserializeArg(el));
81 return new Tuple(elements as any);
82 }
83 const typedData = data as { __shared__: string; buffer: SharedArrayBuffer; byteOffset: number };
84 const ctor = registry.get(typedData.__shared__);
85 if (ctor) return new ctor(typedData.buffer, typedData.byteOffset);
86 }
87 return arg;
88}