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

@lojhan/resource-pool

v3.0.0

Published

A generic resource pool implementation for Node.js written in TypeScript.

Readme

@lojhan/resource-pool

CI npm version License: MIT

A high-performance, zero-dependency resource pooling library for Node.js and TypeScript. Achieve 40M+ operations/sec with intelligent auto-scaling, resource validation, and built-in timeout protection.

Perfect for managing database connections, worker threads, HTTP clients, or any reusable resource with automatic lifecycle management and production-ready reliability.

Table of Contents

Features

  • Blazing Fast: 40M+ ops/sec with zero-allocation hot paths
  • 🎯 Two Pool Types: Auto-scaling ObjectPool & lightweight EnginePool (index-based)
  • 🔄 Flexible Acquisition: Sync (acquire()), async (acquireAsync()), or automatic (use())
  • 🛡️ Production Ready: Resource validation, timeout protection, automatic cleanup
  • 🏗️ Static & Dynamic: Fixed-size (min === max) or auto-scaling (min < max) pools
  • 📊 Built-In Metrics: Monitor pool utilization, pending requests, and scaling events
  • 🔒 Type-Safe: Full TypeScript support with comprehensive type inference
  • 🚀 Zero Dependencies: Pure TypeScript, no native bindings, minimal overhead

Installation

npm install @lojhan/resource-pool

Or with yarn/pnpm:

yarn add @lojhan/resource-pool
pnpm add @lojhan/resource-pool

Quick Start

import { createPool } from '@lojhan/resource-pool';

// Create a pool that auto-scales from 2 to 10 resources
const pool = createPool({
  min: 2,
  max: 10,
  resourceFactory: async () => {
    const conn = new DatabaseConnection();
    await conn.connect();
    return conn;
  },
  resourceDestroyer: async (conn) => await conn.close(),
  validateResource: async (conn) => conn.isConnected(),
});

// Automatic resource management (recommended)
const result = await pool.use(async (connection) => {
  return await connection.query('SELECT * FROM users');
});

// Or manual acquire/release
const conn = await pool.acquireAsync(5000); // 5s timeout
try {
  await conn.query('SELECT 1');
} finally {
  pool.release(conn);
}

// Cleanup
await pool.destroy();

Pool Types

ObjectPool (Resource Management)

The main pool implementation that manages resource lifecycle. Supports both fixed-size and auto-scaling configurations.

Fixed-Size Pool (min === max)

Pre-allocates all resources upfront. Best for stable, predictable workloads.

const pool = createPool({
  min: 10,
  max: 10,
  resourceFactory: () => new DatabaseConnection(),
});

Best for:

  • Known capacity requirements
  • Stable workloads
  • Minimal latency (all resources pre-created)

Dynamic Pool (min < max)

Starts with minimum resources, scales up on demand, scales down when idle.

const pool = createPool({
  min: 2,
  max: 50,
  resourceFactory: async () => new DatabaseConnection(),
  idleTimeoutMs: 30000, // Remove idle resources after 30s
  scaleDownIntervalMs: 10000, // Check every 10s
});

Best for:

  • Variable workloads
  • Traffic spikes
  • Resource-constrained environments
  • Automatic cleanup

EnginePool (Index Management)

Lightweight pool that manages slot indices instead of resources. For maximum performance and custom resource management.

import { EnginePool } from '@lojhan/resource-pool';

const workers = [new Worker('./worker.js'), new Worker('./worker.js')];
const pool = new EnginePool(workers.length);

const idx = await pool.acquireAsync();
try {
  await workers[idx].process(data);
} finally {
  pool.release(idx);
}

Best for:

  • Maximum throughput (39M+ ops/sec)
  • Pre-indexed resource arrays
  • Load shedding patterns
  • Custom resource routing

API Reference

createPool()

Creates an ObjectPool for managing resource lifecycle.

