@pgpmjs/pglite-adapter 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
4
+ Copyright (c) 2025 Constructive <developers@constructive.io>
5
+ Copyright (c) 2020-present, Interweb, Inc.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # @pgpmjs/pglite-adapter
2
+
3
+ A PGlite driver for pgpm. It registers an **in-process** [PGlite](https://github.com/electric-sql/pglite)
4
+ instance (WASM Postgres) as the `pg-cache` pool factory, so the **unmodified**
5
+ pgpm engine deploys, verifies, and reverts migrations against PGlite with **no
6
+ Postgres server and no socket**.
7
+
8
+ All `@electric-sql/pglite*` dependencies live here — `@pgpmjs/core`, `pg-cache`,
9
+ and `pgsql-test` never import them.
10
+
11
+ ## How it works
12
+
13
+ pgpm's engine only needs a node-`pg`-shaped pool (`query` / `connect` / `end`).
14
+ Step 1 added a driver seam in `pg-cache` (`registerPgPoolFactory`). This package
15
+ implements that seam against PGlite's JS API:
16
+
17
+ - parameterized queries → `db.query` (extended protocol)
18
+ - parameterless / multi-statement SQL (pgpm's bootstrap) → `db.exec`
19
+
20
+ Because PGlite is a single in-process session, both `pool.query` and the client
21
+ returned by `connect()` share one session — so pgpm's transactional deploy works
22
+ without the socket PoC's `useTransaction: false` workaround.
23
+
24
+ ## Usage
25
+
26
+ ```ts
27
+ import { PgpmMigrate } from '@pgpmjs/core';
28
+ import { registerPglite } from '@pgpmjs/pglite-adapter';
29
+
30
+ const { db, close } = await registerPglite();
31
+
32
+ const migrate = new PgpmMigrate({ database: 'postgres' /* ...pg config */ });
33
+ await migrate.deploy({ modulePath: './my-module' });
34
+ await migrate.verify({ modulePath: './my-module' });
35
+
36
+ await close(); // restores the previous factory and closes PGlite
37
+ ```
38
+
39
+ ### Extensions
40
+
41
+ pgpm's `cleanSql` strips `CREATE EXTENSION` from migrations, so extensions are
42
+ provisioned out-of-band — register the WASM extension at construction and run
43
+ `CREATE EXTENSION` at bootstrap:
44
+
45
+ ```ts
46
+ import { vector } from '@electric-sql/pglite-pgvector';
47
+
48
+ const { db, close } = await registerPglite({
49
+ extensions: { vector },
50
+ extensionSql: ['CREATE EXTENSION IF NOT EXISTS vector;']
51
+ });
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Education and Tutorials
57
+
58
+ 1. 🚀 [Quickstart: Getting Up and Running](https://constructive.io/learn/quickstart)
59
+ Get started with modular databases in minutes. Install prerequisites and deploy your first module.
60
+
61
+ 2. 📦 [Modular PostgreSQL Development with Database Packages](https://constructive.io/learn/modular-postgres)
62
+ Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.
63
+
64
+ 3. ✏️ [Authoring Database Changes](https://constructive.io/learn/authoring-database-changes)
65
+ Master the workflow for adding, organizing, and managing database changes with pgpm.
66
+
67
+ 4. 🧪 [End-to-End PostgreSQL Testing with TypeScript](https://constructive.io/learn/e2e-postgres-testing)
68
+ Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.
69
+
70
+ 5. ⚡ [Supabase Testing](https://constructive.io/learn/supabase)
71
+ Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.
72
+
73
+ 6. 💧 [Drizzle ORM Testing](https://constructive.io/learn/drizzle-testing)
74
+ Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.
75
+
76
+ 7. 🔧 [Troubleshooting](https://constructive.io/learn/troubleshooting)
77
+ Common issues and solutions for pgpm, PostgreSQL, and testing.
78
+
79
+ ## Related Constructive Tooling
80
+
81
+ ### 📦 Package Management
82
+
83
+ * [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
84
+
85
+ ### 🧪 Testing
86
+
87
+ * [pgsql-test](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
88
+ * [pgsql-seed](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-seed): **🌱 PostgreSQL seeding utilities** for CSV, JSON, SQL data loading, and pgpm deployment.
89
+ * [supabase-test](https://github.com/constructive-io/constructive/tree/main/postgres/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
90
+ * [graphile-test](https://github.com/constructive-io/constructive/tree/main/graphile/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
91
+ * [pg-query-context](https://github.com/constructive-io/constructive/tree/main/postgres/pg-query-context): **🔒 Session context injection** to add session-local context (e.g., `SET LOCAL`) into queries—ideal for setting `role`, `jwt.claims`, and other session settings.
92
+
93
+ ### 🧠 Parsing & AST
94
+
95
+ * [pgsql-parser](https://www.npmjs.com/package/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
96
+ * [libpg-query-node](https://www.npmjs.com/package/libpg-query): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
97
+ * [pg-proto-parser](https://www.npmjs.com/package/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
98
+ * [@pgsql/enums](https://www.npmjs.com/package/@pgsql/enums): **🏷️ TypeScript enums** for PostgreSQL AST for safe and ergonomic parsing logic.
99
+ * [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **📝 Type definitions** for PostgreSQL AST nodes in TypeScript.
100
+ * [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **🛠️ AST utilities** for constructing and transforming PostgreSQL syntax trees.
101
+
102
+ ### 📚 Documentation & Skills
103
+
104
+ * [constructive-skills](https://github.com/constructive-io/constructive-skills): **📖 Platform documentation and AI agent skills** — feature catalog, blueprint reference, SDK guides (i18n, billing, limits, events, uploads, security, entities, search, AI), and deployment guides.
105
+
106
+ Install skills for AI coding agents:
107
+
108
+ ```bash
109
+ # All platform skills (security, blueprints, codegen, billing, etc.)
110
+ npx skills add constructive-io/constructive-skills
111
+
112
+ # Individual repo skills (pgpm, testing, CLI, search, etc.)
113
+ npx skills add https://github.com/constructive-io/constructive --skill pgpm
114
+ npx skills add https://github.com/constructive-io/constructive --skill constructive-testing
115
+ ```
116
+
117
+ ## Credits
118
+
119
+ **🛠 Built by the [Constructive](https://constructive.io) team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**
120
+
121
+ ## Disclaimer
122
+
123
+ AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
124
+
125
+ No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
package/client.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { PGlite } from '@electric-sql/pglite';
2
+ import { type PgResultLike } from './runner';
3
+ /**
4
+ * The minimal node-`pg` `Client` surface that `pgsql-client`'s `PgClient` uses:
5
+ * `connect()`, `query()` and `end()`. A real `pg.Client` structurally satisfies
6
+ * it, so registering a factory that returns this is transparent to consumers.
7
+ */
8
+ export interface QueryablePgClient {
9
+ connect(): Promise<void>;
10
+ query(text: any, values?: any[]): Promise<PgResultLike>;
11
+ end(): Promise<void>;
12
+ }
13
+ /**
14
+ * Build a `pg.Client`-shaped object backed by an existing PGlite instance.
15
+ *
16
+ * Every client returned for the same instance shares that single in-process
17
+ * session, so transaction control (`BEGIN`/`SAVEPOINT`/`ROLLBACK`/`COMMIT`) is
18
+ * shared too. `end()` is a no-op: the instance lifecycle is owned by whoever
19
+ * created it (see `registerPglite`), not by an individual client.
20
+ */
21
+ export declare const createPgliteClient: (db: PGlite) => QueryablePgClient;
package/client.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPgliteClient = void 0;
4
+ const runner_1 = require("./runner");
5
+ /**
6
+ * Build a `pg.Client`-shaped object backed by an existing PGlite instance.
7
+ *
8
+ * Every client returned for the same instance shares that single in-process
9
+ * session, so transaction control (`BEGIN`/`SAVEPOINT`/`ROLLBACK`/`COMMIT`) is
10
+ * shared too. `end()` is a no-op: the instance lifecycle is owned by whoever
11
+ * created it (see `registerPglite`), not by an individual client.
12
+ */
13
+ const createPgliteClient = (db) => {
14
+ const query = (0, runner_1.boundQuery)(db);
15
+ return {
16
+ connect: async () => {
17
+ await db.waitReady;
18
+ },
19
+ query,
20
+ end: async () => { }
21
+ };
22
+ };
23
+ exports.createPgliteClient = createPgliteClient;
package/esm/client.js ADDED
@@ -0,0 +1,19 @@
1
+ import { boundQuery } from './runner';
2
+ /**
3
+ * Build a `pg.Client`-shaped object backed by an existing PGlite instance.
4
+ *
5
+ * Every client returned for the same instance shares that single in-process
6
+ * session, so transaction control (`BEGIN`/`SAVEPOINT`/`ROLLBACK`/`COMMIT`) is
7
+ * shared too. `end()` is a no-op: the instance lifecycle is owned by whoever
8
+ * created it (see `registerPglite`), not by an individual client.
9
+ */
10
+ export const createPgliteClient = (db) => {
11
+ const query = boundQuery(db);
12
+ return {
13
+ connect: async () => {
14
+ await db.waitReady;
15
+ },
16
+ query,
17
+ end: async () => { }
18
+ };
19
+ };
package/esm/index.js ADDED
@@ -0,0 +1,35 @@
1
+ import { PGlite } from '@electric-sql/pglite';
2
+ import { getActivePgPoolFactory, registerPgPoolFactory } from 'pg-cache';
3
+ import { createPglitePool } from './pool';
4
+ export { createPgliteClient } from './client';
5
+ export { createPglitePool } from './pool';
6
+ /**
7
+ * Create (or adopt) a PGlite instance and register it as the pg-cache pool
8
+ * factory, so every `getPgPool()` call — and therefore the unmodified pgpm
9
+ * engine — talks to PGlite in-process. No socket, no Postgres server.
10
+ *
11
+ * @example
12
+ * const { db, close } = await registerPglite();
13
+ * const migrate = new PgpmMigrate({ database: 'postgres', ... });
14
+ * await migrate.deploy({ modulePath });
15
+ * await close();
16
+ */
17
+ export const registerPglite = async (options = {}) => {
18
+ const db = options.instance ??
19
+ (await PGlite.create({ dataDir: options.dataDir, extensions: options.extensions }));
20
+ await db.waitReady;
21
+ for (const sql of options.extensionSql ?? []) {
22
+ await db.exec(sql);
23
+ }
24
+ const previous = getActivePgPoolFactory();
25
+ const pool = createPglitePool(db);
26
+ registerPgPoolFactory(() => pool);
27
+ return {
28
+ db,
29
+ unregister: () => registerPgPoolFactory(previous),
30
+ close: async () => {
31
+ registerPgPoolFactory(previous);
32
+ await db.close();
33
+ }
34
+ };
35
+ };
package/esm/pool.js ADDED
@@ -0,0 +1,34 @@
1
+ import { boundQuery } from './runner';
2
+ /**
3
+ * Build a `QueryablePool` backed by an existing PGlite instance.
4
+ *
5
+ * PGlite is a single in-process engine (one session), so both `pool.query` and
6
+ * the client returned by `connect()` funnel to the same instance. That is
7
+ * exactly what lets pgpm's transactional deploy work: the `BEGIN` opened on the
8
+ * connected client and the `isDeployed()` check issued via `pool.query` share
9
+ * one session, so there is no cross-connection deadlock (the failure mode of a
10
+ * multi-connection pool against a single-connection backend).
11
+ *
12
+ * `end()` intentionally does NOT close the PGlite instance: the same instance
13
+ * may back several cached pool wrappers (pg-cache keys by database name), and
14
+ * its lifecycle is owned by whoever created it (see `registerPglite`).
15
+ */
16
+ export const createPglitePool = (db) => {
17
+ let ended = false;
18
+ const query = boundQuery(db);
19
+ const client = {
20
+ query,
21
+ release: () => { }
22
+ };
23
+ const pool = {
24
+ get ended() {
25
+ return ended;
26
+ },
27
+ query,
28
+ connect: async () => client,
29
+ end: async () => {
30
+ ended = true;
31
+ }
32
+ };
33
+ return pool;
34
+ };
package/esm/runner.js ADDED
@@ -0,0 +1,33 @@
1
+ const toResult = (r) => {
2
+ const rows = r?.rows ?? [];
3
+ const rowCount = typeof r?.affectedRows === 'number' ? r.affectedRows : rows.length;
4
+ return { rows, rowCount, fields: r?.fields ?? [] };
5
+ };
6
+ /**
7
+ * Run a statement against the single in-process PGlite engine.
8
+ *
9
+ * PGlite mirrors node-`pg`'s two protocols:
10
+ * - parameterized (`$1`, ...) -> `db.query` (extended protocol, single statement)
11
+ * - no parameters -> `db.exec` (simple protocol, supports the multi-statement
12
+ * bootstrap SQL pgpm runs in `initialize()`), returning the last result.
13
+ */
14
+ export const run = async (db, text, values) => {
15
+ if (values && values.length > 0) {
16
+ return toResult(await db.query(text, values));
17
+ }
18
+ const results = await db.exec(text);
19
+ const last = Array.isArray(results) && results.length ? results[results.length - 1] : undefined;
20
+ return toResult(last);
21
+ };
22
+ /** Normalize the two call shapes pg accepts: `(text, values)` or `({ text, values })`. */
23
+ export const normalize = (first, second) => {
24
+ if (first && typeof first === 'object' && 'text' in first) {
25
+ return { text: first.text, values: first.values };
26
+ }
27
+ return { text: first, values: second };
28
+ };
29
+ /** A bound `(text, values | { text, values })` query function over a PGlite instance. */
30
+ export const boundQuery = (db) => (text, values) => {
31
+ const q = normalize(text, values);
32
+ return run(db, q.text, q.values);
33
+ };
package/index.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { PGlite } from '@electric-sql/pglite';
2
+ export { createPgliteClient, type QueryablePgClient } from './client';
3
+ export { createPglitePool } from './pool';
4
+ export type { PgResultLike } from './runner';
5
+ export interface PgliteAdapterOptions {
6
+ /** Persist to a directory (default: in-memory). */
7
+ dataDir?: string;
8
+ /**
9
+ * PGlite WASM extensions to register at construction, e.g. `{ vector }` from
10
+ * `@electric-sql/pglite-pgvector`. The corresponding `CREATE EXTENSION`
11
+ * statements still run out-of-band (see `extensionSql`) because pgpm's
12
+ * `cleanSql` strips `CREATE EXTENSION` from migration scripts.
13
+ */
14
+ extensions?: Record<string, any>;
15
+ /**
16
+ * SQL run once after the instance is ready — the place for
17
+ * `CREATE EXTENSION IF NOT EXISTS ...`, mirroring pgsql-test's
18
+ * `DbAdmin.installExtensions()`.
19
+ */
20
+ extensionSql?: string[];
21
+ /** Reuse an already-created PGlite instance instead of creating one. */
22
+ instance?: PGlite;
23
+ }
24
+ export interface PgliteAdapterHandle {
25
+ /** The underlying PGlite instance (for direct queries / lifecycle control). */
26
+ db: PGlite;
27
+ /** Restore the previously-active pg-cache factory. Does not close `db`. */
28
+ unregister: () => void;
29
+ /** Unregister and close the PGlite instance. */
30
+ close: () => Promise<void>;
31
+ }
32
+ /**
33
+ * Create (or adopt) a PGlite instance and register it as the pg-cache pool
34
+ * factory, so every `getPgPool()` call — and therefore the unmodified pgpm
35
+ * engine — talks to PGlite in-process. No socket, no Postgres server.
36
+ *
37
+ * @example
38
+ * const { db, close } = await registerPglite();
39
+ * const migrate = new PgpmMigrate({ database: 'postgres', ... });
40
+ * await migrate.deploy({ modulePath });
41
+ * await close();
42
+ */
43
+ export declare const registerPglite: (options?: PgliteAdapterOptions) => Promise<PgliteAdapterHandle>;
package/index.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerPglite = exports.createPglitePool = exports.createPgliteClient = void 0;
4
+ const pglite_1 = require("@electric-sql/pglite");
5
+ const pg_cache_1 = require("pg-cache");
6
+ const pool_1 = require("./pool");
7
+ var client_1 = require("./client");
8
+ Object.defineProperty(exports, "createPgliteClient", { enumerable: true, get: function () { return client_1.createPgliteClient; } });
9
+ var pool_2 = require("./pool");
10
+ Object.defineProperty(exports, "createPglitePool", { enumerable: true, get: function () { return pool_2.createPglitePool; } });
11
+ /**
12
+ * Create (or adopt) a PGlite instance and register it as the pg-cache pool
13
+ * factory, so every `getPgPool()` call — and therefore the unmodified pgpm
14
+ * engine — talks to PGlite in-process. No socket, no Postgres server.
15
+ *
16
+ * @example
17
+ * const { db, close } = await registerPglite();
18
+ * const migrate = new PgpmMigrate({ database: 'postgres', ... });
19
+ * await migrate.deploy({ modulePath });
20
+ * await close();
21
+ */
22
+ const registerPglite = async (options = {}) => {
23
+ const db = options.instance ??
24
+ (await pglite_1.PGlite.create({ dataDir: options.dataDir, extensions: options.extensions }));
25
+ await db.waitReady;
26
+ for (const sql of options.extensionSql ?? []) {
27
+ await db.exec(sql);
28
+ }
29
+ const previous = (0, pg_cache_1.getActivePgPoolFactory)();
30
+ const pool = (0, pool_1.createPglitePool)(db);
31
+ (0, pg_cache_1.registerPgPoolFactory)(() => pool);
32
+ return {
33
+ db,
34
+ unregister: () => (0, pg_cache_1.registerPgPoolFactory)(previous),
35
+ close: async () => {
36
+ (0, pg_cache_1.registerPgPoolFactory)(previous);
37
+ await db.close();
38
+ }
39
+ };
40
+ };
41
+ exports.registerPglite = registerPglite;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@pgpmjs/pglite-adapter",
3
+ "version": "0.1.0",
4
+ "author": "Constructive <developers@constructive.io>",
5
+ "description": "PGlite driver for pgpm — registers an in-process PGlite instance as the pg-cache pool factory so the unmodified pgpm engine deploys into PGlite (WASM Postgres) with no server",
6
+ "main": "index.js",
7
+ "module": "esm/index.js",
8
+ "types": "index.d.ts",
9
+ "homepage": "https://github.com/constructive-io/constructive",
10
+ "license": "MIT",
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "directory": "dist"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/constructive-io/constructive"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/constructive-io/constructive/issues"
21
+ },
22
+ "keywords": [
23
+ "postgres",
24
+ "postgresql",
25
+ "pglite",
26
+ "wasm",
27
+ "pgpm",
28
+ "migrations",
29
+ "pg-cache",
30
+ "adapter",
31
+ "driver"
32
+ ],
33
+ "scripts": {
34
+ "clean": "makage clean",
35
+ "prepack": "npm run build",
36
+ "build": "makage build",
37
+ "build:dev": "makage build --dev",
38
+ "lint": "eslint . --fix",
39
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
40
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
41
+ },
42
+ "devDependencies": {
43
+ "@electric-sql/pglite": "0.5.4",
44
+ "@pgpmjs/core": "^6.27.1",
45
+ "@types/pg": "^8.20.0",
46
+ "makage": "^0.3.0"
47
+ },
48
+ "dependencies": {
49
+ "pg-cache": "^3.14.0"
50
+ },
51
+ "peerDependencies": {
52
+ "@electric-sql/pglite": ">=0.5.0"
53
+ },
54
+ "gitHead": "7cbe15fac21a9345b169203ba9542ba9a50c866c"
55
+ }
package/pool.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { PGlite } from '@electric-sql/pglite';
2
+ import type { QueryablePool } from 'pg-cache';
3
+ /**
4
+ * Build a `QueryablePool` backed by an existing PGlite instance.
5
+ *
6
+ * PGlite is a single in-process engine (one session), so both `pool.query` and
7
+ * the client returned by `connect()` funnel to the same instance. That is
8
+ * exactly what lets pgpm's transactional deploy work: the `BEGIN` opened on the
9
+ * connected client and the `isDeployed()` check issued via `pool.query` share
10
+ * one session, so there is no cross-connection deadlock (the failure mode of a
11
+ * multi-connection pool against a single-connection backend).
12
+ *
13
+ * `end()` intentionally does NOT close the PGlite instance: the same instance
14
+ * may back several cached pool wrappers (pg-cache keys by database name), and
15
+ * its lifecycle is owned by whoever created it (see `registerPglite`).
16
+ */
17
+ export declare const createPglitePool: (db: PGlite) => QueryablePool;
package/pool.js ADDED
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPglitePool = void 0;
4
+ const runner_1 = require("./runner");
5
+ /**
6
+ * Build a `QueryablePool` backed by an existing PGlite instance.
7
+ *
8
+ * PGlite is a single in-process engine (one session), so both `pool.query` and
9
+ * the client returned by `connect()` funnel to the same instance. That is
10
+ * exactly what lets pgpm's transactional deploy work: the `BEGIN` opened on the
11
+ * connected client and the `isDeployed()` check issued via `pool.query` share
12
+ * one session, so there is no cross-connection deadlock (the failure mode of a
13
+ * multi-connection pool against a single-connection backend).
14
+ *
15
+ * `end()` intentionally does NOT close the PGlite instance: the same instance
16
+ * may back several cached pool wrappers (pg-cache keys by database name), and
17
+ * its lifecycle is owned by whoever created it (see `registerPglite`).
18
+ */
19
+ const createPglitePool = (db) => {
20
+ let ended = false;
21
+ const query = (0, runner_1.boundQuery)(db);
22
+ const client = {
23
+ query,
24
+ release: () => { }
25
+ };
26
+ const pool = {
27
+ get ended() {
28
+ return ended;
29
+ },
30
+ query,
31
+ connect: async () => client,
32
+ end: async () => {
33
+ ended = true;
34
+ }
35
+ };
36
+ return pool;
37
+ };
38
+ exports.createPglitePool = createPglitePool;
package/runner.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { PGlite } from '@electric-sql/pglite';
2
+ /**
3
+ * A node-`pg`-shaped result. pgpm / pgsql-client read `rows` and (occasionally)
4
+ * `rowCount`, so we normalize PGlite's `{ rows, affectedRows, fields }` onto that
5
+ * shape.
6
+ */
7
+ export interface PgResultLike {
8
+ rows: any[];
9
+ rowCount: number;
10
+ fields: any[];
11
+ }
12
+ /**
13
+ * Run a statement against the single in-process PGlite engine.
14
+ *
15
+ * PGlite mirrors node-`pg`'s two protocols:
16
+ * - parameterized (`$1`, ...) -> `db.query` (extended protocol, single statement)
17
+ * - no parameters -> `db.exec` (simple protocol, supports the multi-statement
18
+ * bootstrap SQL pgpm runs in `initialize()`), returning the last result.
19
+ */
20
+ export declare const run: (db: PGlite, text: string, values?: any[]) => Promise<PgResultLike>;
21
+ /** Normalize the two call shapes pg accepts: `(text, values)` or `({ text, values })`. */
22
+ export declare const normalize: (first: any, second?: any[]) => {
23
+ text: string;
24
+ values?: any[];
25
+ };
26
+ /** A bound `(text, values | { text, values })` query function over a PGlite instance. */
27
+ export declare const boundQuery: (db: PGlite) => (text: any, values?: any[]) => Promise<PgResultLike>;
package/runner.js ADDED
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.boundQuery = exports.normalize = exports.run = void 0;
4
+ const toResult = (r) => {
5
+ const rows = r?.rows ?? [];
6
+ const rowCount = typeof r?.affectedRows === 'number' ? r.affectedRows : rows.length;
7
+ return { rows, rowCount, fields: r?.fields ?? [] };
8
+ };
9
+ /**
10
+ * Run a statement against the single in-process PGlite engine.
11
+ *
12
+ * PGlite mirrors node-`pg`'s two protocols:
13
+ * - parameterized (`$1`, ...) -> `db.query` (extended protocol, single statement)
14
+ * - no parameters -> `db.exec` (simple protocol, supports the multi-statement
15
+ * bootstrap SQL pgpm runs in `initialize()`), returning the last result.
16
+ */
17
+ const run = async (db, text, values) => {
18
+ if (values && values.length > 0) {
19
+ return toResult(await db.query(text, values));
20
+ }
21
+ const results = await db.exec(text);
22
+ const last = Array.isArray(results) && results.length ? results[results.length - 1] : undefined;
23
+ return toResult(last);
24
+ };
25
+ exports.run = run;
26
+ /** Normalize the two call shapes pg accepts: `(text, values)` or `({ text, values })`. */
27
+ const normalize = (first, second) => {
28
+ if (first && typeof first === 'object' && 'text' in first) {
29
+ return { text: first.text, values: first.values };
30
+ }
31
+ return { text: first, values: second };
32
+ };
33
+ exports.normalize = normalize;
34
+ /** A bound `(text, values | { text, values })` query function over a PGlite instance. */
35
+ const boundQuery = (db) => (text, values) => {
36
+ const q = (0, exports.normalize)(text, values);
37
+ return (0, exports.run)(db, q.text, q.values);
38
+ };
39
+ exports.boundQuery = boundQuery;