Why Familiar?
Raw Web Workers require blob-URL boilerplate, untyped postMessage/onmessage pairs, and no built-in pooling, timeouts, or cancellation.
ts
// Before — raw Web Worker
const blob = new Blob([`onmessage = (e) => postMessage(e.data * 2);`]);
const rawWorker = new Worker(URL.createObjectURL(blob));
rawWorker.postMessage(21);
rawWorker.onmessage = (e) => console.log(e.data); // 42 — untyped, no await, no error handling
// After — familiar
import { createWorker, task } from '@vielzeug/familiar';
const double = task<number, number>((n) => n * 2);
const typedWorker = createWorker(double);
console.log(await typedWorker.run(21)); // 42 — typed, awaitable, error-safe
typedWorker.dispose();| Feature | Worker | Comlink | workerpool |
|---|---|---|---|
| Bundle size | 4.1 KB | ~2 kB | ~10 kB |
| Worker pools | |||
| Typed payloads | Partial | ||
| Timeout support | |||
| Priority queue | |||
| AbortSignal | |||
| Streaming | runStream() | ||
| Heartbeat | |||
| Typed errors | instanceof WorkerTimeoutError etc. | ||
| Testing utilities | |||
| Module workers | createModuleWorker | ||
| Zero dependencies |
Use Worker when you need typed, awaitable Web Workers with pooling, priorities, timeouts, streaming, and cancellation.
Consider Comlink if you only need a simple typed RPC proxy over a single Worker without pooling, priority, or timeout requirements.
Installation
sh
pnpm add @vielzeug/familiarsh
npm install @vielzeug/familiarsh
yarn add @vielzeug/familiarQuick Start
ts
import { createWorker, task, WorkerTimeoutError, WorkerQueueFullError } from '@vielzeug/familiar';
// Wrap the function with task() to mark it as self-contained (safe to serialize)
const sum = task<number[], number>((nums) => nums.reduce((a, b) => a + b, 0));
// Single worker — processes one task at a time
const worker = createWorker(sum);
console.log(await worker.run([1, 2, 3, 4, 5])); // 15
worker.dispose();
// Worker pool — 4 concurrent slots with a timeout
const upper = task<string, string>((text) => text.toUpperCase());
const pool = createWorker(upper, { concurrency: 4, timeout: 5000 });
const items = ['alpha', 'beta', 'gamma', 'delta'];
const results = await Promise.all(items.map((item) => pool.run(item)));
pool.dispose();
// Priority — higher values run first when tasks queue up
await pool.run(urgentTask, { priority: 10 });
// Typed errors — precise instanceof checks
try {
await pool.run(input, { timeout: 100 });
} catch (err) {
if (err instanceof WorkerTimeoutError) console.error(`Timed out after ${err.timeoutMs}ms`);
if (err instanceof WorkerQueueFullError) console.error(`Queue full (max ${err.maxQueue})`);
}Features
- Type-safe — payload types flow from
TaskFndeclaration to everyrun()call - Web Worker backed — CPU-bound work runs off the main thread, no jank
- Pool support — create N workers via the
concurrencyoption with built-in queuing - Priority queue — pass
priorityper-run; higher values run first with FIFO tiebreaking - Timeout support — pool-level or per-run
timeoutrejects withWorkerTimeoutError - Heartbeat monitoring —
heartbeatTimeoutkills tasks that stop responding, with auto-heartbeats for inline workers - AbortSignal — cancel queued tasks with the standard
AbortControllerAPI - Streaming —
runStream()for tasks that yield multiple partial results - Batch —
batch()runs inputs through the pool and yields results ordered or as-completed - Task groups —
group()ties related tasks to a shared abort and drain lifecycle - Transferables — move large buffers to the Worker without a structured-clone copy
- Prime — pre-initialize worker slots to eliminate first-task latency
- Metrics —
active,queued,utilization,completed,failedcounters for observability - Typed error hierarchy —
WorkerTimeoutError,WorkerTaskError,WorkerQueueFullError, and more [Symbol.dispose]—usingkeyword support (ES2025 explicit resource management)- Module workers —
createModuleWorkerloads a real.js/.tsmodule file as the Worker - Testing utilities —
createTestWorkerruns tasks in-process with call recording - Zero dependencies — no supply chain risk, minimal bundle size