npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nitro-pool

v2.0.0

Published

Run dynamic functions in parallel using process pools and worker threads — no worker files, no boilerplate, just execution.

Readme

Nitro

Installation

npm install nitro-pool

Goal

Node.js does not provide a simple way to execute dynamic tasks in isolated threads or processes.

When working with child_process or worker_threads, developers are usually required to create separate .js files and manually manage their execution. This adds friction, reduces developer experience, and often leads teams to avoid proper parallelism — even for CPU-bound or blocking tasks.

Nitro was created to solve this problem.

It provides a simple and powerful abstraction over both child_process and worker_threads, allowing you to:

  • Create multiple process pools
  • Configure the number of threads per pool
  • Execute tasks in parallel without managing worker files
  • Run tasks using simple inline functions

Instead of dealing with low-level APIs, you can just write:

await nitro.run(
  async ({ modules }) => {
    const content = await modules.fs.readFile('file.txt', 'utf-8');
    return content.toUpperCase();
  },
  {},
  {
    modules: [defineModule('fs', 'fs/promises')],
  },
);

Nitro handles the rest — process isolation, worker management, and execution.

Basic Usage

The Nitro class uses an internal Promise-based system that waits for task execution across threads and resolves or rejects based on the final result of your code.

An important detail about the function you provide to the run method is that, due to JavaScript limitations, it is completely isolated from the outer scope. This means you must explicitly inject any variables (via context) and modules you want to use inside the function.

Additionally, it is not possible to use externally declared functions — everything must be defined within the function itself.

Below is a basic usage example:

const nitro = new Nitro({
  poolMaxMemoryMb: 128, // memory limit per pool
  pools: 10, // number of pools
  threads: 2, // threads per pool (total: 20)
});

await nitro.run(
  async ({ context, modules }) => {
    const content = await modules.fs.readFile(context.path);
    return content;
  },
  {
    path: 'test.txt',
  },
  {
    modules: [defineModule('fs', 'fs/promises')],
  },
);

When executing this operation, the library automatically loads the specified module and injects it into the execution context.

All parameters passed through context and modules are fully typed, making development easier and safer.

If you try to execute more tasks than available threads, the library will automatically queue the extra tasks. Each thread processes one task at a time and continuously pulls new tasks from the queue.

const nitro = new Nitro({
  poolMaxMemoryMb: 128,
  pools: 2,
  threads: 1,
});

// Since there are only 2 threads available, one of these tasks will be queued
await Promise.all([
  nitro.run(
    async ({ context, modules }) => {
      const content = await modules.fs.readFile(context.path);
      return content;
    },
    {
      path: 'test.txt',
    },
    {
      modules: [defineModule('fs', 'fs/promises')],
    },
  ),
  nitro.run(
    async ({ context, modules }) => {
      const content = await modules.fs.readFile(context.path);
      return content;
    },
    {
      path: 'test.txt',
    },
    {
      modules: [defineModule('fs', 'fs/promises')],
    },
  ),
  nitro.run(
    async ({ context, modules }) => {
      const content = await modules.fs.readFile(context.path);
      return content;
    },
    {
      path: 'test.txt',
    },
    {
      modules: [defineModule('fs', 'fs/promises')],
    },
  ),
]);

How it works

Nitro uses a combination of child_process and worker_threads to execute tasks in parallel.

  • Each pool is a separate process
  • Each process manages multiple worker threads
  • Tasks are distributed across workers
  • A queue system ensures tasks are executed when threads become available

This design provides both isolation (process-level) and performance (thread-level).

When to use

Nitro is designed for:

  • CPU-intensive tasks
  • Blocking operations
  • Parallel data processing

It is not recommended for:

  • Simple I/O operations already handled efficiently by Node.js
  • Lightweight async flows

Best Practices

  • Keep tasks small and focused
  • Avoid heavy serialization in context
  • Reuse pools instead of creating new instances frequently
  • Prefer async functions for I/O operations

Configuration

| Option | Type | Required | Description | |---------------------------|--------|----------|-----------------------------------------------------------------------------| | pools | number | yes | Number of process pools to create | | threads | number | yes | Initial number of worker threads per pool | | poolMaxMemoryMb | number | no | Maximum memory (in MB) allowed per pool process | | logging | boolean| no | Enables internal logging | | maxPoolQueueSize | number | no | Maximum number of tasks allowed in the pool queue | | autoscaling | boolean| no | Enables automatic scaling of worker threads | | scalingInterval | number | no | Interval (in ms) between autoscaling evaluations | | minThreads | number | no | Minimum number of threads when autoscaling is enabled | | maxThreads | number | no | Maximum number of threads when autoscaling is enabled | | targetUtilization | number | no | Desired worker utilization ratio (e.g. 0.7 = 70% busy) | | scaleUpQueueThreshold | number | no | Queue size threshold to trigger scaling up | | scaleDownQueueThreshold | number | no | Queue size threshold to trigger scaling down | | maxStep | number | no | Maximum number of threads to add/remove per scaling cycle |

Defaults

When optional options are not provided, Nitro uses sensible defaults:

  • logging: false
  • autoscaling: false
  • scalingInterval: 1000ms
  • targetUtilization: 0.7
  • maxStep: 1

Autoscaling-related options are only considered when autoscaling is enabled.

Error Handling

If a task throws an error or rejects, the run method will reject the Promise with the corresponding error.

try {
  await bullet.run(...)
} catch (err) {
  console.error(err)
}

Modules

Since tasks run in an isolated environment, external modules must be explicitly injected.

modules: [defineModule('fs', 'fs/promises')];

Inside the task:

modules.fs.readFile(...)

Why this is required

Tasks are executed in a sandboxed environment and do not have access to Node.js globals like require.

Explicit module injection ensures:

  • Predictable execution
  • Better type safety
  • No hidden dependencies

Observability

Nitro provides internal metrics that can be used to monitor system performance.

Metrics include:

  • Active workers
  • Idle workers
  • Queue size
  • Average execution time
  • Average tasks per second

These metrics are used internally for autoscaling and can be exposed for monitoring purposes.

Autoscaling

Nitro includes an optional autoscaling system that dynamically adjusts the number of workers based on workload.

It evaluates:

  • Task throughput
  • Average execution time
  • Queue size

Based on these metrics, Nitro automatically scales the number of workers up or down to maintain optimal performance.

Example

const nitro = new Nitro({
  pools: 2,
  threads: 2,
  autoScale: true,
});

Autoscaling helps maintain a stable queue size and prevents both underutilization and overload.

Limitations

Due to the execution model used by Nitro, there are some important limitations:

  • Functions passed to run are fully isolated from the outer scope
  • Functions are serialized using toString()
  • External variables are not accessible unless passed via context
  • External functions cannot be referenced
  • Closures are not supported
  • Only serializable data can be passed as context
  • Dynamic imports inside the task are not supported (use modules instead)

Make sure all required logic is defined inside the function body.

Security

Tasks are executed using vm in an isolated context.

This library assumes that the code being executed is trusted. It is not designed to safely execute untrusted user input.