@@ -41,69 +41,74 @@ enabled. Set it in every package's scripts:
4141}
4242```
4343
44- ## 3. Generous timeouts for WASM cold-start (required on CI)
44+ ## 3. Generous timeout for WASM cold-start (required on CI)
4545
4646The first ` getConnections() ` compiles/loads the PGlite WASM module. On a cold CI
4747runner this can exceed ** Jest's default 5s hook timeout** , which makes
4848` beforeAll ` fail and ` teardown ` come back ` undefined ` (the failure we hit — the
49- deploy logs actually land * after* the timeout). Two guards, applied everywhere:
50-
51- ``` ts
52- beforeAll (async () => {
53- ({ pg , db , teardown } = await getConnections (/* ... */ ));
54- }, 120000 ); // explicit hook timeout
55- ```
49+ deploy logs actually land * after* the timeout). Fix it in ** one place** —
50+ ` jest.config.js ` — not with per-test inline timeouts:
5651
5752``` js
5853// jest.config.js
5954module .exports = {
6055 // ...
61- testTimeout: 120000 , // WASM cold-start room
56+ testTimeout: 120000 , // WASM cold-start room (covers slow extension loads)
6257};
6358```
6459
6560Loading a WASM ** extension** (e.g. pgvector) is meaningfully slower than the
66- bare instance, so vector suites especially need this. ** This should be a default
67- in any pglite-test boilerplate.**
61+ bare instance, so vector suites especially need this. The pglite boilerplate
62+ ships this in its generated ` jest.config.js ` , so test files carry no inline
63+ ` beforeAll(..., 120000) ` timeouts.
6864
69- ## 4. Roles are not auto-created (required, today )
65+ ## 4. Roles are seeded by default (with an escape hatch )
7066
7167On a real server ` pgsql-test ` bootstraps app roles (` anonymous ` /
72- ` authenticated ` / ` administrator ` ) via ` DbAdmin.createUserRole() ` as part of
73- ` createdb ` . PGlite has no ` createdb ` — the instance * is * the database — so that
74- bootstrap never runs and ** PGlite boots as a single superuser with no app
75- roles ** .
76-
77- Any role used via ` setContext({ role }) ` (and note ` db ` 's default context role
78- is ` anonymous ` ) must be created first, through ` extensionSql ` :
68+ ` authenticated ` / ` administrator ` / ` authenticated_client ` ) via
69+ ` DbAdmin.createUserRole() ` as part of ` createdb ` . PGlite has no ` createdb ` — the
70+ instance * is * the database — so ` pglite-test ` runs the equivalent bootstrap for
71+ you before seeding, using the same role generators ( ` generateCreateBaseRolesSQL `
72+ / ` generateCreateClientRoleSQL ` ) and the same attributes ( ` NOLOGIN ` ,
73+ ` administrator ` gets ` BYPASSRLS ` ). So a bare ` getConnections() ` can switch into
74+ an app role with no manual ` CREATE ROLE ` :
7975
8076``` ts
81- await getConnections (
82- { pglite: { extensionSql: [' CREATE ROLE authenticated;' ] } },
83- [seed .pgpm (__dirname + ' /..' )]
84- );
77+ await getConnections ({}, [seed .pgpm (__dirname + ' /..' )]);
78+ // db.setContext({ role: 'authenticated', ... }) just works
8579```
8680
87- The same ` CREATE ROLE ... NOLOGIN ` / ` GRANT ` statements our server bootstrap
88- uses work verbatim in PGlite (it's real Postgres) — only the ` LOGIN PASSWORD `
89- second-connection bits are superfluous in-process.
81+ Custom role * names* come from ` db.roles ` (a ` RoleMapping ` ), exactly like
82+ ` pgsql-test ` .
9083
91- > ** Boilerplate opportunity:** a default-role bootstrap in ` pglite-test ` (create
92- > the group roles from ` DEFAULT_ROLE_MAPPING ` , ` NOLOGIN ` , idempotent) would make
93- > it a true drop-in and remove this line. Until shipped, the boilerplate creates
94- > the roles it uses explicitly.
84+ ** Escape hatch:** to boot a lone superuser and manage your own roles/users, pass
85+ ` pglite: { roles: false } ` and create them in ` extensionSql ` (real Postgres DDL,
86+ verbatim from what you'd run on a server):
87+
88+ ``` ts
89+ await getConnections ({
90+ pglite: {
91+ roles: false ,
92+ extensionSql: [' CREATE ROLE app_writer NOLOGIN;' , ' CREATE ROLE app_user LOGIN;' ]
93+ }
94+ });
95+ ```
9596
9697## 5. Extensions are provisioned out-of-band (required for extensions)
9798
9899pgpm's ` cleanSql ` strips ` CREATE EXTENSION ` from migrations, and PGlite
99100extensions are WASM modules that must be registered at construction. So an
100101extension like pgvector needs three things wired together:
101102
102- 1 . the module's migration keeps its ` CREATE EXTENSION vector; ` (deploy SQL) and
103- the ` .control ` file lists it in ` requires ` ;
103+ 1 . the ` .control ` file lists it in ` requires ` (how pgpm tracks the dependency);
1041042 . the WASM module is registered at construction: ` pglite: { extensions: { vector } } ` ;
1051053 . it's installed at bootstrap: ` pglite: { extensionSql: ['CREATE EXTENSION IF NOT EXISTS vector;'] } ` .
106106
107+ Because ` seed.pgpm() ` deploys the module's ** entire** plan on every suite, any
108+ suite that seeds a module containing a vector column must register the extension
109+ — even a suite that only tests RLS. Put steps 2–3 in one shared ` connect() `
110+ helper the suites import, rather than repeating them per file.
111+
107112``` ts
108113import { vector } from ' @electric-sql/pglite-pgvector' ;
109114
@@ -154,7 +159,7 @@ steps:
154159 - uses : pnpm/action-setup@v4
155160 with : { version: 10 }
156161 - uses : actions/setup-node@v4
157- with : { node-version: '20 ', cache: 'pnpm' }
162+ with : { node-version: '22 ', cache: 'pnpm' }
158163 - run : pnpm install --frozen-lockfile
159164 - run : cd ./packages/${{ matrix.package }} && pnpm test
160165` ` `
@@ -166,7 +171,8 @@ role bootstrap. This is the main reason a PGlite boilerplate is attractive.
166171
167172The server/supabase suite maps roles in `pgpm.json` (`db.roles`, `useLocksForRoles`).
168173The PGlite suite's `pgpm.json` is just the workspace manifest (`{"packages" : ["packages/*"]}`);
169- roles are handled per-suite via `extensionSql` (see §4).
174+ the standard roles are seeded automatically (see §4), and custom role *names*
175+ can still be passed per-suite via `db.roles`.
170176
171177---
172178
@@ -185,8 +191,8 @@ roles are handled per-suite via `extensionSql` (see §4).
185191
186192- [ ] deps : ` pglite-test` , `@pgpmjs/pglite-adapter`, `@electric-sql/pglite` (+ `@electric-sql/pglite-pgvector` for a vector variant)
187193- [ ] `test` scripts prefixed with `NODE_OPTIONS=--experimental-vm-modules`
188- - [ ] `beforeAll(..., 120000)` + `testTimeout : 120000`
189- - [ ] roles created via `pglite.extensionSql` (until default-role bootstrap ships )
190- - [ ] extensions : ` pglite.extensions` + `CREATE EXTENSION` in `extensionSql`, kept in migration + `.control`
194+ - [ ] single `testTimeout : 120000` in `jest.config.js` (no inline `beforeAll` timeouts)
195+ - [ ] standard app roles seeded by default (opt out with `pglite : { roles: false }` )
196+ - [ ] extensions : ` pglite.extensions` + `CREATE EXTENSION` in `extensionSql`, declared in `.control`, shared via one `connect()` helper
191197- [ ] services-free CI workflow
192198- [ ] minimal `pgpm.json` (no `db.roles`)
0 commit comments