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

lambda-pool

v0.1.0

Published

Serverless-safe connection-pool options for MySQL (mysql2) and Postgres (pg) on Vercel/Lambda with small max_connections (Aiven, Neon, RDS micro). Zero runtime dependencies.

Readme

lambda-pool

npm version license types zero deps

Serverless-safe connection-pool options for MySQL (mysql2) and Postgres (pg) on Vercel / AWS Lambda / Cloudflare-to-DB, where the database has a small max_connections budget (Aiven free tier, Neon, Supabase, RDS micro, PlanetScale).

It returns a plain options object you pass to your own driver. Zero runtime dependencies. It does not open connections, wrap your driver, or pin a version.

npm install lambda-pool

The bug this prevents

On Vercel/Lambda every warm function instance keeps its own pool. If each pool opens N connections and the platform keeps M instances warm, your database sees up to N × M connections.

Managed databases on small plans cap max_connections very low (often 10–25). So a perfectly reasonable-looking connectionLimit: 10 blows the budget under mild traffic and you get:

  • MySQL: ER_CON_COUNT_ERROR: Too many connections
  • Postgres: sorry, too many clients already

…intermittently, only in production, only under load. The classic "works on my machine, dies on Black Friday" footgun.

The fix (and why it's counter-intuitive)

Make each pool tiny — default 1 connection per instance. The platform's horizontal scaling is your concurrency; the database stops melting. Idle instances release their slot so they don't sit on the budget.

That's the whole idea. This package just encodes the right defaults so you don't have to rediscover them in an incident.

Usage

MySQL (mysql2)

import mysql from "mysql2/promise";
import { buildMysqlPoolOptions } from "lambda-pool/mysql";

const pool = mysql.createPool(buildMysqlPoolOptions(process.env));

Postgres (pg)

import { Pool } from "pg";
import { buildPgPoolOptions } from "lambda-pool/pg";

const pool = new Pool(buildPgPoolOptions(process.env));

Both also re-exported from the root: import { buildPgPoolOptions } from "lambda-pool".

Diagnostics — lint your connection for serverless

Beyond building options, lambda-pool can analyze a connection config and tell you whether it will survive serverless fan-out. Pure function, no I/O:

import { inspectEnv, formatReport } from "lambda-pool";

const report = inspectEnv(process.env);
console.log(formatReport(report));
// lambda-pool diagnostics for mysql://u:***@pg.aivencloud.com/db [aiven]
//   ⚠ SMALL_MAX_CONNECTIONS: Aiven typically caps max_connections around 20; keep the per-instance pool at 1.

It recognizes the provider from the host (Aiven, Neon, Supabase, PlanetScale, RDS/Aurora, Railway, Render, Vercel Postgres) and warns about: pool sizes too large for the provider's budget, SSL requested in the URL with no CA supplied, and using a direct host when a pooled endpoint is available.

CLI

# Lint the connection in your env (exit 1 on any warning — good as a CI gate)
DATABASE_URL=postgres://… npx lambda-pool inspect

# Full preflight check (config + diagnostics) — strict gate, exit 1 on any issue
DATABASE_URL=postgres://… npx lambda-pool doctor

# Recommend a pool size straight from a connection URL (detects the provider)
npx lambda-pool recommend "postgres://…@db.aivencloud.com/app" 10

# Recommend from raw numbers: max_connections, instances, [reserved], [other]
npx lambda-pool budget 100 20
#   recommended pool limit: 4
#   97 usable connections (100 max − 3 reserved − 0 other) ÷ 20 instances → pool of 4 per instance (peak 80).

# List recognized providers
npx lambda-pool providers

Budget calculator (programmatic)

import { recommendPoolLimit } from "lambda-pool";

const { recommendedPoolLimit, exceedsBudget, rationale } = recommendPoolLimit({
  maxConnections: 20,   // your DB's max_connections
  expectedInstances: 30, // peak warm serverless instances
});
// recommendedPoolLimit: 1, exceedsBudget: true → use a pooler

Connection-string utilities

import { parseConnectionString, redactUrl } from "lambda-pool";

redactUrl("postgres://u:secret@host/db"); // → "postgres://u:***@host/db"
parseConnectionString("mysql://[email protected]/test").port; // → 3306

Health checks

Driver-agnostic readiness probe. Works with any pool that has a query() method (both mysql2 and pg do), so lambda-pool stays dependency-free:

import { checkHealth } from "lambda-pool";

const { healthy, latencyMs, error } = await checkHealth(pool, { timeoutMs: 2000 });
// use in a /healthz endpoint — never throws

Retry transient connect errors

Cold starts and brief failovers cause transient connect errors. withRetry wraps an operation with jittered exponential backoff:

import { withRetry, isTransientDbError } from "lambda-pool";

const client = await withRetry(() => pool.connect(), {
  attempts: 4,
  retryable: isTransientDbError, // don't retry auth failures
});

Preflight (startup gate)

Run a single check at boot that combines config validation, diagnostics, and an optional live reachability probe. The probe is injected, so the check stays pure unless you opt into a real connection:

import { preflight } from "lambda-pool";
import { isReachable } from "lambda-pool/health";

const result = await preflight(process.env, {
  probe: () => isReachable(pool), // optional live check
});
if (!result.ok) {
  console.error(result.diagnostics);
  process.exit(1);
}

Redact objects for logging

redactUrl masks a connection string; redact masks whole objects (config dumps, error context) by key name, so secrets never reach your log aggregator:

import { redact } from "lambda-pool";

redact({ host: "db", password: "p", nested: { token: "t" } });
// → { host: "db", password: "***", nested: { token: "***" } }

Build a connection string from parts

import { buildDsn } from "lambda-pool";

buildDsn({ engine: "postgres", host: "db", user: "u", password: "p/w", database: "app" });
// → "postgres://u:p%2Fw@db:5432/app"  (credentials safely encoded)

Result type

Non-throwing variants return a small Result you can branch on:

import { safeParseConnectionString } from "lambda-pool";

const r = safeParseConnectionString(process.env.DATABASE_URL ?? "");
if (!r.ok) { /* handle r.error */ } else { /* use r.value */ }

API surface

| Import | Exports | |---|---| | lambda-pool/mysql | buildMysqlPoolOptions | | lambda-pool/pg | buildPgPoolOptions | | lambda-pool/url | parseConnectionString, safeParseConnectionString, redactUrl, urlRequestsSsl | | lambda-pool/providers | detectProvider, listProviders, isPooledEndpoint | | lambda-pool/budget | recommendPoolLimit | | lambda-pool/diagnostics | diagnose, formatReport | | lambda-pool/recommend | recommendForUrl | | lambda-pool/preflight | preflight | | lambda-pool/config | loadPoolConfig | | lambda-pool/dsn | buildDsn | | lambda-pool/redact | redact | | lambda-pool/health | checkHealth, isReachable | | lambda-pool/retry | withRetry, backoffDelay, isTransientDbError | | lambda-pool/result | Result, ok, err, attempt, unwrap |

All of the above are also re-exported from the package root.

Environment variables

| Variable | Purpose | Default | |---|---|---| | DATABASE_URL | Connection URI. Aliases: MYSQL_URL / POSTGRES_URL / PG_URL. | required | | DATABASE_POOL_LIMIT | Per-instance pool size. Raise only behind a pooler (PgBouncer, Neon pooled endpoint) or on a bigger plan. | 1 | | DATABASE_SSL_CA_BASE64 | Base64 of your provider's CA cert → enables strict TLS. | off |

A non-positive or non-numeric DATABASE_POOL_LIMIT is ignored and falls back to the default, so a bad env var can never silently widen the pool.

What the defaults are

MySQL (mysql2 PoolOptions): connectionLimit: 1, maxIdle: 1, idleTimeout: 30000, enableKeepAlive: true, waitForConnections: true, queueLimit: 0.

Postgres (pg PoolConfig): max: 1, idleTimeoutMillis: 30000, connectionTimeoutMillis: 10000, maxUses: 7500, allowExitOnIdle: true.

Override the default size in code without an env var:

buildPgPoolOptions(process.env, { defaultPoolLimit: 2 }); // behind a pooler

A note on TLS

Managed providers hand you a CA bundle, but the URI's ?ssl-mode=REQUIRED / ?sslmode=require param is not interpreted by mysql2 / pg the way people expect. Pass the CA explicitly via DATABASE_SSL_CA_BASE64 and the server is verified (rejectUnauthorized: true).

Architecture

The source is organized into strict clean-architecture layers (core → application → adapters → presentation) with the dependency rule enforced in CI. See ARCHITECTURE.md.

Zero runtime dependencies

lambda-pool imports nothing at runtime. mysql2 / pg are your dependency and only devDependencies here (for tests/types). You stay in control of the driver version.

Development

npm install
npm run typecheck
npm test            # hermetic unit tests, no DB needed
npm run build

Integration tests run the option objects against real mysql2 / pg pools, using throwaway containers whose max_connections is pinned to 10 to mirror a free tier:

docker compose -f docker-compose.test.yml up -d
RUN_DB_TESTS=1 npm run test:integration
docker compose -f docker-compose.test.yml down -v

License

MIT