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 +21 -0
- package/README.md +345 -0
- package/dist/index.cjs +561 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +240 -0
- package/dist/index.d.ts +240 -0
- package/dist/index.js +522 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
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
|