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

@howells/neon

v0.1.1

Published

Hardened Neon Postgres client layer: resilient fetch, write-safe retries, IPv4-first, HMR-safe pools, endpoint guards.

Readme

@howells/neon

Hardened Neon Postgres client layer. One package encoding every fix the portfolio learned the hard way: resilient fetch, write-safe retries, IPv4-first DNS, HMR-safe pools, and pooled/direct endpoint guards.

pnpm add @howells/neon

Drivers are optional peer dependencies — install only what the subpath you use needs: @neondatabase/serverless for /http, pg for /pool and /mastra, drizzle-orm for the factories, drizzle-kit for /kit.

Why

A survey of 19 Neon-backed repos found the same six failure classes fixed ad hoc, over and over:

  1. IPv6 happy-eyeballs stall — Neon publishes AAAA records some networks can't route to AWS; Node races a dead IPv6 connection and stalls ~14s.
  2. Autosuspend cold starts vs. tight timeouts — Neon suspends idle computes and terminates their connections; Postgres' 5s default connect timeout can't survive the wake.
  3. HMR pool leaksnext dev re-evaluates db modules; module-level singletons leak a fresh pool per reload until the connection limit dies.
  4. Idle-drop errors killing the process — a dropped idle pg client emits 'error'; unhandled, it takes the process down.
  5. Transient fetch failed — the HTTP driver is one fetch per query with no retry.
  6. Endpoint misuse — apps stacking pools on the direct endpoint exhaust its compute-sized connection limit; migrations through PgBouncer hit transaction-mode gotchas.

This package is those fixes, versioned.

Quickstart

App data access (default) — @howells/neon/http

import { createHttpDb } from "@howells/neon/http";
import * as schema from "./schema";

export const db = createHttpDb({ url: databaseUrl, schema });
export type Db = typeof db;

Stateless HTTP driver with a retrying fetch installed, IPv4-first DNS, and a cached instance. Pass the pooled (-pooler) URL — the factory asserts it.

Sessions and transactions — @howells/neon/pool

import { createPooledDb } from "@howells/neon/pool";
import * as schema from "./schema";

export const db = createPooledDb({ url: databaseUrl, schema });

Hardened pg pool: max 5, 15s connect timeout (cold starts), 20s idle timeout (release before Neon drops server-side), keepalive, an error listener so idle drops can't kill the process, and globalThis caching so HMR reuses one pool. On Vercel Fluid, pass onPoolCreated: attachDatabasePool from @vercel/functions — unless the code relies on SET LOCAL continuity, which it breaks.

Migrations — @howells/neon/kit

// drizzle.config.ts
import { neonKitConfig } from "@howells/neon/kit";

export default neonKitConfig({
  directUrl: process.env.DIRECT_DATABASE_URL ?? "",
  schema: "./packages/db/src/schema.ts",
});

Asserts the direct endpoint — never push schema through PgBouncer.

Mastra storage — @howells/neon/mastra

import { createMastraPool, mastraPoolOptions } from "@howells/neon/mastra";

const storage = new PostgresStore({
  id: "app-storage",
  pool: createMastraPool({ url }),
});
const vectors = new PgVector({
  connectionString: url,
  pgPoolOptions: mastraPoolOptions({ url }),
});

One config, two pools: PgVector accepts no injected pool, so both derive from the same resolver and cannot drift. max is clamped ≥ 2 — a single-client pool deadlocks @mastra/pg batch writes. @mastra/pg silently drops connectionTimeoutMillis from pgPoolOptions-style config, which is why createMastraPool builds the pool itself.

Lint enforcement — @howells/neon/lint

import { createOxlintConfig } from "@howells/neon/lint";

Bans drizzle-orm/neon-serverless and Pool/Client from @neondatabase/serverless via no-restricted-imports. Stopgap until the @howells/lint fleet preset carries the same rules.

createOxlintConfig({ restrictPgOutsideDb: true }) additionally bans bare pg imports everywhere except the packages that own a direct Postgres connection (pgAllowedIn, default packages/db/** and packages/mastra/**). The carve-out is a positive-glob override rather than negated globs because oxlint ignores !-negation in override files arrays. Overrides replace the top-level rule config for matching files, so if your own oxlint config declares no-restricted-imports in an override that also matches those packages, merge the fragment's paths rather than clobbering them.

Retry safety — read this before touching the knobs

The default retry matcher (isConnectionError) is narrow: it only retries failures where the request provably never reached the server (refused, unresolved DNS, connect-phase timeout). That makes retries safe for writes by construction.

The broad matcher (isTransientNeonError) also retries mid-flight drops (fetch failed, ECONNRESET) — where the server may already have executed the statement. Retrying a non-idempotent write through it can double-apply. Use it only via retryDbRead, or pass it to createResilientFetch only when the workload is read-only or idempotent.

Neither matcher ever retries a real query failure: a non-connection SQLSTATE (constraint violation, syntax error) is authoritative and refuses retry regardless of what the message contains.

installResilientFetch is process-global and first-writer-winsneonConfig.fetchFunction is a driver-level singleton, so per-instance retry tuning is deliberately not offered.

Driver decision table

| Situation | Use | | --- | --- | | Next.js app code, serverless, Workers, scripts | /http | | Interactive transactions, LISTEN/NOTIFY, long-running services, batch work | /pool | | Schema push / migrations | /kit + direct URL | | Mastra PostgresStore / PgVector | /mastra | | The Neon WebSocket driver (neon-serverless) | Don't. Idle-socket drops and HMR leaks; the one documented exception is edge runtimes that genuinely need the WS pool (neonConfig.poolQueryViaFetch). |

Caching

Factories cache on globalThis under a versioned Symbol.for key, which survives both next dev HMR and the ESM/CJS dual-package hazard. Entries are never evicted — keys must be low-cardinality. Never derive name or url from per-tenant/dynamic values, or each distinct key pins a pool for the life of the process. Call closeAll() in test teardown so cached pools don't hang vitest.

For /http the cache is an ergonomics nicety (the HTTP driver holds no connections — per-request clients are officially fine). For /pool it is load-bearing: it's the fix for the HMR pool-leak death spiral.

Environment contract

This package never reads process.env. Bring your own URLs — pair with @howells/envy:

  • DATABASE_URL — pooled (-pooler) host, for /http, /pool, /mastra
  • DIRECT_DATABASE_URL — direct host, for /kit and admin tooling

Recommended connection-string params (or use withNeonParams): sslmode=verify-full&channel_binding=require&connect_timeout=15.

Per-environment branch selection (a dev Neon branch's URL in local .env, production's in Vercel) is env-layer work, not client-layer — do it in your envy schema, then hand the resolved URL here.

Compatibility

| drizzle-orm | Factories (/http, /pool) | Primitives (.) | | --- | --- | --- | | ^0.45 | ✅ | ✅ | | 1.0.0-rc (relational v2) | ❌ use primitives + your own drizzle({ client, relations }) | ✅ |

The 0.45 peer range is a stopgap: when drizzle 1.x GAs and the fleet moves, the factories grow a real 1.x branch.

Non-goals

No process.env reads. No schema or migration opinions beyond /kit's direct-URL enforcement. No query builders. No WebSocket-driver support. No logging framework — injectable callbacks only.