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: shared() allocator for primitive and struct schemas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+165 -6
+1
src/index.ts
··· 30 30 WriteGuard, 31 31 slab, 32 32 SharedStruct, 33 + shared, 33 34 } from './shared/index.ts'; 34 35 export { 35 36 int8, uint8, int16, uint16, int32, uint32, int64, uint64, bool,
+1
src/shared/index.ts
··· 21 21 export { RwLock, ReadGuard, WriteGuard } from './rwlock.ts'; 22 22 export { slab } from './slab.ts'; 23 23 export { SharedStruct } from './shared-struct.ts'; 24 + export { shared } from './shared.ts'; 24 25 export { 25 26 int8, uint8, int16, uint16, int32, uint32, int64, uint64, bool, 26 27 int8atomic, uint8atomic, int16atomic, uint16atomic, int32atomic, uint32atomic, int64atomic, uint64atomic, boolatomic,
+27 -6
src/shared/shared-struct.ts
··· 1 1 import type { Loadable } from './loadable.ts'; 2 2 3 - type FieldValues<T extends Record<string, Loadable<any>>> = { 4 - [K in keyof T]: T[K] extends Loadable<infer V> ? V : never; 3 + type LoadableKeys<T extends Record<string, unknown>> = { 4 + [K in keyof T]: T[K] extends Loadable<any> ? K : never; 5 + }[keyof T]; 6 + 7 + type FieldValues<T extends Record<string, unknown>> = { 8 + [K in LoadableKeys<T>]: T[K] extends Loadable<infer V> ? V : never; 5 9 }; 10 + 11 + function isLoadable(value: unknown): value is Loadable<unknown> { 12 + return ( 13 + typeof value === 'object' && 14 + value !== null && 15 + typeof (value as any).load === 'function' && 16 + typeof (value as any).store === 'function' 17 + ); 18 + } 6 19 7 20 const SHARED = Symbol.for('moroutine.shared'); 8 21 9 - export class SharedStruct<T extends Record<string, Loadable<any>>> implements Loadable<FieldValues<T>> { 22 + export class SharedStruct<T extends Record<string, unknown>> implements Loadable<FieldValues<T>> { 10 23 readonly fields: T; 11 24 12 25 constructor(fields: T) { ··· 14 27 } 15 28 16 29 load(): FieldValues<T> { 17 - const result = {} as FieldValues<T>; 30 + const result = {} as any; 18 31 for (const key in this.fields) { 19 - (result as any)[key] = this.fields[key].load(); 32 + const field = this.fields[key]; 33 + if (isLoadable(field)) { 34 + result[key] = field.load(); 35 + } 20 36 } 21 37 return result; 22 38 } 23 39 24 40 store(values: FieldValues<T>): void { 25 41 for (const key in this.fields) { 26 - this.fields[key].store((values as any)[key]); 42 + const field = this.fields[key]; 43 + if (isLoadable(field)) { 44 + if (key in (values as any)) { 45 + field.store((values as any)[key]); 46 + } 47 + } 27 48 } 28 49 } 29 50
+65
src/shared/shared.ts
··· 1 + import type { Descriptor } from './descriptors.ts'; 2 + import { SharedStruct } from './shared-struct.ts'; 3 + 4 + function isDescriptor(value: unknown): value is Descriptor<unknown> { 5 + return typeof value === 'function' && 'byteSize' in value && '_class' in value; 6 + } 7 + 8 + function isStructSchema(value: unknown): value is Record<string, unknown> { 9 + return typeof value === 'object' && value !== null && !Array.isArray(value); 10 + } 11 + 12 + function align(offset: number, alignment: number): number { 13 + const remainder = offset % alignment; 14 + return remainder === 0 ? offset : offset + (alignment - remainder); 15 + } 16 + 17 + interface LeafEntry { 18 + path: string[]; 19 + descriptor: Descriptor<unknown>; 20 + offset: number; 21 + } 22 + 23 + function collectLeaves(schema: Record<string, unknown>, path: string[], leaves: LeafEntry[], cursor: { offset: number }): void { 24 + for (const key in schema) { 25 + const value = schema[key]; 26 + if (isDescriptor(value)) { 27 + cursor.offset = align(cursor.offset, value.byteAlignment); 28 + leaves.push({ path: [...path, key], descriptor: value, offset: cursor.offset }); 29 + cursor.offset += value.byteSize; 30 + } else if (isStructSchema(value)) { 31 + collectLeaves(value as Record<string, unknown>, [...path, key], leaves, cursor); 32 + } 33 + } 34 + } 35 + 36 + function buildStructTree(schema: Record<string, unknown>, leaves: LeafEntry[], buffer: SharedArrayBuffer, leafIndex: { i: number }): SharedStruct<any> { 37 + const fields: Record<string, any> = {}; 38 + for (const key in schema) { 39 + const value = schema[key]; 40 + if (isDescriptor(value)) { 41 + const leaf = leaves[leafIndex.i++]; 42 + fields[key] = new leaf.descriptor._class(buffer, leaf.offset); 43 + } else if (isStructSchema(value)) { 44 + fields[key] = buildStructTree(value as Record<string, unknown>, leaves, buffer, leafIndex); 45 + } 46 + } 47 + return new SharedStruct(fields); 48 + } 49 + 50 + export function shared(schema: unknown): any { 51 + if (isDescriptor(schema)) { 52 + return schema(); 53 + } 54 + 55 + if (isStructSchema(schema)) { 56 + const leaves: LeafEntry[] = []; 57 + const cursor = { offset: 0 }; 58 + collectLeaves(schema as Record<string, unknown>, [], leaves, cursor); 59 + const buffer = new SharedArrayBuffer(cursor.offset); 60 + const leafIndex = { i: 0 }; 61 + return buildStructTree(schema as Record<string, unknown>, leaves, buffer, leafIndex); 62 + } 63 + 64 + throw new Error('Invalid schema'); 65 + }
+71
test/shared/shared.test.ts
··· 1 + import { describe, it } from 'node:test'; 2 + import assert from 'node:assert/strict'; 3 + import { shared, int32, int64, bool, mutex } from 'moroutine'; 4 + 5 + describe('shared()', () => { 6 + it('shared(descriptor) creates a standalone instance', () => { 7 + const x = shared(int32); 8 + assert.equal(x.load(), 0); 9 + x.store(42); 10 + assert.equal(x.load(), 42); 11 + }); 12 + 13 + it('shared(struct schema) creates a struct with fields', () => { 14 + const point = shared({ x: int32, y: int32 }); 15 + assert.deepEqual(point.load(), { x: 0, y: 0 }); 16 + point.store({ x: 10, y: 20 }); 17 + assert.deepEqual(point.load(), { x: 10, y: 20 }); 18 + }); 19 + 20 + it('struct fields are accessible via .fields', () => { 21 + const point = shared({ x: int32, y: int32 }); 22 + point.fields.x.store(42); 23 + assert.equal(point.fields.x.load(), 42); 24 + assert.equal(point.fields.y.load(), 0); 25 + }); 26 + 27 + it('struct with mixed types', () => { 28 + const state = shared({ hp: int32, alive: bool }); 29 + state.store({ hp: 100, alive: true }); 30 + assert.deepEqual(state.load(), { hp: 100, alive: true }); 31 + }); 32 + 33 + it('nested struct schema', () => { 34 + const rect = shared({ 35 + pos: { x: int32, y: int32 }, 36 + size: { w: int32, h: int32 }, 37 + }); 38 + rect.store({ pos: { x: 1, y: 2 }, size: { w: 100, h: 50 } }); 39 + assert.deepEqual(rect.load(), { pos: { x: 1, y: 2 }, size: { w: 100, h: 50 } }); 40 + }); 41 + 42 + it('nested struct fields accessible', () => { 43 + const rect = shared({ 44 + pos: { x: int32, y: int32 }, 45 + w: int32, 46 + }); 47 + rect.fields.pos.fields.x.store(99); 48 + rect.fields.w.store(200); 49 + assert.deepEqual(rect.load(), { pos: { x: 99, y: 0 }, w: 200 }); 50 + }); 51 + 52 + it('struct with lock excludes lock from load/store', () => { 53 + const state = shared({ x: int32, lock: mutex }); 54 + state.store({ x: 42 }); 55 + assert.deepEqual(state.load(), { x: 42 }); 56 + }); 57 + 58 + it('struct lock accessible via fields', async () => { 59 + const state = shared({ x: int32, lock: mutex }); 60 + const guard = await state.fields.lock.lock(); 61 + guard[Symbol.dispose](); 62 + }); 63 + 64 + it('struct fields share one SharedArrayBuffer', () => { 65 + const point = shared({ x: int32, y: int32 }); 66 + const SHARED = Symbol.for('moroutine.shared'); 67 + const xSync = (point.fields.x as any)[SHARED](); 68 + const ySync = (point.fields.y as any)[SHARED](); 69 + assert.equal(xSync.buffer, ySync.buffer); 70 + }); 71 + });