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: slab() helper for allocating multiple sync primitives in one SharedArrayBuffer

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

Devin Ivy 98dd6fbd 115e2fb7

+84
+1
src/index.ts
··· 17 17 RwLock, 18 18 ReadGuard, 19 19 WriteGuard, 20 + slab, 20 21 } from './sync/index.ts';
+1
src/sync/atomic-bool.ts
··· 1 1 export class AtomicBool { 2 2 static readonly byteSize = 1; 3 + static readonly byteAlignment = 1; 3 4 private readonly view: Uint8Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-int16.ts
··· 1 1 export class AtomicInt16 { 2 2 static readonly byteSize = 2; 3 + static readonly byteAlignment = 2; 3 4 private readonly view: Int16Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-int32.ts
··· 1 1 export class AtomicInt32 { 2 2 static readonly byteSize = 4; 3 + static readonly byteAlignment = 4; 3 4 private readonly view: Int32Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-int64.ts
··· 1 1 export class AtomicInt64 { 2 2 static readonly byteSize = 8; 3 + static readonly byteAlignment = 8; 3 4 private readonly view: BigInt64Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-int8.ts
··· 1 1 export class AtomicInt8 { 2 2 static readonly byteSize = 1; 3 + static readonly byteAlignment = 1; 3 4 private readonly view: Int8Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-uint16.ts
··· 1 1 export class AtomicUint16 { 2 2 static readonly byteSize = 2; 3 + static readonly byteAlignment = 2; 3 4 private readonly view: Uint16Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-uint32.ts
··· 1 1 export class AtomicUint32 { 2 2 static readonly byteSize = 4; 3 + static readonly byteAlignment = 4; 3 4 private readonly view: Uint32Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-uint64.ts
··· 1 1 export class AtomicUint64 { 2 2 static readonly byteSize = 8; 3 + static readonly byteAlignment = 8; 3 4 private readonly view: BigUint64Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/atomic-uint8.ts
··· 1 1 export class AtomicUint8 { 2 2 static readonly byteSize = 1; 3 + static readonly byteAlignment = 1; 3 4 private readonly view: Uint8Array; 4 5 5 6 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/index.ts
··· 9 9 export { AtomicUint64 } from './atomic-uint64.ts'; 10 10 export { Mutex, MutexGuard } from './mutex.ts'; 11 11 export { RwLock, ReadGuard, WriteGuard } from './rwlock.ts'; 12 + export { slab } from './slab.ts';
+1
src/sync/mutex.ts
··· 15 15 16 16 export class Mutex { 17 17 static readonly byteSize = 4; 18 + static readonly byteAlignment = 4; 18 19 private readonly view: Int32Array; 19 20 20 21 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+1
src/sync/rwlock.ts
··· 27 27 28 28 export class RwLock { 29 29 static readonly byteSize = 4; 30 + static readonly byteAlignment = 4; 30 31 private readonly view: Int32Array; 31 32 32 33 constructor(buffer?: SharedArrayBuffer, byteOffset?: number) {
+28
src/sync/slab.ts
··· 1 + interface Slabbable<T> { 2 + readonly byteSize: number; 3 + readonly byteAlignment: number; 4 + new (buffer: SharedArrayBuffer, byteOffset: number): T; 5 + } 6 + 7 + type SlabInstances<T extends Slabbable<any>[]> = { 8 + [K in keyof T]: T[K] extends Slabbable<infer I> ? I : never; 9 + }; 10 + 11 + function align(offset: number, alignment: number): number { 12 + const remainder = offset % alignment; 13 + return remainder === 0 ? offset : offset + (alignment - remainder); 14 + } 15 + 16 + export function slab<T extends Slabbable<any>[]>(...classes: T): SlabInstances<T> { 17 + const offsets: number[] = []; 18 + let cursor = 0; 19 + for (const cls of classes) { 20 + cursor = align(cursor, cls.byteAlignment); 21 + offsets.push(cursor); 22 + cursor += cls.byteSize; 23 + } 24 + 25 + const buffer = new SharedArrayBuffer(cursor); 26 + const instances = classes.map((cls, i) => new cls(buffer, offsets[i])); 27 + return instances as SlabInstances<T>; 28 + }
+43
test/sync/slab.test.ts
··· 1 + import { describe, it } from 'node:test'; 2 + import assert from 'node:assert/strict'; 3 + import { slab, AtomicInt32, AtomicBool, AtomicInt64, Mutex, RwLock } from 'moroutine'; 4 + 5 + describe('slab', () => { 6 + it('allocates a single SharedArrayBuffer for multiple types', () => { 7 + const [a, b] = slab(AtomicInt32, AtomicInt32); 8 + a.store(1); 9 + b.store(2); 10 + assert.equal(a.load(), 1); 11 + assert.equal(b.load(), 2); 12 + }); 13 + 14 + it('works with mixed types', () => { 15 + const [counter, flag, lock] = slab(AtomicInt32, AtomicBool, Mutex); 16 + counter.store(42); 17 + flag.store(true); 18 + assert.equal(counter.load(), 42); 19 + assert.equal(flag.load(), true); 20 + }); 21 + 22 + it('handles alignment for 64-bit types', () => { 23 + const [flag, big] = slab(AtomicBool, AtomicInt64); 24 + flag.store(true); 25 + big.store(999n); 26 + assert.equal(flag.load(), true); 27 + assert.equal(big.load(), 999n); 28 + }); 29 + 30 + it('returns correct types', () => { 31 + const [i32, bool, mutex, rwlock] = slab(AtomicInt32, AtomicBool, Mutex, RwLock); 32 + i32.add(1); 33 + bool.xor(true); 34 + mutex.lock(); 35 + rwlock.readLock(); 36 + }); 37 + 38 + it('single type works', () => { 39 + const [a] = slab(AtomicInt32); 40 + a.store(7); 41 + assert.equal(a.load(), 7); 42 + }); 43 + });