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.

Library
npm
v2.1.2
1,588stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
60/100Good
Development Activity56
Maintenance36
Community52
Maturity56
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture78
Code Quality85
Innovation80
Learning Curve60

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 Tinypool class with a single run(task, options) entrypoint that returns a Promise resolving with the worker’s result
  • Two interchangeable worker runtimes — worker_threads (default) and child_process — selected via one constructor option
  • Zero runtime dependencies and a ~38KB install size, in contrast to much heavier alternatives
  • Support for Transferable objects and MessageChannel/MessagePort for zero-copy main-thread-to-worker communication
  • Built-in worker lifecycle controls: idleTimeout, maxMemoryLimitBeforeRecycle, isolateWorkers, and recycleWorkers() for imperative isolation
  • Cooperative task cancellation via AbortSignal plus a separate cancelPendingTasks() 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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search