Offload functions to worker threads with shared memory primitives for Node.js.
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: cross-worker serialization for tuple, string, bytes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+101
+9
src/shared/reconstruct.ts
··· 1 1 import { SharedStruct } from './shared-struct.ts'; 2 2 import { Bytes } from './bytes.ts'; 3 3 import { SharedString } from './string.ts'; 4 + import { Tuple } from './tuple.ts'; 4 5 5 6 const SHARED = Symbol.for('moroutine.shared'); 6 7 ··· 19 20 serializedFields[key] = serializeStructField(data.fields[key]); 20 21 } 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 }; 22 27 } 23 28 return { __shared__: data.tag, buffer: data.buffer, byteOffset: data.byteOffset, ...(data.size !== undefined && { size: data.size }), ...(data.maxBytes !== undefined && { maxBytes: data.maxBytes }) }; 24 29 } ··· 54 59 reconstructed[key] = deserializeArg(fields[key]); 55 60 } 56 61 return new SharedStruct(reconstructed as any); 62 + } 63 + if (data.__shared__ === 'Tuple') { 64 + const elements = (data.elements as unknown[]).map((el) => deserializeArg(el)); 65 + return new Tuple(elements as any); 57 66 } 58 67 const typedData = data as { __shared__: string; buffer: SharedArrayBuffer; byteOffset: number }; 59 68 const ctor = registry.get(typedData.__shared__);
+9
test/fixtures/shared-new.ts
··· 1 + import { mo } from 'moroutine'; 2 + 3 + export const readValue = mo(import.meta, (s: any): unknown => { 4 + return s.load(); 5 + }); 6 + 7 + export const writeValue = mo(import.meta, (s: any, values: any): void => { 8 + s.store(values); 9 + });
+83
test/shared/cross-worker-new.test.ts
··· 1 + import { describe, it } from 'node:test'; 2 + import assert from 'node:assert/strict'; 3 + import { workerPool, shared, int32, string, bool, bytes } from 'moroutine'; 4 + import { readValue, writeValue } from '../fixtures/shared-new.ts'; 5 + 6 + describe('new types across workers', () => { 7 + it('struct via shared() works across workers', async () => { 8 + const point = shared({ x: int32, y: int32 }); 9 + point.store({ x: 1, y: 2 }); 10 + 11 + const pool = workerPool(1); 12 + try { 13 + const result = await pool(readValue(point)); 14 + assert.deepEqual(result, { x: 1, y: 2 }); 15 + } finally { 16 + pool[Symbol.dispose](); 17 + } 18 + }); 19 + 20 + it('worker can write to struct via shared()', async () => { 21 + const point = shared({ x: int32, y: int32 }); 22 + 23 + const pool = workerPool(1); 24 + try { 25 + await pool(writeValue(point, { x: 10, y: 20 })); 26 + assert.deepEqual(point.load(), { x: 10, y: 20 }); 27 + } finally { 28 + pool[Symbol.dispose](); 29 + } 30 + }); 31 + 32 + it('tuple via shared() works across workers', async () => { 33 + const t = shared([int32, int32]); 34 + t.store([1, 2]); 35 + 36 + const pool = workerPool(1); 37 + try { 38 + const result = await pool(readValue(t)); 39 + assert.deepEqual(result, [1, 2]); 40 + } finally { 41 + pool[Symbol.dispose](); 42 + } 43 + }); 44 + 45 + it('string via shared() works across workers', async () => { 46 + const s = shared(string(32)); 47 + s.store('hello'); 48 + 49 + const pool = workerPool(1); 50 + try { 51 + const result = await pool(readValue(s)); 52 + assert.equal(result, 'hello'); 53 + } finally { 54 + pool[Symbol.dispose](); 55 + } 56 + }); 57 + 58 + it('bytes via shared() works across workers', async () => { 59 + const b = shared(bytes(4)); 60 + b.store(new Uint8Array([1, 2, 3, 4])); 61 + 62 + const pool = workerPool(1); 63 + try { 64 + const result = await pool(readValue(b)) as Uint8Array; 65 + assert.deepEqual([...result], [1, 2, 3, 4]); 66 + } finally { 67 + pool[Symbol.dispose](); 68 + } 69 + }); 70 + 71 + it('nested struct with string across workers', async () => { 72 + const entity = shared({ name: string(16), hp: int32 }); 73 + entity.store({ name: 'goblin', hp: 50 }); 74 + 75 + const pool = workerPool(1); 76 + try { 77 + const result = await pool(readValue(entity)); 78 + assert.deepEqual(result, { name: 'goblin', hp: 50 }); 79 + } finally { 80 + pool[Symbol.dispose](); 81 + } 82 + }); 83 + });