tinypool
A minimal, dependency-free worker thread pool for Node.js that offloads CPU-bound work without the install-size overhead of larger pool libraries.
Repository Health
Technical Analysis
Tinypool is a friendly fork of Piscina built to strip out the features and dependencies that its primary consumer, Vitest, doesn’t need. It gives Node.js applications a worker pool for running CPU-bound or blocking tasks off the main thread, backed by either node:worker_threads or node:child_process, while keeping the install footprint down to roughly 38KB versus Piscina’s much larger size.
The library exposes a small, promise-based API: construct a Tinypool pointed at a worker file, call run() with task data, and the pool handles worker lifecycle, queuing, and result delivery. It supports advanced use cases like transferable objects, MessageChannel/MessagePort communication between the main thread and workers, worker recycling for memory-leak mitigation, task cancellation via AbortSignal, and pluggable task queues, all without pulling in any runtime dependencies of its own.
What You Get
- A
Tinypoolclass with a singlerun(task, options)entrypoint that returns a Promise resolving with the worker’s result - Two interchangeable worker runtimes —
worker_threads(default) andchild_process— selected via one constructor option - Zero runtime dependencies and a ~38KB install size, in contrast to much heavier alternatives
- Support for
Transferableobjects andMessageChannel/MessagePortfor zero-copy main-thread-to-worker communication - Built-in worker lifecycle controls:
idleTimeout,maxMemoryLimitBeforeRecycle,isolateWorkers, andrecycleWorkers()for imperative isolation - Cooperative task cancellation via
AbortSignalplus a separatecancelPendingTasks()for graceful queue draining
Common Use Cases
- Running a test framework’s test files in isolated worker threads or processes (Tinypool’s original use case inside Vitest)
- Offloading CPU-intensive computation (image processing, data transformation, parsing) so it doesn’t block the Node.js event loop
- Isolating untrusted or crash-prone code in disposable workers that can be recycled without taking down the main process
- Building a task-processing service that needs bounded concurrency across a fixed pool of worker threads or child processes
- Replacing a heavier worker-pool dependency in projects where install size and dependency count matter (CLIs, serverless functions)
Under The Hood
Architecture
A ThreadPool class manages a resource pool of WorkerInfo instances through an AsynchronouslyCreatedResourcePool that tracks pending vs. ready workers, dispatching tasks to either ThreadWorker (node:worker_threads) or ProcessWorker (node:child_process) behind a shared TinypoolWorker interface (src/runtime/thread-worker.ts, src/runtime/process-worker.ts). The public Tinypool class extends EventEmitterAsyncResource and wraps ThreadPool, exposing run(), destroy(), and recycleWorkers(). Task lifecycle is modeled by TaskInfo (extending AsyncResource for async-hook visibility) and queued through a pluggable TaskQueue interface (default ArrayTaskQueue), while entry scripts (src/entry/worker.ts, src/entry/process.ts) run inside the spawned worker/process and communicate back through shared message-protocol constants defined in common.ts, using SharedArrayBuffer atomics when useAtomics is enabled. The layering (public API / pool orchestration / worker-runtime abstraction / entry protocol) is coherent, though most orchestration logic lives in one large index.ts file, so changing the core WorkerInfo/ThreadPool abstraction would touch nearly everything downstream.
Tech Stack
Written in TypeScript (98% of the codebase) targeting Node’s own worker_threads, child_process, async_hooks, perf_hooks, fs, and os modules, with zero runtime dependencies declared in package.json. It builds via tsdown (an esbuild-based bundler) into a pure ESM dist/ output for Node 18/20/22, is tested with Vitest — the same tool Tinypool exists to serve — linted with ESLint 9’s flat config plus typescript-eslint and eslint-plugin-unicorn, and is released through clean-publish with GitHub Actions running the test matrix across Ubuntu, macOS, and Windows.
Code Quality
The test/ directory holds a substantial suite of dedicated spec files covering async context propagation, atomics, idle timeouts, worker isolation, task movement, resource limits, task queues, teardown hooks, and abrupt termination — a strong signal of deliberate edge-case coverage rather than happy-path-only testing. The code favors explicit TypeScript interfaces (Options, FilledOptions, RunOptions, TaskQueue) over loosely-typed data flow, uses private class fields for encapsulation, and models failure states as dedicated AbortError/CancelError classes instead of generic throws. ESLint runs with --max-warnings=0 and CI exercises the full OS/Node-version matrix on every push and pull request.
API Design
The public surface is intentionally small: one Tinypool class, typed constructor Options, a single primary run(task, options?) method, and a handful of secondary methods (destroy(), cancelPendingTasks(), recycleWorkers()) that map directly onto real operational needs instead of a sprawling configuration surface. Naming stays consistent with Piscina, the project it forked from, so existing Piscina users can adopt it with minimal relearning, and worker files use a plain default-export function with no required boilerplate or decorators. Advanced capabilities — Tinypool.move() for transferables, custom TaskQueue implementations, AbortSignal cancellation — are opt-in layers on top of the basic case, and the README documents every constructor option and API method directly rather than deferring to generated API docs.