Skip to content

About

In-process PGlite bridge for Prisma — zero-TCP, zero-Docker PostgreSQL for testing

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

451 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

prisma-pglite-bridge

In-process PGlite bridge for Prisma. Replaces the TCP socket in pg.Client with a Duplex stream that speaks PostgreSQL wire protocol directly to PGlite's WASM engine.

Everything else is your production stack: the real pg client and the official @prisma/adapter-pg run unchanged, so tests exercise the identical adapter, wire-protocol serialization, and type-parsing code path they ship with — only the transport differs.

Install

Requires Prisma 7+ and Node.js 22+.

pnpm add -D prisma-pglite-bridge

pnpm and npm auto-install the peer dependencies — @electric-sql/pglite, @prisma/adapter-pg, and pg. In a Prisma-on-Postgres project they are already your production stack; the bridge deliberately reuses your copies rather than bundling its own.

On Yarn (which does not auto-install peers), or to keep the peers visible in your package.json so upgrades stay deliberate and bot-managed, list them explicitly:

pnpm add -D prisma-pglite-bridge @electric-sql/pglite @prisma/adapter-pg pg

TypeScript works out of the box — @prisma/adapter-pg ships pg's type declarations. Add @types/pg only if your own code imports pg directly.

Using the bridge without Prisma (drizzle, kysely, knex, typeorm, mikro-orm)? Import from prisma-pglite-bridge/pool — that entry never loads @prisma/* code at runtime. The Prisma packages still land in node_modules (the peers above plus a ~5.5 MB schema engine) until 2.0; they are install weight only on this path.

Quickstart

// tests/users.test.ts
import { PrismaClient } from '@prisma/client';
import { createBridgeTest } from 'prisma-pglite-bridge/vitest';
import { expect } from 'vitest';

const test = createBridgeTest({
  client: (adapter) => new PrismaClient({ adapter }),
  migrations: true, // applies prisma/migrations, resolved via prisma.config.ts
  seed: async (prisma) => {
    await prisma.user.create({ data: { email: '[email protected]', name: 'Ada' } });
  },
});

test('starts from the seeded snapshot', async ({ prisma }) => {
  expect(await prisma.user.count()).toBe(1);
});

One call boots an in-memory PostgreSQL, applies your migrations, runs the seed once, snapshots the result, and hands every test a prisma client restored to that snapshot. No Docker, no database server — works in GitHub Actions, GitLab CI, and anywhere Node.js runs.

  • migrations: true reads the migrations directory from prisma.config.ts (needs the optional @prisma/config peer). Pass migrations: { migrationsPath: './prisma/migrations' } to skip config resolution, or schema: { schema } with the contents of schema.prisma when there is no migrations directory yet (pushSchema).
  • Using the prisma-client generator? Import PrismaClient from your generator output path (for example ./src/generated/prisma/client.js) instead of @prisma/client.
  • Jest: setupPGliteBridge from prisma-pglite-bridge/jest takes the same options (cookbook).
  • Other runners (node:test, ava) or a vitest globalSetup: createBridgeContext, createBridgeTemplate + loadBridgeTemplate from prisma-pglite-bridge/testing — build once, load per test, cache as a file (cookbook).
  • createBridgeTest needs vitest ≥ 3.2 (fixture scopes). Every option, the scope dial ('file' / 'worker' / 'test'), and the fixtures are in the API reference.

By hand

The helper composes four building blocks you can call directly — for other test runners, a custom lifecycle, or a long-lived dev database:

import { PGliteBridge, pushMigrations } from 'prisma-pglite-bridge';
import { PrismaClient } from '@prisma/client';
import seed from './seed.ts'; // your own (prisma: PrismaClient) => Promise<void>

const bridge = new PGliteBridge();
await pushMigrations(bridge.pglite, { migrationsPath: './prisma/migrations' });

const prisma = new PrismaClient({ adapter: bridge.adapter });
await seed(prisma);
await bridge.snapshotDb(); // capture the seeded state once

beforeEach(() => bridge.resetDb()); // restore it before every test
afterAll(() => bridge.close());

Skip the snapshot/reset pair if your tests are read-only or you want state to carry over. Run prisma migrate dev first to generate migration files; for projects without a prisma/migrations directory see Populating the database.

Running the Prisma CLI against this bridge (shadow DB for migrate dev, psql, SQL GUIs), or an app in a separate process (end-to-end tests)? See PGliteServer for the TCP/Unix-socket front and the end-to-end recipe.

When a test fails, see what it did to the database

The vitest / jest helpers capture an on-failure query trail. A failing test that shows only an assertion diff now also prints the SQL it ran — every query, its params, its error — scoped to exactly that test, to stderr, on by default, zero config:

pglite-bridge query trail — 3 queries — test "creates a user with a unique email"
#0 c0 0.28ms BEGIN · BEGIN
#1 c0 0.67ms QUERY · INSERT INTO users (email, name) VALUES ($1, $2) · [[email protected], Grace]
#2 c0 1.58ms QUERY · INSERT INTO users (email, name) VALUES ($1, $2) · [[email protected], Ada II]
    ↳ error 23505: duplicate key value violates unique constraint "users_email_key"

Each line is one query: sequence number, client tag, duration, kind, SQL, params; the failing query carries its error inline. The trail is in-memory, printed only for the failing test, and never written to disk — nothing to gitignore. Passing tests print nothing.

Params are previews of your test-fixture data, printed to the same console your assertion failures already use. Where CI logs are durable and shared, pass queryTrail: { redactParams: true } to render every param as <redacted>, or set PGLITE_BRIDGE_QUERY_TRAIL=0 to turn capture off entirely. Structured access (Jest, standalone, agents) and the JSONL format are in the cookbook; options and precedence are in the API reference.

Performance

On reads, the wire-protocol path is roughly 2x faster than going through a direct PGlite driver adapter — on every latency percentile and every query shape — and ahead of a native local PostgreSQL server on both Apple Silicon and x86 with the 1.7 defaults (prepared-statement caching plus the fast query path; opt out with preparedStatements: false). Prisma findMany({ take: 100 }), 1000 iterations, PGlite 0.5.3 (reproduced on 0.5.4), measured 2026-07-06:

Machine bridge (p50 / p99) direct adapter (p50 / p99) native Postgres (p50 / p99)
Apple M3 Max 0.33ms / 0.54ms 0.88ms / 1.90ms 0.40ms / 1.05ms
Apple i9-9980HK 0.87ms / 1.73ms 2.45ms / 4.95ms 1.03ms / 2.80ms
Linux i7-8700 0.67ms / 1.01ms 1.69ms / 4.10ms 0.95ms / 2.76ms

That lead isn't an artifact of one hot query: across a nine-shape read mix (point lookups, filters, sorts, joins, count/groupBy) the bridge stays ahead on every shape and machine, and it now wins indexed interactive transactions on every machine too. The trade-offs are honest, though — give native Postgres prepared statements and the read median becomes a near-tie (native wins it outright on one of the three machines and keeps the tightest worst case everywhere), and native's faster sequential scan still takes unindexed sorts on Apple Silicon. Full tables (operation breadth, multi-shape reads, transactions, memory, cold start, where native wins) and methodology live in the benchmark suite. Reproduce with:

NODE_OPTIONS="--expose-gc" pnpm bench --scenario findmany-focused -n 1000 -w 100 -r 5

The same wire path serves other ORMs: PgBridgePool is a pg.Pool, so drizzle, kysely, knex, typeorm, and mikro-orm run their standard Postgres dialects on PGlite through prisma-pglite-bridge/pool — a Prisma-free entry — and beat their native PGlite drivers on every measured operation (query-builder p50 2.2–3.9×, typeorm 1.7–2.5×, mikro-orm 1.4–1.9×; spreads in the ORM tables). The mechanism is the same one behind the Prisma numbers: PGlite's public query() API takes a mutex and makes ~6 WASM protocol crossings per call, while the bridge issues one buffered raw-stream write. Wiring recipes and driver-agnostic testing helpers (prisma-pglite-bridge/pool/vitest, /pool/jest, and the runner-agnostic /pool/testing builders) live in the cookbook.

Test-setup cost matters as much as query speed for a suite. createBridgeTest's scope trades isolation against per-test overhead: 'file' / 'worker' reset one shared snapshot (tens of ms/test), while 'test' hands every test its own independent, test.concurrent-safe PGlite loaded from a per-file template — ~5x cheaper than a full cold start per test and far more predictable (sub-second p99 on Apple Silicon and x86, vs a cold-start tail that runs to seconds). Numbers and the pnpm bench:isolation entry point live in per-test isolation cost.

Documentation

  • API reference — exports, options, return values, fs-sync policy.
  • Cookbook — one-call Vitest / Jest setup (prisma-pglite-bridge/vitest, prisma-pglite-bridge/jest), per-test isolation, seed sharing, raw SQL next to Prisma, end-to-end via PGliteServer, running in CI, extensions, persistent dev database, clean shutdown.
  • PGliteServer — TCP / Unix-socket front for PGlite. Use for the Prisma CLI shadow database, psql, and SQL GUIs.
  • Stats and diagnostics — stats() snapshots and node:diagnostics_channel event streams.
  • Troubleshooting & limitations — known issues (PGlite version mismatch, WASM ExperimentalWarning) and runtime constraints.

For AI agents

The package ships its docs: node_modules/prisma-pglite-bridge/docs/ holds the API reference, cookbook, troubleshooting guide, PGliteServer, stats, and compatibility docs, and node_modules/prisma-pglite-bridge/AGENTS.md is a one-page map of entry points, recipes, error codes, and foot-guns written for coding agents. Point an agent at AGENTS.md first.

License

MIT

About

In-process PGlite bridge for Prisma — zero-TCP, zero-Docker PostgreSQL for testing

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages