|
| 1 | +# pgpm → PGlite |
| 2 | + |
| 3 | +<p align="center" width="100%"> |
| 4 | + <img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" /> |
| 5 | +</p> |
| 6 | + |
| 7 | +<p align="center" width="100%"> |
| 8 | + <a href="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml"> |
| 9 | + <img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml/badge.svg" /> |
| 10 | + </a> |
| 11 | + <a href="https://github.com/constructive-io/constructive/blob/main/LICENSE"> |
| 12 | + <img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/> |
| 13 | + </a> |
| 14 | +</p> |
| 15 | + |
| 16 | +An end-to-end demonstration that the **unmodified** `@pgpmjs/core` migrate engine can deploy real migrations into [**PGlite**](https://github.com/electric-sql/pglite) — ElectricSQL's WASM build of Postgres ("SQLite for Postgres"): embedded, in-process, with **no Postgres server or service container**. |
| 17 | + |
| 18 | +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`](../../.github/workflows/run-tests.yaml). |
| 19 | + |
| 20 | +## Run |
| 21 | + |
| 22 | +```sh |
| 23 | +cd examples/pglite-socket |
| 24 | +pnpm test # jest: boots PGlite, runs pgpm deploy → verify → data → revert, asserts |
| 25 | +``` |
| 26 | + |
| 27 | +It'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. |
| 28 | + |
| 29 | +## How it works |
| 30 | + |
| 31 | +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: |
| 32 | + |
| 33 | +``` |
| 34 | +pgpm (@pgpmjs/core, unmodified) ──node-pg / TCP──▶ PGLiteSocketServer ──▶ PGlite (WASM) |
| 35 | +``` |
| 36 | + |
| 37 | +It then asserts a full lifecycle against `__fixtures__/` (a 4-change pgpm plan ending in a `pgvector` column): |
| 38 | + |
| 39 | +* **initialize** — bootstraps the `pgpm_migrate` schema (tables + PL/pgSQL procs) |
| 40 | +* **deploy** — `schema → table → index → embedding` |
| 41 | +* **verify** — all four verified |
| 42 | +* **data round-trip** — a pgvector nearest-neighbor query returns the seeded row |
| 43 | +* **revert** — everything reverted, registry emptied |
| 44 | + |
| 45 | +## The two accommodations this pins down |
| 46 | + |
| 47 | +The engine runs against PGlite **today** with two call-site accommodations; the productized in-process driver folds these into the adapter layer: |
| 48 | + |
| 49 | +1. **Single-connection / transaction affinity.** PGlite serializes everything and, while a transaction is open, pins the engine to that one connection. `deploy({ useTransaction: true })` opens `BEGIN` on one pooled connection and calls `isDeployed()` on a *second* — which can never run on PGlite → deadlock. This demo uses `useTransaction: false`; the in-process adapter avoids it entirely because both seams funnel to PGlite's single session. (The socket shim also defaults to `maxConnections: 1`; we raise it — PGlite still serializes, so multiplexing is safe.) |
| 50 | + |
| 51 | +2. **Extensions are provisioned out-of-band.** pgpm's `cleanSql` deliberately strips `CREATE EXTENSION` (and `BEGIN`/`COMMIT`) from migrations — extensions come from the environment (the `postgres-plus` image / `pgsql-test`'s `installExtensions()`), 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 } })`). |
| 52 | + |
| 53 | +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. |
| 54 | + |
| 55 | +## See also |
| 56 | + |
| 57 | +* [`@pgpmjs/pglite-adapter`](../../postgres/pglite-adapter) — the productized in-process driver (no socket): registers a PGlite instance as the `pg-cache` pool factory. |
| 58 | +* [`pglite-test`](../../postgres/pglite-test) — a drop-in `getConnections()` on in-process PGlite for writing tests. |
0 commit comments