Skip to content

Commit cf8cec4

Browse files
committed
chore(pglite): standalone demo uses pnpm + run.js; rewrite README
- Drop package-lock.json; add pnpm-lock.yaml (installed with --ignore-workspace so it stays standalone on the published @pgpmjs/core, not the workspace build). - Rename run.mjs -> run.js (package.json already has type:module). - Workflow pglite.yaml: pnpm/action-setup + pnpm install --ignore-workspace / pnpm start. - Rewrite poc/pglite/README.md in the sibling test-framework style (badges, structure).
1 parent 3ab60ca commit cf8cec4

6 files changed

Lines changed: 1102 additions & 1530 deletions

File tree

.github/workflows/pglite.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ jobs:
2929
timeout-minutes: 10
3030
steps:
3131
- uses: actions/checkout@v4
32+
- uses: pnpm/action-setup@v2
33+
with:
34+
version: 10
3235
- uses: actions/setup-node@v4
3336
with:
3437
node-version: '22'
35-
- name: Install (standalone, not the monorepo)
38+
- name: Install (standalone, ignoring the monorepo workspace)
3639
working-directory: poc/pglite
37-
run: npm ci
40+
run: pnpm install --ignore-workspace --frozen-lockfile
3841
- name: Deploy pgpm migrations into PGlite (no Postgres service)
3942
working-directory: poc/pglite
40-
run: npm start
43+
run: pnpm start

poc/pglite/README.md

Lines changed: 40 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,60 @@
1-
# pgpm → PGlite (proof of concept)
1+
# pgpm → PGlite
22

3-
Proof that the **unmodified** pgpm migrate engine can deploy real migrations into
4-
[**PGlite**](https://github.com/electric-sql/pglite) — ElectricSQL's WASM build of
5-
Postgres ("SQLite for Postgres"): embedded, in-process, **no Postgres server or
6-
service container required**.
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>
76

8-
```bash
7+
<p align="center" width="100%">
8+
<a href="https://github.com/constructive-io/constructive/actions/workflows/pglite.yaml">
9+
<img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/pglite.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+
A standalone, end-to-end demonstration that the **unmodified, published** `@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 and is covered by the `unit-tests (pglite …)` job in [`run-tests.yaml`](../../.github/workflows/run-tests.yaml).
19+
20+
## Run
21+
22+
```sh
923
cd poc/pglite
10-
npm ci
11-
npm start # boots PGlite in-process, runs pgpm deploy -> verify -> revert, asserts
24+
pnpm install --ignore-workspace
25+
pnpm start # boots PGlite in-process, runs pgpm deploy verify revert, asserts
1226
```
1327

14-
This runs in CI via `.github/workflows/pglite.yaml` with no `services:` block —
15-
the whole point: PGlite needs no external database.
28+
Runs in CI via [`.github/workflows/pglite.yaml`](../../.github/workflows/pglite.yaml) (job `pglite-socket-e2e`) with **no `services:` block** — the whole point: PGlite needs no external database.
1629

17-
## What it does
30+
## How it works
1831

19-
`run.mjs` boots a PGlite instance, exposes it over a socket with the official
20-
`pg-gateway` shim (`@electric-sql/pglite-socket`), and points the published
21-
`@pgpmjs/core` `PgpmMigrate` engine at it as if it were ordinary Postgres:
32+
`run.js` boots a PGlite instance, exposes it over a socket with the official `pg-gateway` shim (`@electric-sql/pglite-socket`), and points the published `@pgpmjs/core` `PgpmMigrate` engine at it as if it were ordinary Postgres:
2233

2334
```
24-
pgpm (@pgpmjs/core, unmodified) --node-pg / TCP--> PGLiteSocketServer --> PGlite (WASM)
35+
pgpm (@pgpmjs/core, unmodified) ──node-pg / TCP──▶ PGLiteSocketServer ──▶ PGlite (WASM)
2536
```
2637

27-
It then asserts a full lifecycle against `module/` (a 4-change pgpm plan that ends
28-
in a `pgvector` column):
38+
It then asserts a full lifecycle against `module/` (a 4-change pgpm plan ending in a `pgvector` column):
39+
40+
* **initialize** — bootstraps the `pgpm_migrate` schema (tables + PL/pgSQL procs)
41+
* **deploy**`schema → table → index → embedding`
42+
* **verify** — all four verified
43+
* **data round-trip** — a pgvector nearest-neighbor query returns the seeded row
44+
* **revert** — everything reverted, registry emptied
2945

30-
- `initialize` — bootstraps the `pgpm_migrate` schema (tables + PL/pgSQL procs)
31-
- `deploy``schema → table → index → embedding`
32-
- `verify` — all four verified
33-
- data round-trip — a pgvector nearest-neighbor query returns the seeded row
34-
- `revert` — everything reverted, registry emptied
46+
Assertions exit non-zero on any mismatch, so CI fails loudly on regression.
3547

36-
## Why the code isn't quite drop-in (the findings this PoC pins down)
48+
## The two accommodations this pins down
3749

38-
The engine works against PGlite **today** with two call-site accommodations; a real
39-
`pglite` target would fold these into the engine/driver layer:
50+
The engine runs against PGlite **today** with two call-site accommodations; the productized in-process driver folds these into the adapter layer:
4051

41-
1. **Single-connection / transaction affinity.** PGlite serializes everything and,
42-
while a transaction is open, pins the engine to that one connection. The engine's
43-
`deploy({useTransaction:true})` opens `BEGIN` on one pooled connection and calls
44-
`isDeployed()` on a *second* — which can never run on PGlite → deadlock. The PoC
45-
uses `useTransaction:false`. Proper fix: run all transaction-scoped work on the
46-
same client. (Also: the socket shim defaults to `maxConnections:1`; we raise it —
47-
PGlite still serializes, so multiplexing is safe.)
52+
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.)
4853

49-
2. **Extensions are provisioned out-of-band.** pgpm's `cleanSql` deliberately strips
50-
`CREATE EXTENSION` (and `BEGIN/COMMIT`) from migrations — extensions come from the
51-
environment (the `postgres-plus` image / `pgsql-test`'s `installExtensions()`),
52-
not migrations. So the PoC creates the extension at PGlite bootstrap, and PGlite
53-
additionally requires the extension registered in JS at construction
54-
(`PGlite.create({ extensions: { vector } })`).
54+
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 } })`).
5555

56-
Extension availability — not pgpm — is the functional boundary. Bundled/available:
57-
`pg_trgm`, `citext`, `ltree`, `uuid_ossp`, `pgvector`, … Not available (no WASM /
58-
no background workers): `pg_cron`, `pg_partman`, the BM25 search ext; PostGIS is
59-
experimental. A module needing those simply won't deploy to PGlite.
56+
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.
6057

6158
## Notes
6259

63-
Standalone on purpose: this directory is **not** part of the pnpm workspace (its own
64-
`package.json` + `package-lock.json`, run with `npm ci`) so the PoC stays independent
65-
of the monorepo build. It depends on the *published* `@pgpmjs/core`; it could be
66-
repointed at the workspace build to track unreleased engine changes.
60+
Standalone on purpose: this directory is **not** part of the pnpm workspace. It has its own `package.json` + `pnpm-lock.yaml` and is installed with `pnpm install --ignore-workspace`, so the demo stays independent of the monorepo build and depends on the *published* `@pgpmjs/core` (it could be repointed at the workspace build to track unreleased engine changes).

0 commit comments

Comments
 (0)