function createPool<T extends object>(
  config: {
    min?: number;
    max?: number;
    resourceFactory: (() => T) | (() => Promise<T>);
    resourceDestroyer?: (resource: T) => void | Promise<void>;
    validateResource?: (resource: T) => boolean | Promise<boolean>;

    // Timeout protection
    factoryTimeoutMs?: number; // Default: 5000
    destroyerTimeoutMs?: number; // Default: 5000
    validatorTimeoutMs?: number; // Default: 3000

    // Error handling
    bubbleFactoryErrors?: boolean; // Default: false
    bubbleDestroyerErrors?: boolean; // Default: false
    bubbleValidationErrors?: boolean; // Default: false

    // Auto-scaling (dynamic pools only)
    idleTimeoutMs?: number; // Default: 30000
    scaleDownIntervalMs?: number; // Default: 10000

    // Acquisition
    acquireTimeoutMs?: number; // Default: 0 (no timeout)
  },
  initialResources?: T[],
): IObjectPool<T>;

Configuration Options

Required Configuration

resourceFactory: () => T | Promise<T> (required)

Function that creates new resources. Can be sync or async.

// Sync factory
resourceFactory: () => new Connection();

// Async factory
resourceFactory: async () => {
  const conn = new Connection();
  await conn.connect();
  return conn;
};

Size Configuration

min?: number and max?: number

Pool size boundaries. See Validation Rules for requirements.

// Fixed-size pool (static)
{ min: 10, max: 10 }

// Dynamic pool (auto-scaling)
{ min: 2, max: 50 }

// Static pool from initialResources
{ resourceFactory, /* no min/max */ }

Optional Lifecycle Hooks

resourceDestroyer?: (resource: T) => void | Promise<void>

Called when resources are destroyed (scale-down, validation failure, or pool destruction).

resourceDestroyer: async (conn) => {
  await conn.close();
  console.log('Connection closed');
};
validateResource?: (resource: T) => boolean | Promise<boolean>

Validates resources before returning from acquireAsync(). Invalid resources are destroyed and replaced.

validateResource: async (conn) => {
  try {
    await conn.ping();
    return true; // Valid
  } catch {
    return false; // Will be replaced
  }
};

Timeout Protection

factoryTimeoutMs?: number (default: 5000)

Maximum time to wait for resource creation. Prevents hanging on slow factories.

destroyerTimeoutMs?: number (default: 5000)

Maximum time to wait for resource destruction. Prevents hanging on cleanup.

validatorTimeoutMs?: number (default: 3000)

Maximum time to wait for resource validation. Treats timeout as invalid.

{
  factoryTimeoutMs: 10000,    // 10s to create
  destroyerTimeoutMs: 5000,   // 5s to destroy
  validatorTimeoutMs: 2000,   // 2s to validate
}

Error Handling

bubbleFactoryErrors?: boolean (default: false)

Controls whether factory errors in background scale-up operations are thrown or logged.

bubbleDestroyerErrors?: boolean (default: false)

If true, errors during resource destruction are thrown. If false, errors are silently ignored.

bubbleValidationErrors?: boolean (default: false)

If true, validation errors are thrown. If false, errors are treated as invalid (return false).

{
  bubbleDestroyerErrors: true, // Throw on cleanup errors
  bubbleValidationErrors: true, // Throw on validation errors
}

Auto-Scaling (Dynamic Pools)

idleTimeoutMs?: number (default: 30000)

Duration before idle resources are destroyed. Only applies when min < max.

scaleDownIntervalMs?: number (default: 10000)

How often to check for idle resources. Only applies when min < max.

{
  min: 5,
  max: 50,
  idleTimeoutMs: 60000,       // Remove after 60s idle
  scaleDownIntervalMs: 15000, // Check every 15s
}

Acquisition Timeout

acquireTimeoutMs?: number (default: 0)

Default timeout for acquireAsync() if not specified per-call. 0 means no timeout.

{
  acquireTimeoutMs: 5000, // Default 5s timeout for all acquires
}

Pool Methods

acquire(): T | null

