worker-island
v1.0.1
Published
Seamlessly offload heavy CPU logic, functions, or classes into background Web Workers using Proxies
Maintainers
Readme
🏝️ Worker Island
Seamlessly offload heavy CPU logic, functions, or classes into background threads using ES6 Proxies. Zero configuration. Zero dependencies. Fully typed. Works natively in the Browser and Node.js! 🚀
✨ Features
- 🧮 Transparent Proxies - Call background functions, construct classes, or access object methods as if they were local.
- 🌊 Async Generator RPC Streaming - Stream progress, status updates, or large datasets live from worker threads with native
async *andyieldsupport. Supports backpressure and early loop breaks (break). - 🏊♂️ Dynamic Adaptive Worker Pooling - Load balance tasks across a bounded worker pool. Automatically caps background threads to system concurrency limits (
navigator.hardwareConcurrency/os.cpus()) to protect device battery and memory. - ⚡ Zero-Copy Transferables Auto-Detection - Automatically detects and transfers transferrable objects (like
ArrayBuffer, typed arrays, andMessagePort) without costly cloning overhead. - 💥 Transparent Error Boundaries - Catches worker-side errors, preserves custom fields/callstacks, and reconstructs them with a custom
[Worker Boundary]trace on the main thread. - 🧹 Explicit Resource Management - Ready for TypeScript 5.2+
usingdeclarations with native[Symbol.dispose]and.terminate()hooks. - 📦 Dual ESM & CommonJS Build - Published with native type definitions and package exports optimized for modern module systems and Node.js.
📦 Installation
npm install worker-island🚀 Quick Start & Examples
1. Standalone Function 🧮
Offload a CPU-heavy computation so it doesn't freeze the user interface.
import { workerIsland } from 'worker-island';
// 1. Define your heavy CPU logic
function heavyFibonacci(n: number): number {
if (n <= 1) return n;
return heavyFibonacci(n - 1) + heavyFibonacci(n - 2);
}
// 2. Wrap it in a workerIsland
const asyncFibonacci = workerIsland(heavyFibonacci);
// 3. Call it asynchronously from the main thread!
const result = await asyncFibonacci(42);
console.log(`Fibonacci(42) = ${result}`); // Runs on a background thread!
// Clean up when done
asyncFibonacci.terminate();2. Stateful Class Instances with Constructor Parameters 🏗️
Instantiate classes inside a background thread and keep their internal state isolated.
import { workerIsland } from 'worker-island';
class IslandCalculator {
private multiplier: number;
constructor(multiplier: number) {
this.multiplier = multiplier;
}
calculate(n: number): number {
return n * this.multiplier;
}
}
// 1. Wrap the class in workerIsland
const ActiveCalculator = workerIsland(HeavyCalculator);
// 2. Instantiate with constructor parameters
const calculator = new ActiveCalculator(5); // multiplier = 5
// 3. Methods are automatically mapped to return Promises
const result = await calculator.calculate(10);
console.log(result); // 50 (runs inside the background worker)
calculator.terminate();3. Live Progress & Streaming via Async Generators 🌊
Stream long-running processes or chunks of data sequentially from background workers using standard for await...of loops.
import { workerIsland } from 'worker-island';
class FileDownloader {
async *downloadAndParse(url: string) {
yield { status: 'Downloading', progress: 0.1 };
await new Promise(r => setTimeout(r, 500));
yield { status: 'Parsing chunk 1', progress: 0.5 };
await new Promise(r => setTimeout(r, 500));
yield { status: 'Finished', progress: 1.0 };
}
}
const RemoteDownloader = workerIsland(FileDownloader);
const downloader = new RemoteDownloader();
// Stream live progress logs from the Web Worker!
for await (const event of downloader.downloadAndParse('https://example.com/data.csv')) {
console.log(`[Main Thread] ${event.status}: ${(event.progress * 100).toFixed(0)}%`);
}
// Memory and thread contexts are fully cleaned up!
downloader.terminate();💡 Early Abort Handling: If you exit the loop early (e.g. via
break),worker-islandautomatically notifies the worker thread to clean up and terminate the underlying generator generator context, preventing leaks!
4. Zero-Copy Data Transfer (Transferables) ⚡
Avoid the overhead of structured cloning when working with large datasets like buffers.
import { workerIsland } from 'worker-island';
const processor = workerIsland({
processBuffer(buf: ArrayBuffer): number {
const view = new Uint8Array(buf);
view[0] = 42;
return buf.byteLength;
}
});
const buffer = new ArrayBuffer(1024 * 1024 * 10); // 10MB buffer
console.log(buffer.byteLength); // 10485760
// The buffer is automatically detected and transferred with zero copying
const size = await processor.processBuffer(buffer);
console.log(size); // 10485760
console.log(buffer.byteLength); // 0 (transferred, memory released on main thread!)
processor.terminate();5. Dynamic Adaptive Worker Pooling 🏊♂️
Scale concurrently without choking the machine. Setting a pool size configures a global manager that load-balances tasks across a fixed number of threads using a Least-Busy scheduling algorithm.
import { workerIsland } from 'worker-island';
// Cap active background threads to 4
workerIsland.setPoolSize(4);
class TaskProcessor {
async compute(id: number) {
// heavy computing...
return id * id;
}
}
const RemoteProcessor = workerIsland(TaskProcessor);
// Spawn 20 calculator instances
const instances = Array.from({ length: 20 }, () => new RemoteProcessor());
// Computations are automatically load-balanced across only 4 background workers!
const results = await Promise.all(
instances.map((instance, idx) => instance.compute(idx))
);
// Cleanup the pool when your app shuts down
workerIsland.clearPool();6. Explicit Resource Management (using statement) 🧹
Using TypeScript 5.2+ or modern JS runtimes, you can declare worker islands with the using keyword to automatically terminate them when they go out of scope.
import { workerIsland } from 'worker-island';
function heavyWork() {
// The worker island is automatically terminated when function scope exits!
using fibInstance = workerIsland(heavyFibonacci);
const result = await fibInstance(35);
return result;
}7. Transparent Error Boundaries 💥
If an exception occurs in a worker thread, it is serialized and re-thrown on the main thread with a combined stack trace mapping exactly where the exception was thrown in the worker.
try {
await calculator.triggerError("Bad dynamic input!");
} catch (err: any) {
console.error(err.message); // "Bad dynamic input!"
console.error(err.customCode); // 404 (custom properties are fully preserved!)
console.error(err.stack);
/*
Prints stack trace showing both:
- where it occurred inside the Web Worker
- [Worker Boundary]
- where it was captured on the main thread!
*/
}📖 API Reference
workerIsland(target)
Wraps a target function, class constructor, or plain object and returns an asynchronous proxy interface.
- Target: A function, class constructor, or object.
- Returns: An asynchronous proxy where all methods and functions return a
Promiseor anAsyncIterableIterator. - The returned proxy also implements:
.terminate(): Terminates the background worker thread.[Symbol.dispose]: Disposes of the worker when exiting a scope.
workerIsland.setPoolSize(limit: number)
Sets the maximum number of background worker threads that can be spawned globally. All proxies created afterward will share and load-balance across these threads.
workerIsland.clearPool()
Terminates and disposes of all active workers in the global pool.
🛠️ How It Works Under The Hood
- Proxy Capture: When you call
workerIsland(target), an ES6Proxyintercepts all constructor calls (new) and method calls (obj.method()). - Code Serialization: The library serializes the target functions/classes to string representations, wrapping them in a worker environment wrapper.
- Environment Agnostic Bridge: It runs worker code via browser
BlobObject URLs or Node'sworker_threadsevaluation dynamically depending on the current environment. - Zero-copy Transfer System: Before invoking
postMessage, it recursively scans arguments for Transferable objects and pipes them to the worker memory space with zero copy overhead. - Sequential Async Iterators: For generators, it pipes sequential stream yields and listens to early-abort commands to close generators on both sides safely.
📄 License
MIT © Saurabh Patel
