prisma-pglite-bridge 0.4.1 → 0.5.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/README.md +305 -56
- package/dist/index.cjs +917 -387
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +310 -81
- package/dist/index.d.mts +311 -81
- package/dist/index.mjs +915 -388
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -8
package/README.md
CHANGED
|
@@ -15,88 +15,129 @@ pnpm add -D prisma-pglite-bridge @electric-sql/pglite @prisma/adapter-pg pg
|
|
|
15
15
|
The last three are peer dependencies you may already have.
|
|
16
16
|
TypeScript users also need `@types/pg`.
|
|
17
17
|
|
|
18
|
+
### Bridge fs-sync policy
|
|
19
|
+
|
|
20
|
+
The adapter defaults `syncToFs` to `'auto'`:
|
|
21
|
+
|
|
22
|
+
- in-memory PGlite (`new PGlite()` or `memory://...`) resolves to `false`
|
|
23
|
+
- persistent `dataDir` usage resolves to `true`
|
|
24
|
+
|
|
25
|
+
That keeps bridge-heavy test workloads on the lower-memory fast path
|
|
26
|
+
without changing durability defaults for persistent databases.
|
|
27
|
+
If you use a custom `fs`, set `syncToFs` explicitly because the
|
|
28
|
+
adapter cannot infer whether that storage is durable.
|
|
29
|
+
|
|
18
30
|
## Quickstart
|
|
19
31
|
|
|
20
32
|
```typescript
|
|
33
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
21
34
|
import { createPgliteAdapter } from 'prisma-pglite-bridge';
|
|
22
35
|
import { PrismaClient } from '@prisma/client';
|
|
23
36
|
|
|
24
|
-
const
|
|
37
|
+
const pglite = new PGlite();
|
|
38
|
+
const { adapter, resetDb } = await createPgliteAdapter({
|
|
39
|
+
pglite,
|
|
40
|
+
migrationsPath: './prisma/migrations',
|
|
41
|
+
});
|
|
25
42
|
const prisma = new PrismaClient({ adapter });
|
|
26
43
|
|
|
27
|
-
// Per-test isolation (optional)
|
|
28
44
|
beforeEach(() => resetDb());
|
|
29
45
|
```
|
|
30
46
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
Call `resetDb()` in `beforeEach` to wipe all data between tests.
|
|
48
|
+
Skip it if your tests are read-only or you want state to carry
|
|
49
|
+
over.
|
|
50
|
+
|
|
51
|
+
That's it. Run `prisma migrate dev` first to generate migration
|
|
52
|
+
files. No Docker, no database server — works in GitHub Actions,
|
|
53
|
+
GitLab CI, and any environment where Node.js runs.
|
|
36
54
|
|
|
37
55
|
## Schema Resolution
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
When you pass any of `sql`, `migrationsPath`, or `configRoot`,
|
|
58
|
+
`createPgliteAdapter` applies schema SQL. Resolution order:
|
|
40
59
|
|
|
41
60
|
1. **`sql` option** — pre-generated SQL string, applied directly
|
|
42
61
|
2. **`migrationsPath` option** — reads migration files from the
|
|
43
62
|
given directory
|
|
44
63
|
3. **Auto-discovered migrations** — uses `@prisma/config` to find
|
|
45
|
-
migration files (same resolution as `prisma migrate dev`)
|
|
46
|
-
Requires `prisma` to be
|
|
47
|
-
`@prisma/config` as a transitive
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
migration files (same resolution as `prisma migrate dev`),
|
|
65
|
+
triggered by passing `configRoot`. Requires `prisma` to be
|
|
66
|
+
installed (which provides `@prisma/config` as a transitive
|
|
67
|
+
dependency).
|
|
68
|
+
|
|
69
|
+
When none of these options is provided, no SQL is applied — the
|
|
70
|
+
PGlite instance is assumed to already hold the schema (useful
|
|
71
|
+
for reopening a persistent `dataDir`).
|
|
72
|
+
|
|
73
|
+
Schema SQL — whether inline via `sql` or loaded from `migrationsPath`
|
|
74
|
+
— is executed verbatim with no checksum or signature verification.
|
|
75
|
+
Ensure the source is trusted and version-controlled. Do not compose
|
|
76
|
+
it from environment variables, network input, or any value that
|
|
77
|
+
crosses a trust boundary, and keep the migrations directory
|
|
78
|
+
writable only by trusted processes.
|
|
51
79
|
|
|
52
80
|
## API
|
|
53
81
|
|
|
54
|
-
### `createPgliteAdapter(options
|
|
82
|
+
### `createPgliteAdapter(options)`
|
|
55
83
|
|
|
56
|
-
Creates a Prisma adapter backed by
|
|
84
|
+
Creates a Prisma adapter backed by a caller-supplied PGlite
|
|
85
|
+
instance.
|
|
57
86
|
|
|
58
87
|
```typescript
|
|
59
|
-
const
|
|
60
|
-
|
|
88
|
+
const pglite = new PGlite(/* dataDir, extensions, ... */);
|
|
89
|
+
|
|
90
|
+
const { adapter, resetDb, close, stats } = await createPgliteAdapter({
|
|
91
|
+
pglite, // required — caller owns lifecycle
|
|
61
92
|
migrationsPath: './prisma/migrations', // or:
|
|
62
93
|
sql: 'CREATE TABLE ...', // (first match wins, see Schema Resolution)
|
|
63
94
|
configRoot: '../..', // monorepo: where to find prisma.config.ts
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
max: 5, // pool connections (default: 5)
|
|
95
|
+
max: 1, // pool connections (default: 1, see "Pool sizing" below)
|
|
96
|
+
statsLevel: 'off', // 'off' | 'basic' | 'full' (default: 'off')
|
|
67
97
|
});
|
|
68
98
|
```
|
|
69
99
|
|
|
70
100
|
Returns:
|
|
71
101
|
|
|
72
102
|
- `adapter` — pass to `new PrismaClient({ adapter })`
|
|
73
|
-
- `
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- `close()` — shuts down pool
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
- `resetDb()` — truncates all user tables and discards
|
|
104
|
+
session-local state via `DISCARD ALL` (for example `SET`
|
|
105
|
+
variables, prepared statements, temp tables, and `LISTEN`
|
|
106
|
+
registrations). Call in `beforeEach` for per-test isolation.
|
|
107
|
+
Note: this clears all data including seed data — re-seed after
|
|
108
|
+
reset if needed.
|
|
109
|
+
- `close()` — shuts down the pool. The caller-supplied PGlite
|
|
110
|
+
instance is not closed — you own its lifecycle. Not needed in
|
|
111
|
+
tests (process exit handles it); use in long-running scripts
|
|
112
|
+
or dev servers.
|
|
113
|
+
- `stats()` — returns telemetry when `statsLevel` is `'basic'` or
|
|
114
|
+
`'full'`, else `undefined`. See [Stats collection](#stats-collection).
|
|
115
|
+
- `adapterId` — a unique `symbol` identifying this adapter. Use it
|
|
116
|
+
to filter events from the public
|
|
117
|
+
[diagnostics channels](#diagnostics-channels) when multiple
|
|
118
|
+
adapters share a process.
|
|
119
|
+
|
|
120
|
+
### `createPool(options)`
|
|
84
121
|
|
|
85
122
|
Lower-level escape hatch. Creates a `pg.Pool` backed by PGlite
|
|
86
|
-
without
|
|
87
|
-
|
|
123
|
+
without schema handling — useful for custom Prisma setups,
|
|
124
|
+
other ORMs, or raw SQL.
|
|
88
125
|
|
|
89
126
|
```typescript
|
|
127
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
90
128
|
import { createPool } from 'prisma-pglite-bridge';
|
|
91
129
|
import { PrismaPg } from '@prisma/adapter-pg';
|
|
92
130
|
|
|
93
|
-
const
|
|
131
|
+
const pglite = new PGlite();
|
|
132
|
+
const { pool, close } = await createPool({ pglite });
|
|
94
133
|
const adapter = new PrismaPg(pool);
|
|
95
134
|
```
|
|
96
135
|
|
|
97
|
-
Returns `pool` (pg.Pool), `
|
|
98
|
-
|
|
99
|
-
|
|
136
|
+
Returns `pool` (pg.Pool), `adapterId` (a unique `symbol` for
|
|
137
|
+
[diagnostics channel](#diagnostics-channels) filtering), and
|
|
138
|
+
`close()` (which shuts down the pool only — the caller-supplied
|
|
139
|
+
PGlite instance is not closed). Accepts `pglite` (required),
|
|
140
|
+
`max`, and `adapterId`.
|
|
100
141
|
|
|
101
142
|
### `PGliteBridge`
|
|
102
143
|
|
|
@@ -142,10 +183,15 @@ the in-memory PGlite version:
|
|
|
142
183
|
|
|
143
184
|
```typescript
|
|
144
185
|
// vitest.setup.ts
|
|
186
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
145
187
|
import { createPgliteAdapter } from 'prisma-pglite-bridge';
|
|
146
188
|
import { PrismaClient } from '@prisma/client';
|
|
147
189
|
|
|
148
|
-
const
|
|
190
|
+
const pglite = new PGlite();
|
|
191
|
+
const { adapter, resetDb } = await createPgliteAdapter({
|
|
192
|
+
pglite,
|
|
193
|
+
migrationsPath: './prisma/migrations',
|
|
194
|
+
});
|
|
149
195
|
export const testPrisma = new PrismaClient({ adapter });
|
|
150
196
|
|
|
151
197
|
vi.mock('./lib/prisma', () => ({ prisma: testPrisma }));
|
|
@@ -172,6 +218,7 @@ the top level, not inside `beforeAll`:
|
|
|
172
218
|
|
|
173
219
|
```typescript
|
|
174
220
|
// jest.setup.ts
|
|
221
|
+
const { PGlite } = require('@electric-sql/pglite');
|
|
175
222
|
const { createPgliteAdapter } = require('prisma-pglite-bridge');
|
|
176
223
|
const { PrismaClient } = require('@prisma/client');
|
|
177
224
|
|
|
@@ -183,7 +230,11 @@ jest.mock('./lib/prisma', () => ({
|
|
|
183
230
|
}));
|
|
184
231
|
|
|
185
232
|
beforeAll(async () => {
|
|
186
|
-
const
|
|
233
|
+
const pglite = new PGlite();
|
|
234
|
+
const result = await createPgliteAdapter({
|
|
235
|
+
pglite,
|
|
236
|
+
migrationsPath: './prisma/migrations',
|
|
237
|
+
});
|
|
187
238
|
testPrisma = new PrismaClient({ adapter: result.adapter });
|
|
188
239
|
resetDb = result.resetDb;
|
|
189
240
|
});
|
|
@@ -196,6 +247,7 @@ beforeEach(() => resetDb());
|
|
|
196
247
|
If your code accepts `PrismaClient` as a parameter:
|
|
197
248
|
|
|
198
249
|
```typescript
|
|
250
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
199
251
|
import { createPgliteAdapter, type ResetDbFn } from 'prisma-pglite-bridge';
|
|
200
252
|
import { PrismaClient } from '@prisma/client';
|
|
201
253
|
import { beforeAll, beforeEach, it, expect } from 'vitest';
|
|
@@ -204,7 +256,11 @@ let prisma: PrismaClient;
|
|
|
204
256
|
let resetDb: ResetDbFn;
|
|
205
257
|
|
|
206
258
|
beforeAll(async () => {
|
|
207
|
-
const
|
|
259
|
+
const pglite = new PGlite();
|
|
260
|
+
const result = await createPgliteAdapter({
|
|
261
|
+
pglite,
|
|
262
|
+
migrationsPath: './prisma/migrations',
|
|
263
|
+
});
|
|
208
264
|
prisma = new PrismaClient({ adapter: result.adapter });
|
|
209
265
|
resetDb = result.resetDb;
|
|
210
266
|
});
|
|
@@ -243,6 +299,7 @@ if (import.meta.url === new URL(process.argv[1]!, 'file:').href) {
|
|
|
243
299
|
Then reuse it in tests:
|
|
244
300
|
|
|
245
301
|
```typescript
|
|
302
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
246
303
|
import { createPgliteAdapter, type ResetDbFn } from 'prisma-pglite-bridge';
|
|
247
304
|
import { PrismaClient } from '@prisma/client';
|
|
248
305
|
import { seed } from '../prisma/seed';
|
|
@@ -251,7 +308,11 @@ let prisma: PrismaClient;
|
|
|
251
308
|
let resetDb: ResetDbFn;
|
|
252
309
|
|
|
253
310
|
beforeAll(async () => {
|
|
254
|
-
const
|
|
311
|
+
const pglite = new PGlite();
|
|
312
|
+
const result = await createPgliteAdapter({
|
|
313
|
+
pglite,
|
|
314
|
+
migrationsPath: './prisma/migrations',
|
|
315
|
+
});
|
|
255
316
|
prisma = new PrismaClient({ adapter: result.adapter });
|
|
256
317
|
resetDb = result.resetDb;
|
|
257
318
|
await seed(prisma);
|
|
@@ -270,12 +331,15 @@ If your schema uses `uuid-ossp`, `pgcrypto`, or other extensions,
|
|
|
270
331
|
pass them via the `extensions` option:
|
|
271
332
|
|
|
272
333
|
```typescript
|
|
334
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
273
335
|
import { createPgliteAdapter } from 'prisma-pglite-bridge';
|
|
274
336
|
import { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp';
|
|
275
337
|
import { pgcrypto } from '@electric-sql/pglite/contrib/pgcrypto';
|
|
276
338
|
|
|
339
|
+
const pglite = new PGlite({ extensions: { uuid_ossp, pgcrypto } });
|
|
277
340
|
const { adapter } = await createPgliteAdapter({
|
|
278
|
-
|
|
341
|
+
pglite,
|
|
342
|
+
migrationsPath: './prisma/migrations',
|
|
279
343
|
});
|
|
280
344
|
```
|
|
281
345
|
|
|
@@ -285,8 +349,18 @@ for the full list.
|
|
|
285
349
|
|
|
286
350
|
### Pre-generated SQL (fastest)
|
|
287
351
|
|
|
352
|
+
The `sql` option runs verbatim with no sandbox or checksum. Compose
|
|
353
|
+
it from trusted, version-controlled source only — never from
|
|
354
|
+
environment variables, network input, or values that cross a trust
|
|
355
|
+
boundary. See [Schema Resolution](#schema-resolution) for the
|
|
356
|
+
full source-of-trust guidance.
|
|
357
|
+
|
|
288
358
|
```typescript
|
|
359
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
360
|
+
|
|
361
|
+
const pglite = new PGlite();
|
|
289
362
|
const { adapter } = await createPgliteAdapter({
|
|
363
|
+
pglite,
|
|
290
364
|
sql: `
|
|
291
365
|
CREATE TABLE "User" (id text PRIMARY KEY, name text NOT NULL);
|
|
292
366
|
CREATE TABLE "Post" (
|
|
@@ -300,30 +374,43 @@ const { adapter } = await createPgliteAdapter({
|
|
|
300
374
|
|
|
301
375
|
### Persistent dev database (optional)
|
|
302
376
|
|
|
303
|
-
By default,
|
|
377
|
+
By default, PGlite runs entirely in memory — the database
|
|
304
378
|
disappears when the process exits. This is ideal for tests. If you
|
|
305
379
|
want data to survive restarts (local development, prototyping),
|
|
306
|
-
pass a `dataDir
|
|
380
|
+
pass a `dataDir` when constructing PGlite, and only apply
|
|
381
|
+
migrations on first run:
|
|
307
382
|
|
|
308
383
|
```typescript
|
|
384
|
+
import { existsSync } from 'node:fs';
|
|
385
|
+
import { join } from 'node:path';
|
|
386
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
387
|
+
|
|
388
|
+
const dataDir = './data/pglite';
|
|
389
|
+
const firstRun = !existsSync(join(dataDir, 'PG_VERSION'));
|
|
390
|
+
|
|
391
|
+
const pglite = new PGlite(dataDir);
|
|
309
392
|
const { adapter, close } = await createPgliteAdapter({
|
|
310
|
-
|
|
393
|
+
pglite,
|
|
394
|
+
...(firstRun ? { migrationsPath: './prisma/migrations' } : {}),
|
|
311
395
|
});
|
|
312
396
|
const prisma = new PrismaClient({ adapter });
|
|
313
|
-
|
|
314
|
-
// Data persists across restarts. Schema is only applied on first run
|
|
315
|
-
// (PGlite detects an existing PGDATA directory). Delete the data
|
|
316
|
-
// directory after schema changes to pick up new migrations.
|
|
317
397
|
```
|
|
318
398
|
|
|
319
|
-
**Add `data/pglite/` to `.gitignore`.**
|
|
320
|
-
|
|
321
|
-
|
|
399
|
+
**Add `data/pglite/` to `.gitignore`.** Delete the data directory
|
|
400
|
+
after schema changes to pick up new migrations. This gives you a
|
|
401
|
+
local PostgreSQL without Docker — useful for offline development
|
|
402
|
+
or environments where installing PostgreSQL is impractical.
|
|
322
403
|
|
|
323
404
|
### Long-running script with clean shutdown
|
|
324
405
|
|
|
325
406
|
```typescript
|
|
326
|
-
|
|
407
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
408
|
+
|
|
409
|
+
const pglite = new PGlite();
|
|
410
|
+
const { adapter, close } = await createPgliteAdapter({
|
|
411
|
+
pglite,
|
|
412
|
+
migrationsPath: './prisma/migrations',
|
|
413
|
+
});
|
|
327
414
|
const prisma = new PrismaClient({ adapter });
|
|
328
415
|
|
|
329
416
|
try {
|
|
@@ -331,9 +418,141 @@ try {
|
|
|
331
418
|
} finally {
|
|
332
419
|
await prisma.$disconnect();
|
|
333
420
|
await close();
|
|
421
|
+
await pglite.close();
|
|
334
422
|
}
|
|
335
423
|
```
|
|
336
424
|
|
|
425
|
+
## Stats collection
|
|
426
|
+
|
|
427
|
+
For most developers, this is the easiest way to see how the bridge
|
|
428
|
+
performed in tests.
|
|
429
|
+
|
|
430
|
+
Enable `statsLevel` when creating the adapter, run your tests, then
|
|
431
|
+
call `await stats()` at the end. You get one snapshot with the main
|
|
432
|
+
things you usually care about: query counts, timing percentiles,
|
|
433
|
+
database size, and, at `'full'`, process RSS and session-lock wait
|
|
434
|
+
times.
|
|
435
|
+
|
|
436
|
+
This is the built-in, low-friction path for test diagnostics. It is
|
|
437
|
+
useful for CI cost insight, perf tuning, and understanding test-suite
|
|
438
|
+
behavior without wiring up a separate metrics pipeline. **Off by
|
|
439
|
+
default**; the hot path stays effectively zero-cost as long as no
|
|
440
|
+
external consumer subscribes to the public
|
|
441
|
+
[diagnostics channels](#diagnostics-channels).
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
445
|
+
|
|
446
|
+
const pglite = new PGlite();
|
|
447
|
+
const { adapter, stats, close } = await createPgliteAdapter({
|
|
448
|
+
pglite,
|
|
449
|
+
migrationsPath: './prisma/migrations',
|
|
450
|
+
statsLevel: 'basic', // or 'full'
|
|
451
|
+
});
|
|
452
|
+
const prisma = new PrismaClient({ adapter });
|
|
453
|
+
|
|
454
|
+
afterAll(async () => {
|
|
455
|
+
await prisma.$disconnect();
|
|
456
|
+
await close();
|
|
457
|
+
const s = await stats();
|
|
458
|
+
if (s) console.log(s);
|
|
459
|
+
await pglite.close();
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`stats()` returns `Promise<Stats | undefined>` — `undefined` when
|
|
464
|
+
`statsLevel` is `'off'` (or omitted). Safe to call before or after
|
|
465
|
+
`close()`; post-close reads return frozen values from the moment
|
|
466
|
+
`close()` was invoked.
|
|
467
|
+
|
|
468
|
+
If you need live per-query or per-lock-wait events instead of a final
|
|
469
|
+
snapshot, use the public [diagnostics channels](#diagnostics-channels)
|
|
470
|
+
described below. That path is more flexible, but also more advanced.
|
|
471
|
+
|
|
472
|
+
### Levels
|
|
473
|
+
|
|
474
|
+
**`'basic'`** — timing and counters:
|
|
475
|
+
|
|
476
|
+
- `durationMs` — adapter lifetime (frozen at `close()`, drain
|
|
477
|
+
excluded)
|
|
478
|
+
- `schemaSetupMs` — one-time cost of applying migration SQL
|
|
479
|
+
- `queryCount`, `failedQueryCount` — WASM round-trips (a Prisma
|
|
480
|
+
extended-query pipeline is one round-trip, not five). Lifetime
|
|
481
|
+
counters.
|
|
482
|
+
- `totalQueryMs`, `avgQueryMs` — lifetime sum and mean of query
|
|
483
|
+
durations
|
|
484
|
+
- `recentP50QueryMs`, `recentP95QueryMs`, `recentMaxQueryMs` —
|
|
485
|
+
nearest-rank percentiles (no interpolation) over the most recent
|
|
486
|
+
~10,000 queries. On long-lived adapters these describe a different
|
|
487
|
+
population than `avgQueryMs`.
|
|
488
|
+
- `resetDbCalls` — counts `resetDb()` attempts
|
|
489
|
+
- `dbSizeBytes` — `pg_database_size(current_database())`, cached
|
|
490
|
+
at close
|
|
491
|
+
|
|
492
|
+
**`'full'`** — adds:
|
|
493
|
+
|
|
494
|
+
- `processRssPeakBytes` — process-wide RSS peak, read from
|
|
495
|
+
`process.resourceUsage().maxRSS` (kernel-tracked, lossless) at
|
|
496
|
+
the moment `stats()` is called. Contaminated if unrelated work
|
|
497
|
+
shares the process — use as an ordering signal, not an absolute
|
|
498
|
+
measurement. `undefined` on runtimes without
|
|
499
|
+
`process.resourceUsage` (Bun, Deno, edge workers).
|
|
500
|
+
- `totalSessionLockWaitMs`, `sessionLockAcquisitionCount`,
|
|
501
|
+
`avgSessionLockWaitMs`, `maxSessionLockWaitMs` — session-lock
|
|
502
|
+
contention across pool connections
|
|
503
|
+
|
|
504
|
+
`statsLevel` is echoed on the returned object. Any field typed
|
|
505
|
+
`T | undefined` in the returned `Stats` is the exhaustive list of
|
|
506
|
+
fields that can be missing — `dbSizeBytes` if `pg_database_size`
|
|
507
|
+
rejects, `processRssPeakBytes` on runtimes without
|
|
508
|
+
`process.resourceUsage`. Every other field is always defined.
|
|
509
|
+
|
|
510
|
+
## Diagnostics channels
|
|
511
|
+
|
|
512
|
+
The bridge publishes per-query and per-lock-wait events to
|
|
513
|
+
[`node:diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html)
|
|
514
|
+
channels. Built-in adapter stats are updated directly by the bridge
|
|
515
|
+
when `statsLevel` is `'basic'` or `'full'`; external consumers (OpenTelemetry, APM,
|
|
516
|
+
custom loggers) can subscribe directly without touching the bridge
|
|
517
|
+
API.
|
|
518
|
+
|
|
519
|
+
Publication is gated by `channel.hasSubscribers`, so when nobody
|
|
520
|
+
is listening the hot path pays no timing or payload cost.
|
|
521
|
+
Subscribing opts you in to that work.
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import diagnostics_channel from 'node:diagnostics_channel';
|
|
525
|
+
import {
|
|
526
|
+
createPgliteAdapter,
|
|
527
|
+
QUERY_CHANNEL,
|
|
528
|
+
type QueryEvent,
|
|
529
|
+
} from 'prisma-pglite-bridge';
|
|
530
|
+
|
|
531
|
+
const { adapterId } = await createPgliteAdapter({ /* ... */ });
|
|
532
|
+
|
|
533
|
+
const listener = (msg: unknown) => {
|
|
534
|
+
const e = msg as QueryEvent;
|
|
535
|
+
if (e.adapterId !== adapterId) return;
|
|
536
|
+
myMetrics.record('db.query', e.durationMs, { ok: e.succeeded });
|
|
537
|
+
};
|
|
538
|
+
diagnostics_channel.channel(QUERY_CHANNEL).subscribe(listener);
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Channels:
|
|
542
|
+
|
|
543
|
+
- `QUERY_CHANNEL` (`prisma-pglite-bridge:query`) — every
|
|
544
|
+
whole-query boundary. Payload: `{ adapterId: symbol; durationMs:
|
|
545
|
+
number; succeeded: boolean }`. `succeeded` is `false` for both
|
|
546
|
+
thrown errors and protocol-level `ErrorResponse` frames.
|
|
547
|
+
- `LOCK_WAIT_CHANNEL` (`prisma-pglite-bridge:lock-wait`) — every
|
|
548
|
+
session-lock acquisition. Payload: `{ adapterId: symbol;
|
|
549
|
+
durationMs: number }`. `durationMs` is how long the acquirer
|
|
550
|
+
waited before the lock was granted.
|
|
551
|
+
|
|
552
|
+
Filter on `adapterId` to isolate events when multiple adapters
|
|
553
|
+
share a process. Obtain it from the `createPgliteAdapter()` or
|
|
554
|
+
`createPool()` return value.
|
|
555
|
+
|
|
337
556
|
## Limitations
|
|
338
557
|
|
|
339
558
|
- **Node.js 20+ only** — requires `node:stream` and `node:fs`.
|
|
@@ -344,12 +563,42 @@ try {
|
|
|
344
563
|
- **Single PostgreSQL session** — PGlite runs in single-user mode.
|
|
345
564
|
All pool connections share one session. A `SessionLock` serializes
|
|
346
565
|
transactions (one at a time), but `SET` variables leak between
|
|
347
|
-
connections within a single test. `resetDb()` clears this
|
|
348
|
-
tests via `
|
|
566
|
+
connections within a single test. `resetDb()` clears more of this
|
|
567
|
+
between tests via `DISCARD ALL`.
|
|
349
568
|
- **Migration files required** — run `prisma migrate dev` once to
|
|
350
569
|
generate migration files, or pass schema SQL directly via the
|
|
351
570
|
`sql` option.
|
|
352
571
|
|
|
572
|
+
## Troubleshooting
|
|
573
|
+
|
|
574
|
+
### `this.pglite.execProtocolRawStream is not a function`
|
|
575
|
+
|
|
576
|
+
The bridge uses PGlite 0.4's streaming protocol API. Some packages
|
|
577
|
+
in the Prisma ecosystem (e.g. `@prisma/dev`) still pin
|
|
578
|
+
`@electric-sql/pglite` to 0.3.x, which pnpm will install alongside
|
|
579
|
+
0.4 — and the bridge can end up with the older copy.
|
|
580
|
+
|
|
581
|
+
Check your tree:
|
|
582
|
+
|
|
583
|
+
```sh
|
|
584
|
+
pnpm why @electric-sql/pglite
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
If you see more than one version, force a single 0.4.x via
|
|
588
|
+
`pnpm.overrides` in your project's `package.json`:
|
|
589
|
+
|
|
590
|
+
```json
|
|
591
|
+
{
|
|
592
|
+
"pnpm": {
|
|
593
|
+
"overrides": {
|
|
594
|
+
"@electric-sql/pglite": "^0.4.4"
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Then `pnpm install`.
|
|
601
|
+
|
|
353
602
|
## License
|
|
354
603
|
|
|
355
604
|
MIT
|