Synchronously acquire a resource. Returns null if none available.

const resource = pool.acquire();
if (resource) {
  // Use resource
  pool.release(resource);
} else {
  // Pool exhausted
}

acquireAsync(timeoutMs?: number): Promise<T>

Asynchronously acquire resource, waiting if necessary. Throws on timeout.

const resource = await pool.acquireAsync(5000); // 5s timeout
try {
  await resource.doWork();
} finally {
  pool.release(resource);
}

use<R>(fn: (resource: T) => R | Promise<R>, timeoutMs?: number): Promise<R>

Recommended. Automatically acquires, executes function, and releases resource (even on error).

const result = await pool.use(async (conn) => {
  return await conn.query('SELECT * FROM users');
});
// Connection released automatically

release(resource: T): void

Return resource to pool.

pool.release(resource);

destroy(): Promise<void>

Shutdown pool and destroy all resources.

await pool.destroy();

getMetrics(): PoolMetrics

Get current pool statistics.

const metrics = pool.getMetrics();
console.log({
  size: metrics.size, // Current active resources
  available: metrics.available, // Idle resources
  busy: metrics.busy, // In-use resources
  capacity: metrics.capacity, // Max capacity
  pendingCreates: metrics.pendingCreates, // Resources being created
});

EnginePool

Index-based pool for maximum performance.

import { EnginePool } from '@lojhan/resource-pool';

const pool = new EnginePool(size: number);

// Same methods as ObjectPool but returns indices
const idx: number = await pool.acquireAsync();
pool.release(idx);
await pool.use(async (idx) => { ... });

Validation Rules

createPool() enforces strict validation rules:

Required Parameters

  • resourceFactory is always required
  • If min is specified, max is required
  • If max is specified, min is required

Static Pool (no min/max)

  • When neither min nor max are provided:
    • initialResources are required
    • Pool size is initialResources.length
    • Pool is fixed-size (min === max)
// ✅ Valid static pool
createPool(
  {
    resourceFactory: () => new Connection(),
  },
  [conn1, conn2, conn3],
); // min: 3, max: 3

Size Constraints

  • min must be non-negative (>= 0)
  • max must be at least 1 (>= 1)
  • max must be >= min
  • max cannot exceed INT32_MAX (2,147,483,647)

Static Pool (min === max)

  • If initialResources are provided, length must exactly equal min
// ❌ Error: Static pool requires exactly 5 resources
createPool(
  {
    min: 5,
    max: 5,
    resourceFactory: () => new Connection(),
  },
  [conn1, conn2, conn3],
); // Only 3 provided

// ✅ Valid
createPool(
  {
    min: 5,
    max: 5,
    resourceFactory: () => new Connection(),
  },
  [conn1, conn2, conn3, conn4, conn5],
); // Exactly 5

Initial Resources

  • Cannot exceed max capacity
// ❌ Error: 10 resources exceed max of 5
createPool(
  {
    min: 2,
    max: 5,
    resourceFactory: () => new Connection(),
  },
  tenConnections,
); // 10 resources

// ✅ Valid

createPool(
  {
    min: 2,
    max: 5,
    resourceFactory: () => new Connection(),
  },
  [conn1, conn2, conn3],
); // 3 resources OK

Dynamic Pool (min < max)

  • min: 0 is allowed for lazy/on-demand pools
  • initialResources are optional
// ✅ Valid: Lazy pool
createPool({
  min: 0,
  max: 10,
  resourceFactory: () => new Connection(),
}); // Starts with 0 resources, scales up on demand

Examples

Database Connection Pool

import { createPool } from '@lojhan/resource-pool';
import { Client } from 'pg';

const pool = createPool({
  min: 5,
  max: 20,
  resourceFactory: async () => {
    const client = new Client({
      host: 'localhost',
      database: 'mydb',
    });
    await client.connect();
    return client;
  },
  resourceDestroyer: async (client) => {
    await client.end();
  },
  validateResource: async (client) => {
    try {
      await client.query('SELECT 1');
      return true;
    } catch {
      return false;
    }
  },
  idleTimeoutMs: 60000,
  validatorTimeoutMs: 2000,
});

