prisma-pglite-bridge 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,345 @@
1
+ # prisma-pglite-bridge
2
+
3
+ In-process PGlite bridge for Prisma. Replaces the TCP socket in
4
+ `pg.Client` with a Duplex stream that speaks PostgreSQL wire protocol
5
+ directly to PGlite's WASM engine.
6
+
7
+ ## Install
8
+
9
+ Requires **Prisma 7+** and **Node.js 20+**.
10
+
11
+ ```sh
12
+ pnpm add -D prisma-pglite-bridge @electric-sql/pglite @prisma/adapter-pg pg
13
+ ```
14
+
15
+ TypeScript users also need `@types/pg`.
16
+
17
+ ## Quickstart
18
+
19
+ ```typescript
20
+ import { createPgliteAdapter } from 'prisma-pglite-bridge';
21
+ import { PrismaClient } from '@prisma/client';
22
+
23
+ const { adapter, resetDb } = await createPgliteAdapter();
24
+ const prisma = new PrismaClient({ adapter });
25
+
26
+ // Per-test isolation (optional)
27
+ beforeEach(() => resetDb());
28
+ ```
29
+
30
+ That's it. Schema is auto-discovered from `prisma.config.ts`
31
+ and migration files. No Docker, no database server — works
32
+ in GitHub Actions, GitLab CI, and any environment where
33
+ Node.js runs.
34
+
35
+ ## Schema Resolution
36
+
37
+ `createPgliteAdapter()` resolves schema SQL in this order:
38
+
39
+ 1. **`sql` option** — pre-generated SQL string, applied directly
40
+ 2. **`migrationsPath` option** — reads migration files from the
41
+ given directory
42
+ 3. **Auto-discovered migrations** — uses `@prisma/config` to find
43
+ migration files (same resolution as `prisma migrate dev`).
44
+ Requires `prisma` to be installed (which provides
45
+ `@prisma/config` as a transitive dependency).
46
+
47
+ If no migration files are found, it throws with a message to run
48
+ `prisma migrate dev` first.
49
+
50
+ ## API
51
+
52
+ ### `createPgliteAdapter(options?)`
53
+
54
+ Creates a Prisma adapter backed by an in-process PGlite instance.
55
+
56
+ ```typescript
57
+ const { adapter, pglite, resetDb, close } = await createPgliteAdapter({
58
+ // All optional — migrations auto-discovered from prisma.config.ts
59
+ migrationsPath: './prisma/migrations',
60
+ sql: 'CREATE TABLE ...',
61
+ configRoot: '../..', // monorepo: where to find prisma.config.ts
62
+ dataDir: './data/pglite', // omit for in-memory
63
+ extensions: {}, // PGlite extensions
64
+ max: 5, // pool connections (default: 5)
65
+ });
66
+ ```
67
+
68
+ Returns:
69
+
70
+ - `adapter` — pass to `new PrismaClient({ adapter })`
71
+ - `pglite` — the underlying PGlite instance for direct SQL,
72
+ snapshots, or extension access
73
+ - `resetDb()` — truncates all user tables, resets session state
74
+ (`RESET ALL`, `DEALLOCATE ALL`). Call in `beforeEach` for
75
+ per-test isolation. Note: this clears all data including seed
76
+ data — re-seed after reset if needed.
77
+ - `close()` — shuts down pool and PGlite. Not needed in tests
78
+ (process exit handles it). Use in long-running scripts or dev
79
+ servers.
80
+
81
+ ### `createPool(options?)`
82
+
83
+ Lower-level escape hatch. Creates a `pg.Pool` backed by PGlite
84
+ without Prisma wiring.
85
+
86
+ ```typescript
87
+ import { createPool } from 'prisma-pglite-bridge';
88
+ import { PrismaPg } from '@prisma/adapter-pg';
89
+
90
+ const { pool, pglite, close } = await createPool();
91
+ const adapter = new PrismaPg(pool);
92
+ ```
93
+
94
+ Returns `pool` (pg.Pool), `pglite` (the underlying PGlite
95
+ instance), and `close()`. Accepts `dataDir`, `extensions`, `max`,
96
+ and `pglite` (bring your own pre-configured PGlite instance).
97
+
98
+ ### `PGliteBridge`
99
+
100
+ The Duplex stream that replaces `pg.Client`'s network socket.
101
+ Exported for advanced use cases (custom `pg.Client` setup, direct
102
+ wire protocol access). When using multiple bridges against the
103
+ same PGlite instance, pass a shared `SessionLock` to prevent
104
+ transaction interleaving.
105
+
106
+ ```typescript
107
+ import { PGliteBridge } from 'prisma-pglite-bridge';
108
+ import { PGlite } from '@electric-sql/pglite';
109
+ import pg from 'pg';
110
+
111
+ const pglite = new PGlite();
112
+ await pglite.waitReady;
113
+ const client = new pg.Client({
114
+ stream: () => new PGliteBridge(pglite),
115
+ });
116
+ ```
117
+
118
+ ## Examples
119
+
120
+ ### Replacing your production database in tests
121
+
122
+ Most Prisma projects use a singleton module:
123
+
124
+ ```typescript
125
+ // lib/prisma.ts — your production singleton
126
+ import { PrismaPg } from '@prisma/adapter-pg';
127
+ import { PrismaClient } from '@prisma/client';
128
+ import pg from 'pg';
129
+
130
+ const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
131
+ const adapter = new PrismaPg(pool);
132
+ export const prisma = new PrismaClient({ adapter });
133
+ ```
134
+
135
+ In tests, swap the singleton via `vi.mock` so every import gets
136
+ the in-memory PGlite version:
137
+
138
+ ```typescript
139
+ // vitest.setup.ts
140
+ import { createPgliteAdapter } from 'prisma-pglite-bridge';
141
+ import { PrismaClient } from '@prisma/client';
142
+
143
+ const { adapter, resetDb } = await createPgliteAdapter();
144
+ export const testPrisma = new PrismaClient({ adapter });
145
+
146
+ vi.mock('./lib/prisma', () => ({ prisma: testPrisma }));
147
+
148
+ beforeEach(() => resetDb());
149
+ ```
150
+
151
+ ```typescript
152
+ // vitest.config.ts
153
+ export default defineConfig({
154
+ test: {
155
+ setupFiles: ['./vitest.setup.ts'],
156
+ },
157
+ });
158
+ ```
159
+
160
+ Now every test file that imports `prisma` from `lib/prisma`
161
+ gets the PGlite-backed instance. No Docker, no test database,
162
+ no cleanup scripts.
163
+
164
+ For Jest, the same pattern works with `jest.mock`:
165
+
166
+ ```typescript
167
+ // jest.setup.ts
168
+ const { createPgliteAdapter } = require('prisma-pglite-bridge');
169
+ const { PrismaClient } = require('@prisma/client');
170
+
171
+ let testPrisma;
172
+ let resetDb;
173
+
174
+ // jest.mock is hoisted — must be at top level, not inside beforeAll
175
+ jest.mock('./lib/prisma', () => ({
176
+ get prisma() { return testPrisma; },
177
+ }));
178
+
179
+ beforeAll(async () => {
180
+ const result = await createPgliteAdapter();
181
+ testPrisma = new PrismaClient({ adapter: result.adapter });
182
+ resetDb = result.resetDb;
183
+ });
184
+
185
+ beforeEach(() => resetDb());
186
+ ```
187
+
188
+ ### Vitest with per-test isolation (no singleton)
189
+
190
+ If your code accepts `PrismaClient` as a parameter:
191
+
192
+ ```typescript
193
+ import { createPgliteAdapter } from 'prisma-pglite-bridge';
194
+ import { PrismaClient } from '@prisma/client';
195
+ import { beforeAll, beforeEach, it, expect } from 'vitest';
196
+
197
+ let prisma: PrismaClient;
198
+ let resetDb: () => Promise<void>;
199
+
200
+ beforeAll(async () => {
201
+ const { adapter, resetDb: reset } = await createPgliteAdapter();
202
+ prisma = new PrismaClient({ adapter });
203
+ resetDb = reset;
204
+ });
205
+
206
+ beforeEach(() => resetDb());
207
+
208
+ it('creates a user', async () => {
209
+ const user = await prisma.user.create({
210
+ data: { name: 'Test' },
211
+ });
212
+ expect(user.id).toBeDefined();
213
+ });
214
+ ```
215
+
216
+ ### Sharing seed logic between `prisma db seed` and tests
217
+
218
+ Extract your seed logic into a function that accepts a
219
+ PrismaClient:
220
+
221
+ ```typescript
222
+ // prisma/seed.ts
223
+ import { PrismaClient } from '@prisma/client';
224
+
225
+ export const seed = async (prisma: PrismaClient) => {
226
+ await prisma.user.create({ data: { name: 'Alice', role: 'ADMIN' } });
227
+ await prisma.user.create({ data: { name: 'Bob', role: 'MEMBER' } });
228
+ };
229
+
230
+ // Still works as a script for `prisma db seed`
231
+ const prisma = new PrismaClient();
232
+ seed(prisma).then(() => prisma.$disconnect());
233
+ ```
234
+
235
+ Then reuse it in tests:
236
+
237
+ ```typescript
238
+ import { createPgliteAdapter } from 'prisma-pglite-bridge';
239
+ import { PrismaClient } from '@prisma/client';
240
+ import { seed } from '../prisma/seed';
241
+
242
+ let prisma: PrismaClient;
243
+ let resetDb: () => Promise<void>;
244
+
245
+ beforeAll(async () => {
246
+ const { adapter, resetDb: reset } = await createPgliteAdapter();
247
+ prisma = new PrismaClient({ adapter });
248
+ resetDb = reset;
249
+ await seed(prisma);
250
+ });
251
+
252
+ // resetDb() clears all data — re-seed if needed
253
+ beforeEach(async () => {
254
+ await resetDb();
255
+ await seed(prisma);
256
+ });
257
+ ```
258
+
259
+ ### Using PostgreSQL extensions
260
+
261
+ If your schema uses `uuid-ossp`, `pgcrypto`, or other extensions,
262
+ pass them via the `extensions` option:
263
+
264
+ ```typescript
265
+ import { createPgliteAdapter } from 'prisma-pglite-bridge';
266
+ import { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp';
267
+ import { pgcrypto } from '@electric-sql/pglite/contrib/pgcrypto';
268
+
269
+ const { adapter } = await createPgliteAdapter({
270
+ extensions: { uuid_ossp, pgcrypto },
271
+ });
272
+ ```
273
+
274
+ See [PGlite extensions](https://pglite.dev/extensions/) for the
275
+ full list of available extensions.
276
+
277
+ ### Pre-generated SQL (fastest)
278
+
279
+ ```typescript
280
+ const { adapter } = await createPgliteAdapter({
281
+ sql: `
282
+ CREATE TABLE "User" (id text PRIMARY KEY, name text NOT NULL);
283
+ CREATE TABLE "Post" (
284
+ id text PRIMARY KEY,
285
+ title text NOT NULL,
286
+ "userId" text REFERENCES "User"(id)
287
+ );
288
+ `,
289
+ });
290
+ ```
291
+
292
+ ### Persistent dev database (optional)
293
+
294
+ By default, prisma-pglite-bridge runs entirely in memory — the database
295
+ disappears when the process exits. This is ideal for tests. If you
296
+ want data to survive restarts (local development, prototyping),
297
+ pass a `dataDir`:
298
+
299
+ ```typescript
300
+ const { adapter, close } = await createPgliteAdapter({
301
+ dataDir: './data/pglite',
302
+ });
303
+ const prisma = new PrismaClient({ adapter });
304
+
305
+ // Data persists across restarts. Schema is only applied
306
+ // on first run (PGlite detects an existing PGDATA directory).
307
+ ```
308
+
309
+ Add `data/pglite/` to `.gitignore`. This gives you a local
310
+ PostgreSQL without Docker — useful for offline development or
311
+ environments where installing PostgreSQL is impractical.
312
+
313
+ ### Long-running script with clean shutdown
314
+
315
+ ```typescript
316
+ const { adapter, close } = await createPgliteAdapter();
317
+ const prisma = new PrismaClient({ adapter });
318
+
319
+ try {
320
+ await seedDatabase(prisma);
321
+ } finally {
322
+ await prisma.$disconnect();
323
+ await close();
324
+ }
325
+ ```
326
+
327
+ ## Limitations
328
+
329
+ - **Node.js 20+ only** — requires `node:stream` and `node:fs`.
330
+ Does not work in browsers despite PGlite's browser support.
331
+ - **WASM cold start** — first `createPgliteAdapter()` call takes
332
+ ~2s for PGlite WASM compilation. Subsequent calls in the same
333
+ process reuse the compiled module.
334
+ - **Single PostgreSQL session** — PGlite runs in single-user mode.
335
+ All pool connections share one session. A `SessionLock` serializes
336
+ transactions (one at a time), but `SET` variables leak between
337
+ connections within a single test. `resetDb()` clears this between
338
+ tests via `RESET ALL` and `DEALLOCATE ALL`.
339
+ - **Migration files required** — run `prisma migrate dev` once to
340
+ generate migration files, or pass schema SQL directly via the
341
+ `sql` option.
342
+
343
+ ## License
344
+
345
+ MIT