npm.io
0.1.161 • Published 2d ago

sabcom

Licence
Apache-2.0
Version
0.1.161
Deps
0
Size
176 kB
Vulns
0
Weekly
0

SABCOM

Github Build Status NPM version Downloads Coverage Status Snyk

High-performance inter-thread communication using SharedArrayBuffer with atomic operations. Bidirectional channels with synchronous and asynchronous APIs. Faster than postMessage structured clone and transfer.

What sabcom Does

sabcom is a ring-buffer protocol over SharedArrayBuffer. It handles:

  • Bidirectional communication on a single SharedArrayBuffer
  • Synchronization between endpoints via Atomics
  • Segmented transfer for payloads larger than the ring capacity
  • Timeout detection and close semantics

sabcom does NOT:

  • Create worker threads (you create them with worker_threads or new Worker())
  • Transfer the SharedArrayBuffer between threads (you pass it via workerData or postMessage)
  • Serialize data (you encode to Uint8Array before calling write, e.g., with TextEncoder or JSON.stringify)

Features

  • Bidirectional - both endpoints can read and write on a single buffer
  • Async and Sync APIs - write/read (non-blocking) and writeSync/readSync (blocking)
  • Zero-copy reads - single-segment messages return a view into the SharedArrayBuffer
  • Segmented transfer - payloads larger than the ring are split automatically
  • Close and reuse - channels can be closed and buffers reused
  • Configurable timeouts - per-operation timeout support
  • Type-safe - full TypeScript support

Installation

npm install sabcom

Complete Example

worker.ts

import { workerData } from 'worker_threads';
import { open } from 'sabcom';

const ch = open(workerData as SharedArrayBuffer);

const data = ch.readSync();
const message = new TextDecoder().decode(data);
console.log('Worker received:', message);

ch.writeSync(new TextEncoder().encode(message.toUpperCase()));
ch.close();

main.ts

import { Worker } from 'worker_threads';
import { createBuffer, open } from 'sabcom';

const buffer = createBuffer(65536);
const worker = new Worker('./worker.js', { workerData: buffer });
const ch = open(buffer);

const text = 'Hello from the main thread!';
await ch.write(new TextEncoder().encode(text));

const reply = await ch.read();
console.log('Reply:', new TextDecoder().decode(reply));

ch.close();
await worker.terminate();

API

createBuffer(byteLength: number): SharedArrayBuffer

Creates and initializes a SharedArrayBuffer for use as a channel. byteLength must be a multiple of 4 and at least 4096.

open(buffer: SharedArrayBuffer): Channel

Opens a channel on the buffer. Two endpoints can open the same buffer (one per thread). Returns the same handle if called twice in the same thread.

reset(buffer: SharedArrayBuffer): void

Resets a closed buffer so it can be reopened. Both endpoints must be closed first.

Channel
interface Channel {
  write(data: Uint8Array, options?: Options): Promise<void>;
  read(options?: Options): Promise<Uint8Array>;
  writeSync(data: Uint8Array, options?: Options): void;
  readSync(options?: Options): Uint8Array;
  close(): void;
}
  • write / writeSync - send data to the peer endpoint
  • read / readSync - receive data from the peer endpoint
  • close - close this endpoint and notify the peer
Options
interface Options {
  timeout?: number; // milliseconds, default: 5000
}

Zero-Copy Reads

When a message fits in a single ring segment and does not wrap the ring boundary, readSync and read return a Uint8Array view directly into the SharedArrayBuffer - no allocation or copy. The view is valid until the next method call on the same channel (read, readSync, write, writeSync, or close). After that, the underlying memory may be overwritten by the peer.

If you need to keep the data beyond the next call, copy it:

const view = ch.readSync();
const copy = view.slice(); // safe to hold indefinitely

Messages that wrap the ring boundary or span multiple segments return an owned copy automatically.

Buffer Sizing

  • Minimum: 4096 bytes
  • Multiple of 4 required
  • Larger buffers reduce segmentation overhead for large payloads
  • Recommendation: 4KB-64KB depending on average payload size

The buffer is split into two ring directions (one per endpoint). Each direction contains control metadata, message descriptors, and a data ring. Larger buffers allocate more data ring space.

Multi-Worker Architecture

Each worker needs its own SharedArrayBuffer. A single buffer supports exactly two endpoints.

import { Worker } from 'worker_threads';
import { createBuffer, open } from 'sabcom';

async function spawnWorker(task: string) {
  const buffer = createBuffer(65536);
  const worker = new Worker('./worker.js', { workerData: buffer });
  const ch = open(buffer);

  ch.writeSync(new TextEncoder().encode(task));
  const result = await ch.read();

  ch.close();
  await worker.terminate();
  return new TextDecoder().decode(result);
}

const results = await Promise.all([
  spawnWorker('task-a'),
  spawnWorker('task-b'),
  spawnWorker('task-c'),
]);

FAQ

How do I send JSON or objects?

sabcom transfers raw bytes. Serialize before sending:

const obj = { hello: 'world', count: 42 };
ch.writeSync(new TextEncoder().encode(JSON.stringify(obj)));

const data = ch.readSync();
const parsed = JSON.parse(new TextDecoder().decode(data));
Does sabcom work in browsers?

Yes, with Web Workers. SharedArrayBuffer requires cross-origin isolation headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Can I reuse the buffer after closing?

Yes. After both endpoints close, call reset(buffer) and reopen:

ch.close();
// ... peer also closes ...
reset(buffer);
const ch2 = open(buffer);

Or pass the buffer to new workers - open auto-resets buffers where both endpoints have closed.

How do I handle errors?
try {
  ch.writeSync(data, { timeout: 5000 });
} catch (err) {
  if (err.message.includes('timeout')) {
    console.error('Peer did not respond in time');
  } else if (err.message.includes('closed')) {
    console.error('Peer closed the channel');
  }
}

Development

pnpm install
pnpm build
pnpm test
pnpm bench

License

Apache-2.0 (c) Ivan Zakharchanka

Keywords