An end-to-end demonstration that the unmodified @pgpmjs/core migrate engine can deploy real migrations into PGlite — ElectricSQL's WASM build of Postgres ("SQLite for Postgres"): embedded, in-process, with no Postgres server or service container.
It proves the wire-protocol path (pgpm ↔ PGlite over a socket). The in-process driver path — @pgpmjs/pglite-adapter + pglite-test, with no socket — is the productized version. Both run in the same no-services unit-tests (pglite …) job in run-tests.yaml.
cd examples/pglite-socket
pnpm test # jest: boots PGlite, runs pgpm deploy → verify → data → revert, assertsIt's a normal workspace package, so pnpm install / pnpm build at the repo root wires it up. The test needs no Postgres service — PGlite runs in-process.
The jest test boots a PGlite instance, exposes it over a socket with the official pg-gateway shim (@electric-sql/pglite-socket), and points the @pgpmjs/core PgpmMigrate engine at it as if it were ordinary Postgres:
pgpm (@pgpmjs/core, unmodified) ──node-pg / TCP──▶ PGLiteSocketServer ──▶ PGlite (WASM)
It then asserts a full lifecycle against __fixtures__/ (a 4-change pgpm plan ending in a pgvector column):
- initialize — bootstraps the
pgpm_migrateschema (tables + PL/pgSQL procs) - deploy —
schema → table → index → embedding - verify — all four verified
- data round-trip — a pgvector nearest-neighbor query returns the seeded row
- revert — everything reverted, registry emptied
The engine runs against PGlite today with two call-site accommodations; the productized in-process driver folds these into the adapter layer:
-
Single-connection / transaction affinity. PGlite serializes everything and, while a transaction is open, pins the engine to that one connection.
deploy({ useTransaction: true })opensBEGINon one pooled connection and callsisDeployed()on a second — which can never run on PGlite → deadlock. This demo usesuseTransaction: false; the in-process adapter avoids it entirely because both seams funnel to PGlite's single session. (The socket shim also defaults tomaxConnections: 1; we raise it — PGlite still serializes, so multiplexing is safe.) -
Extensions are provisioned out-of-band. pgpm's
cleanSqldeliberately stripsCREATE EXTENSION(andBEGIN/COMMIT) from migrations — extensions come from the environment (thepostgres-plusimage /pgsql-test'sinstallExtensions()), not migrations. So the demo creates the extension at PGlite bootstrap, and PGlite additionally requires the extension registered in JS at construction (PGlite.create({ extensions: { vector } })).
Extension availability — not pgpm — is the functional boundary. Bundled/available: pg_trgm, citext, ltree, uuid_ossp, pgvector, … Not available (no WASM / no background workers): pg_cron, pg_partman, the BM25 search ext; PostGIS is experimental. A module needing those simply won't deploy to PGlite.
@pgpmjs/pglite-adapter— the productized in-process driver (no socket): registers a PGlite instance as thepg-cachepool factory.pglite-test— a drop-ingetConnections()on in-process PGlite for writing tests.