A friendly playground for building and validating PostgreSQL modules — Row-Level Security, triggers, and pgvector similarity search — entirely in-process, with no Postgres server. It includes real-world examples, pgpm migrations, and a test suite you can run anywhere Node runs.
Built with pglite-test — a drop-in pgsql-test-style getConnections() backed by an in-process PGlite (WASM Postgres) instance instead of a server. No createdb, no psql, no Docker: one WASM Postgres session per suite, with the same isolated per-test rollbacks.
- 🚀 Zero infrastructure - No Postgres server, no Docker, no Supabase CLI.
pnpm install && pnpm test. - ⚡ Instant, isolated test DBs - Each suite gets a fresh in-process PGlite; per-test savepoint rollbacks.
- 🔐 RLS policy testing - Real role + JWT-claim context switching (
setContext) on a single WASM session. - 🧠 pgvector - Cosine-distance similarity search via a WASM extension, no native build.
- 🧩 Modular pgpm packages - Reusable database modules deployed via
seed.pgpm(), same plans as a server. - 🧪 Jest - Fast, ordinary Jest — the only requirement is
NODE_OPTIONS=--experimental-vm-modules.
# Install dependencies (that's the whole setup — no database to start)
pnpm install
# Run everything
pnpm test
# Or watch a single package
cd packages/hello-world
pnpm test:watchThis is a pgpm workspace combining pnpm and pgpm for modular Postgres packages:
packages/hello-world- Self-contained RLS demo (apetstable whose rows are owned per JWT claim). Shows role/context switching andWITH CHECKenforcement on in-process PGlite.packages/pgvector- pgvector similarity search. Registers thevectorWASM extension at construction and ranks documents by cosine distance.
It was scaffolded the same way as a normal pgpm project (pgpm init workspace + pgpm init), then wired to use pglite-test in place of pgsql-test — sibling to supabase-test-suite and drizzle-orm-test.
getConnections() from pglite-test is a drop-in for pgsql-test:
import { getConnections, PgTestClient, seed } from 'pglite-test';
let pg: PgTestClient, db: PgTestClient, teardown: () => Promise<void>;
beforeAll(async () => {
// Standard app roles (authenticated, anonymous, …) are seeded for you, so
// setContext({ role: 'authenticated' }) works with no manual CREATE ROLE.
({ pg, db, teardown } = await getConnections(
{},
[seed.pgpm(__dirname + '/..')] // deploy this package's pgpm module in-process
));
});
afterAll(() => teardown());
beforeEach(async () => { await pg.beforeEach(); await db.beforeEach(); });
afterEach(async () => { await db.afterEach(); await pg.afterEach(); });Under the hood, pglite-test composes the same seams the rest of the stack uses:
@pgpmjs/pglite-adapter's registerPglite() points pg-cache at PGlite (so seed.pgpm() deploys into it), and pgsql-client's client-factory seam routes the test client at the same in-process session. Because PGlite is a single session, transaction control is ref-counted so the standard two-client beforeEach/afterEach harness works unchanged.
Building a boilerplate from this repo? See
docs/pglite-vs-pgsql-test.md— a full catalog of every deviation from a standardpgsql-testproject (deps,--experimental-vm-modules, WASM cold-start timeouts, role creation, extension provisioning, services-free CI).
- In-memory by default. Persist with
getConnections({ pglite: { dataDir: './.pglite' } }). - Roles. PGlite starts as one superuser with no app roles; create any role used via
setContext({ role })throughpglite.extensionSql. - Extensions. WASM extensions (e.g. pgvector) are registered at construction via
pglite.extensionsand installed withCREATE EXTENSIONinextensionSql— pgpm'scleanSqlstripsCREATE EXTENSIONfrom migrations, so they are provisioned out-of-band.
- Node.js 20+
- pnpm 10+
(No Postgres, no Docker, no Supabase CLI.)
🛠 Built by the Constructive team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on GitHub.
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED “AS IS”, AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.