// Use in your application
async function getUser(id: number) {
  return pool.use(async (client) => {
    const result = await client.query('SELECT * FROM users WHERE id = $1', [id]);
    return result.rows[0];
  });
}

// Cleanup on shutdown
process.on('SIGINT', async () => {
  await pool.destroy();
  process.exit(0);
});

Worker Thread Pool

import { createPool } from '@lojhan/resource-pool';
import { Worker } from 'worker_threads';

const pool = createPool({
  min: 4,
  max: 8,
  resourceFactory: () => new Worker('./worker.js'),
  resourceDestroyer: async (worker) => {
    await worker.terminate();
  },
  factoryTimeoutMs: 10000,
});

async function processTask(data: any) {
  return pool.use(async (worker) => {
    return new Promise((resolve, reject) => {
      worker.once('message', resolve);
      worker.once('error', reject);
      worker.postMessage(data);
    });
  });
}

HTTP Client Pool with Validation

import { createPool } from '@lojhan/resource-pool';
import fetch from 'node-fetch';

interface HTTPClient {
  fetch: typeof fetch;
  lastUsed: number;
}

const pool = createPool({
  min: 2,
  max: 10,
  resourceFactory: () => ({
    fetch,
    lastUsed: Date.now(),
  }),
  validateResource: (client) => {
    // Invalidate clients older than 5 minutes
    return Date.now() - client.lastUsed < 5 * 60 * 1000;
  },
  idleTimeoutMs: 120000,
});

async function makeRequest(url: string) {
  return pool.use(async (client) => {
    client.lastUsed = Date.now();
    const response = await client.fetch(url);
    return response.json();
  });
}

Load Shedding with EnginePool

import { EnginePool } from '@lojhan/resource-pool';
import { Worker } from 'worker_threads';

const workers = Array.from({ length: 4 }, () => new Worker('./worker.js'));
const pool = new EnginePool(workers.length);

async function processWithLoadShedding(task: any) {
  // Fast-fail if no workers available
  const idx = pool.acquire();
  if (idx === null) {
    throw new Error('SERVICE_OVERLOADED');
  }

  try {
    return await new Promise((resolve, reject) => {
      workers[idx].once('message', resolve);
      workers[idx].once('error', reject);
      workers[idx].postMessage(task);
    });
  } finally {
    pool.release(idx);
  }
}

// Health check
function getHealth() {
  const metrics = pool.getMetrics();
  return {
    utilization: (metrics.busy / metrics.capacity) * 100,
    available: metrics.available,
  };
}

Static Pool with Pre-created Resources

import { createPool } from '@lojhan/resource-pool';

// Pre-create expensive resources
const connections = await Promise.all(
  Array.from({ length: 5 }, async () => {
    const conn = new ExpensiveConnection();
    await conn.initialize();
    return conn;
  }),
);

// Create static pool from existing resources
const pool = createPool(
  {
    resourceFactory: () => new ExpensiveConnection(), // Fallback (not called if static)
    resourceDestroyer: async (conn) => await conn.close(),
  },
  connections, // Exactly 5 resources, pool is min: 5, max: 5
);

// All resources are immediately available
const conn = pool.acquire(); // Never null in static pool
if (conn) {
  // Use connection
  pool.release(conn);
}

Benchmarks

Performance on modern hardware (Apple M1 Pro):

| Library | acquire/release | .use() pattern | vs generic-pool | | :----------------------- | --------------: | -------------: | :-------------- | | ObjectPool (Dynamic) | 48.1M ops/sec | 11.7M ops/sec | 25x faster | | ObjectPool (Static) | 41.6M ops/sec | 12.7M ops/sec | 22x faster | | EnginePool | 39.2M ops/sec | 13.1M ops/sec | 21x faster | | generic-pool | 1.9M ops/sec | 1.7M ops/sec | baseline | | tarn | 0.9M ops/sec | 0.9M ops/sec | 0.5x |

Dynamic vs Static: Dynamic pools (min < max) allow auto-scaling, while static pools (min === max) have fixed size.

Comparison summary:

  • generic-pool: ~1.9M ops/sec
  • tarn: ~0.9M ops/sec
  • @lojhan/resource-pool: 40-48M ops/sec

Run benchmarks locally:

cd benchmarks
npm install
npm run bench

TypeScript Support

Full TypeScript support with comprehensive type inference.

import { createPool, type IObjectPool, type PoolMetrics } from '@lojhan/resource-pool';

interface DatabaseConnection {
  query(sql: string): Promise<any>;
  close(): Promise<void>;
}

// Type-safe pool
const pool: IObjectPool<DatabaseConnection> = createPool({
  min: 5,
  max: 10,
  resourceFactory: async (): Promise<DatabaseConnection> => {
    const conn = new DatabaseConnection();
    await conn.connect();
    return conn;
  },
});

// Type inference works automatically
const result = await pool.use(async (conn) => {
  // conn is typed as DatabaseConnection
  return await conn.query('SELECT 1');
});

// Metrics are typed
const metrics: PoolMetrics = pool.getMetrics();
console.log(metrics.size, metrics.available, metrics.busy);

Best Practices

Always Use use() Method

// ❌ BAD: Prone to leaks if error occurs
const resource = await pool.acquireAsync();
await doSomething(resource);
pool.release(resource);

// ✅ GOOD: Guaranteed release
await pool.use(async (resource) => {
  await doSomething(resource);
});

Implement Resource Validation

const pool = createPool({
  min: 5,
  max: 10,
  resourceFactory: async () => createConnection(),
  validateResource: async (conn) => {
    try {
      await conn.ping();
      return true;
    } catch {
      return false; // Will be replaced
    }
  },
  validatorTimeoutMs: 2000,
});

Set Reasonable Timeouts

const pool = createPool({
  min: 5,
  max: 10,
  resourceFactory: async () => createConnection(),
  factoryTimeoutMs: 10000, // 10s to create
  destroyerTimeoutMs: 5000, // 5s to destroy
  validatorTimeoutMs: 2000, // 2s to validate
  acquireTimeoutMs: 5000, // 5s default acquire timeout
});

Monitor Pool Metrics

setInterval(() => {
  const metrics = pool.getMetrics();
  console.log({
    utilization: (metrics.busy / metrics.capacity) * 100,
    available: metrics.available,
    pending: metrics.pendingCreates,
  });
}, 10000);

Graceful Shutdown

async function shutdown() {
  console.log('Shutting down pool...');
  await pool.destroy();
  console.log('Pool destroyed');
  process.exit(0);
}

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);

Troubleshooting

Pool Exhaustion / Timeouts

Symptoms: acquireAsync() times out frequently

Solutions:

  1. Increase max pool size
  2. Check for resource leaks (not releasing resources)
  3. Reduce acquireTimeoutMs to fail faster
  4. Implement load shedding with EnginePool

Resources Not Scaling Down

Symptoms: Pool stays at max size even when idle

Solutions:

  1. Check idleTimeoutMs is set (default: 30000)
  2. Verify min < max (only dynamic pools scale)
  3. Check scaleDownIntervalMs (default: 10000)

Validation Failures

Symptoms: Frequent resource replacements

Solutions:

  1. Check validateResource logic is correct
  2. Increase validatorTimeoutMs if validation is slow
  3. Monitor metrics for pendingCreates spikes

Memory Leaks

Symptoms: Memory usage grows over time

Solutions:

  1. Ensure resourceDestroyer properly cleans up
  2. Always use pool.use() or try/finally with manual acquire
  3. Call pool.destroy() on shutdown

Contributing

Contributions welcome! See CONTRIBUTING.md

npm install
npm test
npm run bench

License

MIT © Lojhan