pglite-test 0.1.2 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,10 +92,10 @@ await getConnections(
92
92
  {
93
93
  pglite: {
94
94
  dataDir: undefined, // in-memory by default
95
+ roles: true, // seed standard app roles (default) — see below
95
96
  extensions: { vector }, // WASM extensions (e.g. pglite-pgvector)
96
97
  extensionSql: [ // run once after ready
97
- 'CREATE EXTENSION IF NOT EXISTS vector;',
98
- 'CREATE ROLE authenticated;'
98
+ 'CREATE EXTENSION IF NOT EXISTS vector;'
99
99
  ]
100
100
  }
101
101
  },
@@ -103,12 +103,43 @@ await getConnections(
103
103
  );
104
104
  ```
105
105
 
106
+ ## Roles
107
+
108
+ On a server, `pgsql-test` bootstraps the standard app roles at `createdb`. PGlite
109
+ has no `createdb` — it boots as a lone superuser — so by default `getConnections()`
110
+ creates the same roles for you before seeding: `anonymous`, `authenticated`, and
111
+ `administrator` (with `BYPASSRLS`), using the same
112
+ attributes as the server bootstrap. That's why `db.setContext({ role: 'authenticated' })`
113
+ just works with no manual `CREATE ROLE`. Custom role *names* come from `db.roles`
114
+ (a `RoleMapping`), exactly like `pgsql-test`.
115
+
116
+ ### Bring your own roles/users
117
+
118
+ Want a clean superuser-only instance and full control over your own roles? Opt
119
+ out with `pglite: { roles: false }` and create them in `extensionSql` (real
120
+ Postgres DDL — the same statements you'd run on a server):
121
+
122
+ ```typescript
123
+ await getConnections({
124
+ pglite: {
125
+ roles: false, // skip the built-in bootstrap
126
+ extensionSql: [
127
+ "CREATE ROLE app_reader NOLOGIN;",
128
+ "CREATE ROLE app_writer NOLOGIN;",
129
+ // a login user, if a test needs one (no password needed in-process):
130
+ "CREATE ROLE app_user LOGIN;",
131
+ "GRANT app_writer TO app_user;"
132
+ ]
133
+ }
134
+ });
135
+ ```
136
+
106
137
  ## Single-session model
107
138
 
108
139
  PGlite is one in-process session, so `pg` and `db` share it. That differs from `pgsql-test` (two authenticated connections on a real server):
109
140
 
110
141
  - Transaction control is **ref-counted** (`SharedTxn`) so the standard two-client `beforeEach`/`afterEach` harness emits exactly one `BEGIN`/`SAVEPOINT`/`ROLLBACK`/`COMMIT` per test. The single-client (`db` only) pattern also works.
111
- - Role-based RLS uses `setContext({ role })` (i.e. `SET LOCAL role`) on the shared session rather than separate authenticated connections. Any role you switch to must exist — create it via `pglite.extensionSql` (e.g. `['CREATE ROLE authenticated;']`).
142
+ - Role-based RLS uses `setContext({ role })` (i.e. `SET LOCAL role`) on the shared session rather than separate authenticated connections. The standard app roles are created for you by default (see [Roles](#roles)); any *extra* role you switch to must be created via `pglite.extensionSql`.
112
143
  - `publish()` (commit-and-continue) is not supported under the shared-session coordinator.
113
144
 
114
145
  ## Related
package/esm/index.js CHANGED
@@ -28,25 +28,40 @@
28
28
  * afterEach(async () => { await db.afterEach(); await pg.afterEach(); });
29
29
  * ```
30
30
  */
31
+ import { generateCreateBaseRolesSQL, generateCreateClientRoleSQL } from '@pgpmjs/core';
31
32
  import { createPgliteClient, registerPglite } from '@pgpmjs/pglite-adapter';
32
33
  import { getPgEnvOptions } from 'pg-env';
33
- import { getActivePgClientFactory, registerPgClientFactory } from 'pgsql-client';
34
+ import { getActivePgClientFactory, getRoleMapping, registerPgClientFactory } from 'pgsql-client';
34
35
  import { seed } from 'pgsql-test';
35
36
  import { PgliteTestClient, SharedTxn } from './txn';
36
37
  /**
37
38
  * Create an isolated PGlite-backed test environment and return `pg`/`db` clients
38
39
  * plus a `teardown()`. Defaults to seeding via `seed.pgpm()` (deploys the pgpm
39
- * module in `cwd`), matching pgsql-test.
40
+ * module in `cwd`) and creating the standard app roles, matching pgsql-test — so
41
+ * a bare `getConnections()` is enough to deploy and switch roles with no manual
42
+ * `CREATE ROLE`. Opt out of the role bootstrap with `pglite: { roles: false }`.
40
43
  */
41
44
  export const getConnections = async (cn = {}, seedAdapters = [seed.pgpm()]) => {
42
45
  // Capture the previously-active client factory so teardown restores it
43
46
  // (rather than clobbering to the default), mirroring how registerPglite
44
47
  // restores the previous pool factory. Keeps nested/sequential suites clean.
45
48
  const previousClientFactory = getActivePgClientFactory();
49
+ // Seed the standard app roles by default so `db.setContext({ role })` works
50
+ // out of the box — PGlite boots as a lone superuser with no app roles, unlike
51
+ // a server where pgsql-test bootstraps them at createdb. Opt out with
52
+ // `pglite: { roles: false }` to manage your own (see README).
53
+ const bootstrapRoles = cn.pglite?.roles !== false;
54
+ const roleMapping = getRoleMapping({ roles: cn.db?.roles });
55
+ const roleSql = bootstrapRoles
56
+ ? [
57
+ generateCreateBaseRolesSQL(roleMapping),
58
+ generateCreateClientRoleSQL(roleMapping)
59
+ ]
60
+ : [];
46
61
  const handle = await registerPglite({
47
62
  dataDir: cn.pglite?.dataDir,
48
63
  extensions: cn.pglite?.extensions,
49
- extensionSql: cn.pglite?.extensionSql,
64
+ extensionSql: [...roleSql, ...(cn.pglite?.extensionSql ?? [])],
50
65
  instance: cn.pglite?.instance
51
66
  });
52
67
  // Route pgsql-client's PgClient at the same in-process PGlite session.
package/index.d.ts CHANGED
@@ -38,11 +38,22 @@ export interface PgliteConnectionOpts extends GetConnectionOpts {
38
38
  /** WASM extensions registered at construction, e.g. `{ vector }`. */
39
39
  extensions?: Record<string, any>;
40
40
  /**
41
- * SQL run once after the instance is ready — `CREATE EXTENSION ...`, and any
42
- * `CREATE ROLE ...` needed for RLS role switching (PGlite starts as a single
43
- * superuser, so roles used via `setContext({ role })` must be created here).
41
+ * SQL run once after the instance is ready — the place for
42
+ * `CREATE EXTENSION ...` (and any extra roles/objects you want).
44
43
  */
45
44
  extensionSql?: string[];
45
+ /**
46
+ * Auto-create the standard app roles (`anonymous` / `authenticated` /
47
+ * `administrator`) with the same attributes
48
+ * pgsql-test's server bootstrap uses, before seeding. This is what lets a
49
+ * bare `getConnections()` switch into an app role via `db.setContext()`
50
+ * with no manual `CREATE ROLE`. Defaults to `true`.
51
+ *
52
+ * Set `false` to boot as a lone superuser and manage your own roles/users
53
+ * through `extensionSql` (see the README for the pattern). Role *names* can
54
+ * be customized via `db.roles` (a `RoleMapping`), mirroring pgsql-test.
55
+ */
56
+ roles?: boolean;
46
57
  /** Reuse an already-created PGlite instance instead of creating one. */
47
58
  instance?: PGlite;
48
59
  };
@@ -60,7 +71,9 @@ export interface PgliteConnectionResult {
60
71
  /**
61
72
  * Create an isolated PGlite-backed test environment and return `pg`/`db` clients
62
73
  * plus a `teardown()`. Defaults to seeding via `seed.pgpm()` (deploys the pgpm
63
- * module in `cwd`), matching pgsql-test.
74
+ * module in `cwd`) and creating the standard app roles, matching pgsql-test — so
75
+ * a bare `getConnections()` is enough to deploy and switch roles with no manual
76
+ * `CREATE ROLE`. Opt out of the role bootstrap with `pglite: { roles: false }`.
64
77
  */
65
78
  export declare const getConnections: (cn?: PgliteConnectionOpts, seedAdapters?: SeedAdapter[]) => Promise<PgliteConnectionResult>;
66
79
  export { PgliteTestClient, SharedTxn } from './txn';
package/index.js CHANGED
@@ -31,6 +31,7 @@
31
31
  */
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
33
  exports.seed = exports.PgTestClient = exports.SharedTxn = exports.PgliteTestClient = exports.getConnections = void 0;
34
+ const core_1 = require("@pgpmjs/core");
34
35
  const pglite_adapter_1 = require("@pgpmjs/pglite-adapter");
35
36
  const pg_env_1 = require("pg-env");
36
37
  const pgsql_client_1 = require("pgsql-client");
@@ -39,17 +40,31 @@ const txn_1 = require("./txn");
39
40
  /**
40
41
  * Create an isolated PGlite-backed test environment and return `pg`/`db` clients
41
42
  * plus a `teardown()`. Defaults to seeding via `seed.pgpm()` (deploys the pgpm
42
- * module in `cwd`), matching pgsql-test.
43
+ * module in `cwd`) and creating the standard app roles, matching pgsql-test — so
44
+ * a bare `getConnections()` is enough to deploy and switch roles with no manual
45
+ * `CREATE ROLE`. Opt out of the role bootstrap with `pglite: { roles: false }`.
43
46
  */
44
47
  const getConnections = async (cn = {}, seedAdapters = [pgsql_test_1.seed.pgpm()]) => {
45
48
  // Capture the previously-active client factory so teardown restores it
46
49
  // (rather than clobbering to the default), mirroring how registerPglite
47
50
  // restores the previous pool factory. Keeps nested/sequential suites clean.
48
51
  const previousClientFactory = (0, pgsql_client_1.getActivePgClientFactory)();
52
+ // Seed the standard app roles by default so `db.setContext({ role })` works
53
+ // out of the box — PGlite boots as a lone superuser with no app roles, unlike
54
+ // a server where pgsql-test bootstraps them at createdb. Opt out with
55
+ // `pglite: { roles: false }` to manage your own (see README).
56
+ const bootstrapRoles = cn.pglite?.roles !== false;
57
+ const roleMapping = (0, pgsql_client_1.getRoleMapping)({ roles: cn.db?.roles });
58
+ const roleSql = bootstrapRoles
59
+ ? [
60
+ (0, core_1.generateCreateBaseRolesSQL)(roleMapping),
61
+ (0, core_1.generateCreateClientRoleSQL)(roleMapping)
62
+ ]
63
+ : [];
49
64
  const handle = await (0, pglite_adapter_1.registerPglite)({
50
65
  dataDir: cn.pglite?.dataDir,
51
66
  extensions: cn.pglite?.extensions,
52
- extensionSql: cn.pglite?.extensionSql,
67
+ extensionSql: [...roleSql, ...(cn.pglite?.extensionSql ?? [])],
53
68
  instance: cn.pglite?.instance
54
69
  });
55
70
  // Route pgsql-client's PgClient at the same in-process PGlite session.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pglite-test",
3
- "version": "0.1.2",
3
+ "version": "0.2.2",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Drop-in pgsql-test getConnections backed by an in-process PGlite instance — no Postgres server, instance-per-suite isolation",
6
6
  "main": "index.js",
@@ -46,15 +46,16 @@
46
46
  "makage": "^0.3.0"
47
47
  },
48
48
  "dependencies": {
49
- "@pgpmjs/env": "^2.26.3",
50
- "@pgpmjs/pglite-adapter": "^0.1.2",
51
- "pg-cache": "^3.14.2",
52
- "pg-env": "^1.17.0",
53
- "pgsql-client": "^3.20.2",
54
- "pgsql-test": "^4.18.5"
49
+ "@pgpmjs/core": "^6.28.1",
50
+ "@pgpmjs/env": "^2.26.4",
51
+ "@pgpmjs/pglite-adapter": "^0.1.3",
52
+ "pg-cache": "^3.14.3",
53
+ "pg-env": "^1.17.1",
54
+ "pgsql-client": "^3.20.3",
55
+ "pgsql-test": "^4.18.6"
55
56
  },
56
57
  "peerDependencies": {
57
58
  "@electric-sql/pglite": ">=0.5.0"
58
59
  },
59
- "gitHead": "e2a1337c7ec8366b28c10e550645e44cad6085ff"
60
+ "gitHead": "3c1c2020b1e5fc5962c28218de1c1c5405968584"
60
61
  }