prisma-pglite-bridge 0.3.2 → 0.4.1

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/dist/index.cjs CHANGED
@@ -59,6 +59,16 @@ const extractRfqStatus = (response) => {
59
59
  if (response[i] === 90 && response[i + 1] === 0 && response[i + 2] === 0 && response[i + 3] === 0 && response[i + 4] === 5) return response[i + 5] ?? null;
60
60
  return null;
61
61
  };
62
+ /**
63
+ * Coordinates PGlite access across concurrent pool connections.
64
+ *
65
+ * @remarks
66
+ * PGlite runs PostgreSQL in single-user mode — one session shared by all
67
+ * bridges. The session lock tracks which bridge owns the session during
68
+ * transactions, preventing interleaving. Used internally by {@link PGliteBridge}
69
+ * and created automatically by {@link createPool}. Only instantiate directly
70
+ * if building a custom pool setup.
71
+ */
62
72
  var SessionLock = class {
63
73
  owner = null;
64
74
  waitQueue = [];
@@ -67,7 +77,8 @@ var SessionLock = class {
67
77
  * active or if this bridge owns the current transaction. Queues otherwise.
68
78
  */
69
79
  async acquire(id) {
70
- if (this.owner === null || this.owner === id) return;
80
+ if (this.owner === null) return;
81
+ if (this.owner === id) return;
71
82
  return new Promise((resolve) => {
72
83
  this.waitQueue.push({
73
84
  id,
@@ -98,9 +109,10 @@ var SessionLock = class {
98
109
  }
99
110
  }
100
111
  drainWaitQueue() {
101
- const waiters = this.waitQueue;
102
- this.waitQueue = [];
103
- for (const waiter of waiters) waiter.resolve();
112
+ const next = this.waitQueue.shift();
113
+ if (!next) return;
114
+ this.owner = next.id;
115
+ next.resolve();
104
116
  }
105
117
  };
106
118
  //#endregion
@@ -226,7 +238,6 @@ var PGliteBridge = class extends node_stream.Duplex {
226
238
  bridgeId;
227
239
  /** Incoming bytes not yet compacted into buf */
228
240
  pending = [];
229
- pendingLen = 0;
230
241
  /** Compacted input buffer for message framing */
231
242
  buf = Buffer.alloc(0);
232
243
  phase = "pre_startup";
@@ -236,7 +247,6 @@ var PGliteBridge = class extends node_stream.Duplex {
236
247
  drainQueue = [];
237
248
  /** Buffered EQP messages awaiting Sync */
238
249
  pipeline = [];
239
- pipelineLen = 0;
240
250
  constructor(pglite, sessionLock) {
241
251
  super();
242
252
  this.pglite = pglite;
@@ -265,15 +275,11 @@ var PGliteBridge = class extends node_stream.Duplex {
265
275
  _read() {}
266
276
  _write(chunk, _encoding, callback) {
267
277
  this.pending.push(chunk);
268
- this.pendingLen += chunk.length;
269
278
  this.enqueue(callback);
270
279
  }
271
280
  /** Handles corked batches — pg.Client corks during prepared queries (P+B+D+E+S) */
272
281
  _writev(chunks, callback) {
273
- for (const { chunk } of chunks) {
274
- this.pending.push(chunk);
275
- this.pendingLen += chunk.length;
276
- }
282
+ for (const { chunk } of chunks) this.pending.push(chunk);
277
283
  this.enqueue(callback);
278
284
  }
279
285
  _final(callback) {
@@ -284,9 +290,7 @@ var PGliteBridge = class extends node_stream.Duplex {
284
290
  _destroy(error, callback) {
285
291
  this.tornDown = true;
286
292
  this.pipeline.length = 0;
287
- this.pipelineLen = 0;
288
293
  this.pending.length = 0;
289
- this.pendingLen = 0;
290
294
  this.sessionLock?.release(this.bridgeId);
291
295
  const callbacks = this.drainQueue;
292
296
  this.drainQueue = [];
@@ -299,7 +303,6 @@ var PGliteBridge = class extends node_stream.Duplex {
299
303
  if (this.buf.length === 0 && this.pending.length === 1) this.buf = this.pending[0];
300
304
  else this.buf = Buffer.concat([this.buf, ...this.pending]);
301
305
  this.pending.length = 0;
302
- this.pendingLen = 0;
303
306
  }
304
307
  /**
305
308
  * Enqueue a write callback and start draining if not already running.
@@ -378,12 +381,10 @@ var PGliteBridge = class extends node_stream.Duplex {
378
381
  }
379
382
  if (EQP_MESSAGES.has(msgType)) {
380
383
  this.pipeline.push(message);
381
- this.pipelineLen += message.length;
382
384
  continue;
383
385
  }
384
386
  if (msgType === SYNC) {
385
387
  this.pipeline.push(message);
386
- this.pipelineLen += message.length;
387
388
  await this.flushPipeline();
388
389
  continue;
389
390
  }
@@ -404,19 +405,8 @@ var PGliteBridge = class extends node_stream.Duplex {
404
405
  */
405
406
  async flushPipeline() {
406
407
  const messages = this.pipeline;
407
- const totalLen = this.pipelineLen;
408
408
  this.pipeline = [];
409
- this.pipelineLen = 0;
410
- let batch;
411
- if (messages.length === 1) batch = messages[0] ?? new Uint8Array(0);
412
- else {
413
- batch = new Uint8Array(totalLen);
414
- let offset = 0;
415
- for (const msg of messages) {
416
- batch.set(msg, offset);
417
- offset += msg.length;
418
- }
419
- }
409
+ const batch = concat(messages);
420
410
  await this.acquireSession();
421
411
  await this.pglite.runExclusive(async () => {
422
412
  const chunks = [];
@@ -476,6 +466,9 @@ const { Client, Pool } = pg.default;
476
466
  /**
477
467
  * Creates a pg.Pool where every connection is an in-process PGlite bridge.
478
468
  *
469
+ * Most users should prefer {@link createPgliteAdapter}, which wraps this
470
+ * function and also handles schema application and reset/snapshot lifecycle.
471
+ *
479
472
  * ```typescript
480
473
  * import { createPool } from 'prisma-pglite-bridge';
481
474
  * import { PrismaPg } from '@prisma/adapter-pg';
@@ -485,6 +478,8 @@ const { Client, Pool } = pg.default;
485
478
  * const adapter = new PrismaPg(pool);
486
479
  * const prisma = new PrismaClient({ adapter });
487
480
  * ```
481
+ *
482
+ * @see {@link createPgliteAdapter} for the higher-level API with schema management.
488
483
  */
489
484
  const createPool = async (options = {}) => {
490
485
  const { dataDir, extensions, max = 5 } = options;
@@ -535,6 +530,15 @@ const createPool = async (options = {}) => {
535
530
  * ```
536
531
  */
537
532
  const SNAPSHOT_SCHEMA = "_pglite_snapshot";
533
+ const SENTINEL_SCHEMA = "_pglite_bridge";
534
+ const SENTINEL_TABLE = "__initialized";
535
+ const SENTINEL_MARKER = "prisma-pglite-bridge:init:v1";
536
+ const USER_TABLES_WHERE = `schemaname NOT IN ('pg_catalog', 'information_schema')
537
+ AND schemaname != '${SNAPSHOT_SCHEMA}'
538
+ AND schemaname != '${SENTINEL_SCHEMA}'
539
+ AND tablename NOT LIKE '_prisma%'`;
540
+ const escapeLiteral = (s) => `'${s.replace(/'/g, "''")}'`;
541
+ const isValidSentinelRow = (rows) => rows.length === 1 && rows[0]?.marker === SENTINEL_MARKER && rows[0]?.version === 1;
538
542
  /**
539
543
  * Discover the migrations directory via Prisma's config API.
540
544
  * Uses the same resolution as `prisma migrate dev` — reads prisma.config.ts,
@@ -587,8 +591,10 @@ const resolveSQL = async (options) => {
587
591
  if (migrationsPath) {
588
592
  const sql = tryReadMigrationFiles(migrationsPath);
589
593
  if (sql) return sql;
594
+ throw new Error(`No migration.sql files found in auto-discovered path ${migrationsPath}. Run \`prisma migrate dev\` to generate migration files, or pass pre-generated SQL via the \`sql\` option.`);
590
595
  }
591
- throw new Error("No migration files found. Run `prisma migrate dev` to generate them, or pass pre-generated SQL via the `sql` option.");
596
+ if (options.configRoot) throw new Error(`prisma.config.ts loaded from configRoot (${options.configRoot}) but no schema or migrations path could be resolved. Ensure your config specifies a schema path, or pass pre-generated SQL via the \`sql\` option.`);
597
+ throw new Error("No migration files found and no prisma.config.ts could be loaded. Run `prisma migrate dev` to generate them, or pass pre-generated SQL via the `sql` option.");
592
598
  };
593
599
  /**
594
600
  * Creates a Prisma adapter backed by an in-process PGlite instance.
@@ -597,16 +603,88 @@ const resolveSQL = async (options) => {
597
603
  * function for clearing tables between tests.
598
604
  */
599
605
  const createPgliteAdapter = async (options = {}) => {
600
- const sql = await resolveSQL(options);
601
606
  const { pool, pglite, close: poolClose } = await createPool({
602
607
  dataDir: options.dataDir,
603
608
  extensions: options.extensions,
604
609
  max: options.max
605
610
  });
606
- try {
607
- await pglite.exec(sql);
608
- } catch (err) {
609
- throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
611
+ const sentinelStatements = [
612
+ `CREATE SCHEMA IF NOT EXISTS "${SENTINEL_SCHEMA}"`,
613
+ `CREATE TABLE IF NOT EXISTS "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" (marker text PRIMARY KEY, version int NOT NULL)`,
614
+ `INSERT INTO "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" (marker, version) VALUES (${escapeLiteral(SENTINEL_MARKER)}, 1) ON CONFLICT (marker) DO NOTHING`
615
+ ];
616
+ const collisionError = () => `"${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" exists but is not owned by prisma-pglite-bridge. The "${SENTINEL_SCHEMA}" schema is reserved for library metadata.`;
617
+ const writeSentinel = async () => {
618
+ let committed = false;
619
+ try {
620
+ await pglite.exec(`BEGIN;\n${sentinelStatements.join(";\n")}`);
621
+ const { rows } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
622
+ if (!isValidSentinelRow(rows)) throw new Error(collisionError());
623
+ await pglite.exec("COMMIT");
624
+ committed = true;
625
+ } catch (err) {
626
+ if (!committed) await pglite.exec("ROLLBACK");
627
+ throw err instanceof Error && err.message === collisionError() ? err : new Error(collisionError(), { cause: err });
628
+ }
629
+ };
630
+ const isInitialized = async () => {
631
+ const { rows: tableExists } = await pglite.query(`SELECT EXISTS (
632
+ SELECT 1 FROM pg_tables
633
+ WHERE schemaname = '${SENTINEL_SCHEMA}' AND tablename = '${SENTINEL_TABLE}'
634
+ ) AS found`);
635
+ if (tableExists[0]?.found) {
636
+ try {
637
+ const { rows: allRows } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
638
+ if (isValidSentinelRow(allRows)) return true;
639
+ } catch {}
640
+ throw new Error(collisionError());
641
+ }
642
+ const { rows: schemaExists } = await pglite.query(`SELECT EXISTS (
643
+ SELECT 1 FROM pg_namespace WHERE nspname = '${SENTINEL_SCHEMA}'
644
+ ) AS found`);
645
+ if (schemaExists[0]?.found) throw new Error(`Schema "${SENTINEL_SCHEMA}" exists but is not owned by prisma-pglite-bridge. The "${SENTINEL_SCHEMA}" schema is reserved for library metadata.`);
646
+ const { rows: legacy } = await pglite.query(`SELECT EXISTS (
647
+ SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
648
+ WHERE n.nspname = 'public'
649
+ UNION ALL
650
+ SELECT 1 FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid
651
+ WHERE n.nspname = 'public' AND t.typtype NOT IN ('b', 'p')
652
+ UNION ALL
653
+ SELECT 1 FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
654
+ WHERE n.nspname = 'public'
655
+ UNION ALL
656
+ SELECT 1 FROM pg_namespace
657
+ WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'public')
658
+ ) AS initialized`);
659
+ if (legacy[0]?.initialized) {
660
+ await writeSentinel();
661
+ return true;
662
+ }
663
+ return false;
664
+ };
665
+ if (!options.dataDir || !await isInitialized()) {
666
+ const sql = await resolveSQL(options);
667
+ if (!options.sql) {
668
+ let committed = false;
669
+ try {
670
+ await pglite.exec(`BEGIN;\n${sql};\n${sentinelStatements.join(";\n")}`);
671
+ const { rows: verify } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
672
+ if (!isValidSentinelRow(verify)) throw new Error(collisionError());
673
+ await pglite.exec("COMMIT");
674
+ committed = true;
675
+ } catch (err) {
676
+ if (!committed) await pglite.exec("ROLLBACK");
677
+ if (err instanceof Error && err.message === collisionError()) throw err;
678
+ throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
679
+ }
680
+ } else {
681
+ try {
682
+ await pglite.exec(sql);
683
+ } catch (err) {
684
+ throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
685
+ }
686
+ await writeSentinel();
687
+ }
610
688
  }
611
689
  const adapter = new _prisma_adapter_pg.PrismaPg(pool);
612
690
  let cachedTables = null;
@@ -615,23 +693,38 @@ const createPgliteAdapter = async (options = {}) => {
615
693
  if (cachedTables !== null) return cachedTables;
616
694
  const { rows } = await pglite.query(`SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified
617
695
  FROM pg_tables
618
- WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
619
- AND schemaname != '${SNAPSHOT_SCHEMA}'
620
- AND tablename NOT LIKE '_prisma%'`);
696
+ WHERE ${USER_TABLES_WHERE}`);
621
697
  cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(", ") : "";
622
698
  return cachedTables;
623
699
  };
624
700
  const snapshotDb = async () => {
625
701
  await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
626
- await pglite.exec(`CREATE SCHEMA "${SNAPSHOT_SCHEMA}"`);
627
- const { rows: tables } = await pglite.query(`SELECT quote_ident(tablename) AS tablename FROM pg_tables
628
- WHERE schemaname = 'public'
629
- AND tablename NOT LIKE '_prisma%'`);
630
- for (const { tablename } of tables) await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".${tablename} AS SELECT * FROM public.${tablename}`);
631
- const { rows: seqs } = await pglite.query(`SELECT quote_literal(sequencename) AS name, last_value::text AS value
632
- FROM pg_sequences WHERE schemaname = 'public' AND last_value IS NOT NULL`);
633
- await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__sequences (name text, value bigint)`);
634
- for (const { name, value } of seqs) await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__sequences VALUES (${name}, ${value})`);
702
+ try {
703
+ await pglite.exec("BEGIN");
704
+ await pglite.exec(`CREATE SCHEMA "${SNAPSHOT_SCHEMA}"`);
705
+ const { rows: tables } = await pglite.query(`SELECT schemaname, tablename,
706
+ quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified
707
+ FROM pg_tables
708
+ WHERE ${USER_TABLES_WHERE}`);
709
+ await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__tables (snap_name text, source_schema text, source_table text)`);
710
+ for (const [i, { schemaname, tablename, qualified }] of tables.entries()) {
711
+ const snapName = `_snap_${i}`;
712
+ await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}"."${snapName}" AS SELECT * FROM ${qualified}`);
713
+ await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`);
714
+ }
715
+ const { rows: seqs } = await pglite.query(`SELECT quote_literal(schemaname || '.' || sequencename) AS name, last_value::text AS value
716
+ FROM pg_sequences
717
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
718
+ AND schemaname != '${SNAPSHOT_SCHEMA}'
719
+ AND last_value IS NOT NULL`);
720
+ await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__sequences (name text, value bigint)`);
721
+ for (const { name, value } of seqs) await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__sequences VALUES (${name}, ${value})`);
722
+ await pglite.exec("COMMIT");
723
+ } catch (err) {
724
+ await pglite.exec("ROLLBACK");
725
+ await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
726
+ throw err;
727
+ }
635
728
  hasSnapshot = true;
636
729
  };
637
730
  const resetSnapshot = async () => {
@@ -639,23 +732,22 @@ const createPgliteAdapter = async (options = {}) => {
639
732
  await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
640
733
  };
641
734
  const resetDb = async () => {
735
+ cachedTables = null;
642
736
  const tables = await discoverTables();
643
- if (hasSnapshot && tables) {
644
- try {
645
- await pglite.exec("SET session_replication_role = replica");
646
- await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
647
- const { rows: snapshotTables } = await pglite.query(`SELECT quote_ident(tablename) AS tablename FROM pg_tables
648
- WHERE schemaname = '${SNAPSHOT_SCHEMA}'
649
- AND tablename != '__sequences'`);
650
- for (const { tablename } of snapshotTables) await pglite.exec(`INSERT INTO public.${tablename} SELECT * FROM "${SNAPSHOT_SCHEMA}".${tablename}`);
651
- } finally {
652
- await pglite.exec("SET session_replication_role = DEFAULT");
653
- }
737
+ if (hasSnapshot && tables) try {
738
+ await pglite.exec("SET session_replication_role = replica");
739
+ await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);
740
+ const { rows: snapshotTables } = await pglite.query(`SELECT snap_name, quote_ident(source_schema) || '.' || quote_ident(source_table) AS qualified
741
+ FROM "${SNAPSHOT_SCHEMA}".__tables`);
742
+ for (const { snap_name, qualified } of snapshotTables) await pglite.exec(`INSERT INTO ${qualified} SELECT * FROM "${SNAPSHOT_SCHEMA}"."${snap_name}"`);
654
743
  const { rows: seqs } = await pglite.query(`SELECT quote_literal(name) AS name, value::text AS value FROM "${SNAPSHOT_SCHEMA}".__sequences`);
655
744
  for (const { name, value } of seqs) await pglite.exec(`SELECT setval(${name}, ${value})`);
656
- } else if (tables) try {
745
+ } finally {
746
+ await pglite.exec("SET session_replication_role = DEFAULT");
747
+ }
748
+ else if (tables) try {
657
749
  await pglite.exec("SET session_replication_role = replica");
658
- await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
750
+ await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);
659
751
  } finally {
660
752
  await pglite.exec("SET session_replication_role = DEFAULT");
661
753
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["Duplex","PGlite","PrismaPg"],"sources":["../src/session-lock.ts","../src/pglite-bridge.ts","../src/create-pool.ts","../src/create-pglite-adapter.ts"],"sourcesContent":["/**\n * Session-level lock for PGlite's single-session model.\n *\n * PGlite runs PostgreSQL in single-user mode — one session shared by all\n * bridges. runExclusive serializes individual operations, but transactions\n * span multiple operations. Without session-level locking, Bridge A's BEGIN\n * and Bridge B's query interleave, corrupting transaction boundaries.\n *\n * The session lock tracks which bridge owns the session. When PGlite enters\n * transaction state (ReadyForQuery status 'T' or 'E'), the owning bridge\n * gets exclusive access until the transaction completes (status returns to 'I').\n *\n * Non-transactional operations from any bridge are allowed when no transaction\n * is active — they serialize naturally through runExclusive.\n */\n\n// ReadyForQuery status bytes\nconst STATUS_IDLE = 0x49; // 'I' — no transaction\nconst STATUS_IN_TRANSACTION = 0x54; // 'T' — in transaction block\nconst STATUS_FAILED = 0x45; // 'E' — failed transaction block\n\n/** Opaque bridge identity token */\nexport type BridgeId = symbol;\n\nexport const createBridgeId = (): BridgeId => Symbol('bridge');\n\n/**\n * Extracts the ReadyForQuery status byte from a response buffer.\n * Scans from the end since RFQ is always the last message.\n * Returns null if no RFQ found.\n */\nexport const extractRfqStatus = (response: Uint8Array): number | null => {\n // RFQ is always 6 bytes: Z(5a) + length(00000005) + status\n // It's the last message in the response\n if (response.length < 6) return null;\n const i = response.length - 6;\n if (\n response[i] === 0x5a &&\n response[i + 1] === 0x00 &&\n response[i + 2] === 0x00 &&\n response[i + 3] === 0x00 &&\n response[i + 4] === 0x05\n ) {\n return response[i + 5] ?? null;\n }\n return null;\n};\n\nexport class SessionLock {\n private owner: BridgeId | null = null;\n private waitQueue: Array<{ id: BridgeId; resolve: () => void }> = [];\n\n /**\n * Acquire access to PGlite. Resolves immediately if no transaction is\n * active or if this bridge owns the current transaction. Queues otherwise.\n */\n async acquire(id: BridgeId): Promise<void> {\n if (this.owner === null || this.owner === id) return;\n\n // Another bridge owns the session (in a transaction) — wait\n return new Promise<void>((resolve) => {\n this.waitQueue.push({ id, resolve });\n });\n }\n\n /**\n * Update session state based on the ReadyForQuery status byte.\n * Call after every PGlite response that contains RFQ.\n */\n updateStatus(id: BridgeId, status: number): void {\n if (status === STATUS_IN_TRANSACTION || status === STATUS_FAILED) {\n // This bridge now owns the session\n this.owner = id;\n } else if (status === STATUS_IDLE) {\n // Transaction complete — release ownership\n if (this.owner === id) {\n this.owner = null;\n this.drainWaitQueue();\n }\n }\n }\n\n /**\n * Release ownership (e.g., when a bridge is destroyed mid-transaction).\n */\n release(id: BridgeId): void {\n if (this.owner === id) {\n this.owner = null;\n this.drainWaitQueue();\n }\n }\n\n private drainWaitQueue(): void {\n // Wake all waiting bridges — they'll serialize through runExclusive\n const waiters = this.waitQueue;\n this.waitQueue = [];\n for (const waiter of waiters) {\n waiter.resolve();\n }\n }\n}\n","/**\n * PGlite bridge stream.\n *\n * A Duplex stream that replaces the TCP socket in pg.Client, routing\n * wire protocol messages directly to an in-process PGlite instance.\n *\n * pg.Client writes wire protocol bytes → bridge frames messages →\n * PGlite processes via execProtocolRawStream → bridge pushes responses back.\n *\n * Extended Query Protocol pipelines (Parse→Bind→Describe→Execute→Sync) are\n * concatenated into a single buffer and sent as one atomic execProtocolRawStream\n * call within one runExclusive. This prevents portal interleaving between\n * concurrent bridges AND reduces async overhead (1 WASM call instead of 5).\n *\n * The response from a batched pipeline contains spurious ReadyForQuery messages\n * after each sub-message (PGlite's single-user mode). These are stripped,\n * keeping only the final ReadyForQuery after Sync.\n */\nimport { Duplex } from 'node:stream';\nimport type { PGlite } from '@electric-sql/pglite';\nimport {\n type BridgeId,\n createBridgeId,\n extractRfqStatus,\n type SessionLock,\n} from './session-lock.ts';\n\n// Frontend message types\nconst PARSE = 0x50; // P\nconst BIND = 0x42; // B\nconst DESCRIBE = 0x44; // D\nconst EXECUTE = 0x45; // E\nconst CLOSE = 0x43; // C\nconst FLUSH = 0x48; // H\nconst SYNC = 0x53; // S (frontend)\nconst TERMINATE = 0x58; // X\n\n// Backend message type\nconst READY_FOR_QUERY = 0x5a; // Z — 6 bytes: Z + length(5) + status\n\n// Extended Query Protocol message types — must be batched until Sync\nconst EQP_MESSAGES = new Set([PARSE, BIND, DESCRIBE, EXECUTE, CLOSE, FLUSH]);\n\n/**\n * Strips all intermediate ReadyForQuery messages from a response, keeping\n * only the last one. PGlite's single-user mode emits RFQ after every\n * sub-message; pg.Client expects exactly one after Sync.\n *\n * Operates in-place on the response by building a list of byte ranges to\n * keep, then assembling the result. Returns the original buffer (no copy)\n * if there are 0 or 1 RFQ messages.\n */\n/** @internal — exported for testing only */\nexport const stripIntermediateReadyForQuery = (response: Uint8Array): Uint8Array => {\n // Quick scan: count RFQ occurrences and find their positions\n const rfqPositions: number[] = [];\n let offset = 0;\n\n while (offset < response.length) {\n if (offset + 5 >= response.length) break;\n\n if (\n response[offset] === READY_FOR_QUERY &&\n response[offset + 1] === 0x00 &&\n response[offset + 2] === 0x00 &&\n response[offset + 3] === 0x00 &&\n response[offset + 4] === 0x05\n ) {\n rfqPositions.push(offset);\n offset += 6;\n } else {\n // Skip this backend message: type(1) + length(4, big-endian)\n const b1 = response[offset + 1];\n const b2 = response[offset + 2];\n const b3 = response[offset + 3];\n const b4 = response[offset + 4];\n if (b1 === undefined || b2 === undefined || b3 === undefined || b4 === undefined) break;\n const msgLen = ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n if (msgLen < 4) break; // malformed — minimum length field is 4 (includes itself)\n offset += 1 + msgLen;\n }\n }\n\n if (rfqPositions.length <= 1) return response;\n\n // Build result: copy everything except intermediate RFQ messages (all but last)\n const removeCount = rfqPositions.length - 1;\n const resultLen = response.length - removeCount * 6;\n const result = new Uint8Array(resultLen);\n let src = 0;\n let dst = 0;\n let removeIdx = 0;\n\n while (src < response.length) {\n const nextRemove =\n removeIdx < removeCount ? (rfqPositions[removeIdx] ?? response.length) : response.length;\n if (src < nextRemove) {\n const copyLen = nextRemove - src;\n result.set(response.subarray(src, src + copyLen), dst);\n dst += copyLen;\n src += copyLen;\n }\n if (removeIdx < removeCount && src === rfqPositions[removeIdx]) {\n src += 6;\n removeIdx++;\n }\n }\n\n return result;\n};\n\n/**\n * Concatenates multiple Uint8Array views into one contiguous buffer.\n */\nconst concat = (parts: Uint8Array[]): Uint8Array => {\n if (parts.length === 1) return parts[0] ?? new Uint8Array(0);\n const total = parts.reduce((sum, p) => sum + p.length, 0);\n const result = new Uint8Array(total);\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n};\n\n/**\n * Duplex stream that bridges `pg.Client` to an in-process PGlite instance.\n *\n * Replaces the TCP socket in `pg.Client` via the `stream` option. Speaks\n * PostgreSQL wire protocol directly to PGlite — no TCP, no serialization\n * overhead beyond what the wire protocol requires.\n *\n * Pass to `pg.Client` or use via `createPool()` / `createPgliteAdapter()`:\n *\n * ```typescript\n * const client = new pg.Client({\n * stream: () => new PGliteBridge(pglite),\n * });\n * ```\n */\nexport class PGliteBridge extends Duplex {\n private readonly pglite: PGlite;\n private readonly sessionLock: SessionLock | null;\n private readonly bridgeId: BridgeId;\n /** Incoming bytes not yet compacted into buf */\n private pending: Buffer[] = [];\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: used via this.pendingLen\n private pendingLen = 0;\n /** Compacted input buffer for message framing */\n private buf: Buffer = Buffer.alloc(0);\n private phase: 'pre_startup' | 'ready' = 'pre_startup';\n private draining = false;\n private tornDown = false;\n /** Callbacks waiting for drain to process their data */\n private drainQueue: Array<(error?: Error | null) => void> = [];\n /** Buffered EQP messages awaiting Sync */\n private pipeline: Uint8Array[] = [];\n private pipelineLen = 0;\n\n constructor(pglite: PGlite, sessionLock?: SessionLock) {\n super();\n this.pglite = pglite;\n this.sessionLock = sessionLock ?? null;\n this.bridgeId = createBridgeId();\n }\n\n // ── Socket compatibility (called by pg's Connection) ──\n\n connect(): this {\n setImmediate(() => this.emit('connect'));\n return this;\n }\n\n setKeepAlive(): this {\n return this;\n }\n\n setNoDelay(): this {\n return this;\n }\n\n setTimeout(): this {\n return this;\n }\n\n ref(): this {\n return this;\n }\n\n unref(): this {\n return this;\n }\n\n // ── Duplex implementation ──\n\n override _read(): void {\n // Data is pushed proactively when PGlite responses arrive\n }\n\n override _write(\n chunk: Buffer,\n _encoding: BufferEncoding,\n callback: (error?: Error | null) => void,\n ): void {\n this.pending.push(chunk);\n this.pendingLen += chunk.length;\n this.enqueue(callback);\n }\n\n /** Handles corked batches — pg.Client corks during prepared queries (P+B+D+E+S) */\n override _writev(\n chunks: Array<{ chunk: Buffer; encoding: BufferEncoding }>,\n callback: (error?: Error | null) => void,\n ): void {\n for (const { chunk } of chunks) {\n this.pending.push(chunk);\n this.pendingLen += chunk.length;\n }\n this.enqueue(callback);\n }\n\n override _final(callback: (error?: Error | null) => void): void {\n this.sessionLock?.release(this.bridgeId);\n this.push(null);\n callback();\n }\n\n override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {\n this.tornDown = true;\n this.pipeline.length = 0;\n this.pipelineLen = 0;\n this.pending.length = 0;\n this.pendingLen = 0;\n this.sessionLock?.release(this.bridgeId);\n\n // Flush pending write callbacks so pg.Client doesn't hang\n const callbacks = this.drainQueue;\n this.drainQueue = [];\n for (const cb of callbacks) {\n cb(error);\n }\n\n callback(error);\n }\n\n // ── Message processing ──\n\n /** Merge pending chunks into buf only when needed for framing */\n private compact(): void {\n if (this.pending.length === 0) return;\n if (this.buf.length === 0 && this.pending.length === 1) {\n this.buf = this.pending[0] as Buffer;\n } else {\n this.buf = Buffer.concat([this.buf, ...this.pending]);\n }\n this.pending.length = 0;\n this.pendingLen = 0;\n }\n\n /**\n * Enqueue a write callback and start draining if not already running.\n * The callback is NOT called until drain has processed the data.\n */\n private enqueue(callback: (error?: Error | null) => void): void {\n this.drainQueue.push(callback);\n if (!this.draining) {\n // Errors are propagated through drainQueue callbacks, not through this promise\n this.drain().catch(() => {});\n }\n }\n\n /**\n * Process all pending data, looping until no new data arrives.\n * Fires all queued callbacks on completion or error.\n */\n private async drain(): Promise<void> {\n if (this.draining) return;\n this.draining = true;\n\n let error: Error | null = null;\n\n try {\n // Loop until no more pending data to process\n while (this.pending.length > 0 || this.buf.length > 0) {\n if (this.tornDown) break;\n\n if (this.phase === 'pre_startup') {\n await this.processPreStartup();\n }\n if (this.phase === 'ready') {\n await this.processMessages();\n }\n\n // If processMessages couldn't consume anything (incomplete message),\n // stop looping — more data will arrive via _write\n if (this.pending.length === 0) break;\n }\n } catch (err) {\n error = err instanceof Error ? err : new Error(String(err));\n // Release session lock on error — prevents permanent deadlock if\n // PGlite crashes mid-transaction (other bridges would wait forever)\n this.sessionLock?.release(this.bridgeId);\n } finally {\n this.draining = false;\n\n // Fire all waiting callbacks\n const callbacks = this.drainQueue;\n this.drainQueue = [];\n for (const cb of callbacks) {\n cb(error);\n }\n }\n }\n\n /**\n * Frames and processes the startup message.\n *\n * Format: [4 bytes: total length] [4 bytes: protocol version] [key\\0value\\0 pairs]\n * No type byte — length includes itself.\n */\n private async processPreStartup(): Promise<void> {\n this.compact();\n if (this.buf.length < 4) return;\n const len = this.buf.readInt32BE(0);\n if (this.buf.length < len) return;\n\n const message = this.buf.subarray(0, len);\n this.buf = this.buf.subarray(len);\n\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n await this.execAndPush(message);\n });\n\n this.phase = 'ready';\n }\n\n /**\n * Frames and processes regular wire protocol messages.\n *\n * Extended Query Protocol messages (Parse, Bind, Describe, Execute, Close,\n * Flush) are buffered in `this.pipeline`. When Sync arrives, the entire\n * pipeline is concatenated and sent to PGlite as one atomic\n * execProtocolRawStream call within one runExclusive.\n *\n * SimpleQuery messages are sent directly (they're self-contained).\n */\n private async processMessages(): Promise<void> {\n this.compact();\n while (this.buf.length >= 5) {\n const len = 1 + this.buf.readInt32BE(1);\n if (len < 5 || this.buf.length < len) break;\n\n const message = this.buf.subarray(0, len);\n this.buf = this.buf.subarray(len);\n const msgType = message[0] ?? 0;\n\n if (msgType === TERMINATE) {\n this.sessionLock?.release(this.bridgeId);\n this.push(null);\n return;\n }\n\n if (EQP_MESSAGES.has(msgType)) {\n this.pipeline.push(message);\n this.pipelineLen += message.length;\n continue;\n }\n\n if (msgType === SYNC) {\n this.pipeline.push(message);\n this.pipelineLen += message.length;\n await this.flushPipeline();\n continue;\n }\n\n // SimpleQuery or other standalone message\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n await this.execAndPush(message);\n });\n }\n }\n\n /**\n * Sends the accumulated EQP pipeline as one atomic operation.\n *\n * All buffered messages are concatenated into a single buffer and sent\n * as one execProtocolRawStream call. This is both correct (prevents\n * portal interleaving) and fast (1 WASM call + 1 async boundary instead\n * of 5). Intermediate ReadyForQuery messages are stripped from the\n * combined response.\n */\n private async flushPipeline(): Promise<void> {\n const messages = this.pipeline;\n const totalLen = this.pipelineLen;\n this.pipeline = [];\n this.pipelineLen = 0;\n\n // Concatenate pipeline into one buffer\n let batch: Uint8Array;\n if (messages.length === 1) {\n batch = messages[0] ?? new Uint8Array(0);\n } else {\n batch = new Uint8Array(totalLen);\n let offset = 0;\n for (const msg of messages) {\n batch.set(msg, offset);\n offset += msg.length;\n }\n }\n\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n const chunks: Uint8Array[] = [];\n\n await this.pglite.execProtocolRawStream(batch, {\n onRawData: (chunk: Uint8Array) => chunks.push(chunk),\n });\n\n if (this.tornDown || chunks.length === 0) return;\n\n // Single chunk: strip intermediate RFQ and push\n if (chunks.length === 1) {\n const raw = chunks[0] ?? new Uint8Array(0);\n this.trackSessionStatus(raw);\n const cleaned = stripIntermediateReadyForQuery(raw);\n if (cleaned.length > 0) this.push(cleaned);\n return;\n }\n\n // Multiple chunks: concat first, then strip\n const combined = concat(chunks);\n this.trackSessionStatus(combined);\n const cleaned = stripIntermediateReadyForQuery(combined);\n if (cleaned.length > 0) this.push(cleaned);\n });\n }\n\n /**\n * Sends a message to PGlite and pushes response chunks directly to the\n * stream as they arrive. Avoids collecting and concatenating for large\n * multi-row responses (e.g., findMany 500 rows = ~503 onRawData chunks).\n *\n * Must be called inside runExclusive.\n */\n private async execAndPush(message: Uint8Array): Promise<void> {\n let lastChunk: Uint8Array | null = null;\n await this.pglite.execProtocolRawStream(message, {\n onRawData: (chunk: Uint8Array) => {\n if (!this.tornDown && chunk.length > 0) {\n this.push(chunk);\n lastChunk = chunk;\n }\n },\n });\n if (lastChunk) this.trackSessionStatus(lastChunk);\n }\n\n // ── Session lock helpers ──\n\n private async acquireSession(): Promise<void> {\n await this.sessionLock?.acquire(this.bridgeId);\n }\n\n private trackSessionStatus(response: Uint8Array): void {\n if (!this.sessionLock) return;\n const status = extractRfqStatus(response);\n if (status !== null) {\n this.sessionLock.updateStatus(this.bridgeId, status);\n }\n }\n}\n","/**\n * Pool factory — creates a pg.Pool backed by an in-process PGlite instance.\n *\n * Each pool connection gets its own PGliteBridge stream, all sharing the\n * same PGlite WASM instance and SessionLock. The session lock ensures\n * transaction isolation: when one bridge starts a transaction (BEGIN),\n * it gets exclusive PGlite access until COMMIT/ROLLBACK. Non-transactional\n * operations from any bridge serialize through PGlite's runExclusive mutex.\n */\nimport { type Extensions, PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { PGliteBridge } from './pglite-bridge.ts';\nimport { SessionLock } from './session-lock.ts';\n\nconst { Client, Pool } = pg;\n\nexport interface CreatePoolOptions {\n /** PGlite data directory. Omit for in-memory. */\n dataDir?: string;\n\n /** PGlite extensions (e.g., `{ uuid_ossp: uuidOssp() }`) */\n extensions?: Extensions;\n\n /** Maximum pool connections (default: 5) */\n max?: number;\n\n /** Existing PGlite instance to use instead of creating one */\n pglite?: PGlite;\n}\n\nexport interface PoolResult {\n /** pg.Pool backed by PGlite — pass to PrismaPg */\n pool: pg.Pool;\n\n /** The underlying PGlite instance */\n pglite: PGlite;\n\n /** Shut down pool and PGlite */\n close: () => Promise<void>;\n}\n\n/**\n * Creates a pg.Pool where every connection is an in-process PGlite bridge.\n *\n * ```typescript\n * import { createPool } from 'prisma-pglite-bridge';\n * import { PrismaPg } from '@prisma/adapter-pg';\n * import { PrismaClient } from '@prisma/client';\n *\n * const { pool, close } = await createPool();\n * const adapter = new PrismaPg(pool);\n * const prisma = new PrismaClient({ adapter });\n * ```\n */\nexport const createPool = async (options: CreatePoolOptions = {}): Promise<PoolResult> => {\n const { dataDir, extensions, max = 5 } = options;\n const ownsInstance = !options.pglite;\n\n const pglite = options.pglite ?? new PGlite(dataDir, extensions ? { extensions } : undefined);\n await pglite.waitReady;\n\n const sessionLock = new SessionLock();\n\n // Subclass pg.Client to inject PGliteBridge as the stream\n const BridgedClient = class extends Client {\n constructor(config?: string | pg.ClientConfig) {\n const cfg = typeof config === 'string' ? { connectionString: config } : (config ?? {});\n super({\n ...cfg,\n user: 'postgres',\n database: 'postgres',\n stream: (() => new PGliteBridge(pglite, sessionLock)) as pg.ClientConfig['stream'],\n });\n }\n };\n\n const pool = new Pool({\n Client: BridgedClient as typeof Client,\n max,\n });\n\n const close = async () => {\n await pool.end();\n if (ownsInstance) {\n await pglite.close();\n }\n };\n\n return { pool, pglite, close };\n};\n","/**\n * Creates a Prisma adapter backed by in-process PGlite.\n *\n * No TCP, no Docker, no worker threads — everything runs in the same process.\n * Works for testing, development, seeding, and scripts.\n *\n * ```typescript\n * import { createPgliteAdapter } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const { adapter, resetDb } = await createPgliteAdapter();\n * const prisma = new PrismaClient({ adapter });\n *\n * beforeEach(() => resetDb());\n * ```\n */\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { createPool } from './create-pool.ts';\n\nconst SNAPSHOT_SCHEMA = '_pglite_snapshot';\n\nexport interface CreatePgliteAdapterOptions {\n /** Path to prisma/migrations/ directory (auto-discovered via prisma.config.ts if omitted) */\n migrationsPath?: string;\n\n /** Pre-generated SQL to apply instead of auto-generating from schema */\n sql?: string;\n\n /** Root directory for prisma.config.ts discovery (default: process.cwd()). Set this in monorepos where tests run from the workspace root. */\n configRoot?: string;\n\n /** PGlite data directory. Omit for in-memory. */\n dataDir?: string;\n\n /** PGlite extensions (e.g., `{ uuid_ossp: uuidOssp() }`) */\n extensions?: import('@electric-sql/pglite').Extensions;\n\n /** Maximum pool connections (default: 5) */\n max?: number;\n}\n\n/** Clear all user tables. Call in `beforeEach` for per-test isolation. */\nexport type ResetDbFn = () => Promise<void>;\n\nexport type SnapshotDbFn = () => Promise<void>;\n\nexport type ResetSnapshotFn = () => Promise<void>;\n\nexport interface PgliteAdapter {\n /** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */\n adapter: PrismaPg;\n\n /** The underlying PGlite instance for direct SQL, snapshots, or extensions. */\n pglite: import('@electric-sql/pglite').PGlite;\n\n /** Clear all user tables. Call in `beforeEach` for per-test isolation. */\n resetDb: ResetDbFn;\n\n /** Snapshot current DB state. Subsequent `resetDb` calls restore to this snapshot. */\n snapshotDb: SnapshotDbFn;\n\n /** Discard the current snapshot. Subsequent `resetDb` calls truncate to empty. */\n resetSnapshot: ResetSnapshotFn;\n\n /** Shut down pool and PGlite. Not needed in tests (process exit handles it). */\n close: () => Promise<void>;\n}\n\n/**\n * Discover the migrations directory via Prisma's config API.\n * Uses the same resolution as `prisma migrate dev` — reads prisma.config.ts,\n * resolves paths relative to config file location.\n *\n * Returns null if @prisma/config is not available or config cannot be loaded.\n */\nconst discoverMigrationsPath = async (configRoot?: string): Promise<string | null> => {\n try {\n const { loadConfigFromFile } = await import('@prisma/config');\n const { config, error } = await loadConfigFromFile({ configRoot: configRoot ?? process.cwd() });\n if (error) return null;\n\n // Explicit migrations path from prisma.config.ts\n if (config.migrations?.path) return config.migrations.path;\n\n // Fallback: Prisma convention is {schemaDir}/migrations\n const schemaPath = config.schema;\n if (schemaPath) return join(dirname(schemaPath), 'migrations');\n\n return null;\n } catch {\n return null;\n }\n};\n\n/**\n * Read migration SQL files from a migrations directory in directory order.\n * Returns null if the directory doesn't exist or has no migration files.\n */\nconst tryReadMigrationFiles = (migrationsPath: string): string | null => {\n if (!existsSync(migrationsPath)) return null;\n\n const dirs = readdirSync(migrationsPath)\n .filter((d) => statSync(join(migrationsPath, d)).isDirectory())\n .sort();\n\n const sqlParts: string[] = [];\n for (const dir of dirs) {\n const sqlPath = join(migrationsPath, dir, 'migration.sql');\n if (existsSync(sqlPath)) {\n sqlParts.push(readFileSync(sqlPath, 'utf8'));\n }\n }\n\n return sqlParts.length > 0 ? sqlParts.join('\\n') : null;\n};\n\n/**\n * Resolve schema SQL. Priority:\n * 1. Explicit `sql` option — use directly\n * 2. Explicit `migrationsPath` — read migration files\n * 3. Auto-discovered migrations (via prisma.config.ts) — read migration files\n * 4. Error — tell the user to generate migration files\n */\nconst resolveSQL = async (options: CreatePgliteAdapterOptions): Promise<string> => {\n if (options.sql) return options.sql;\n\n // Explicit migrationsPath\n if (options.migrationsPath) {\n const sql = tryReadMigrationFiles(options.migrationsPath);\n if (sql) return sql;\n throw new Error(\n `No migration.sql files found in ${options.migrationsPath}. Run \\`prisma migrate dev\\` to generate migration files.`,\n );\n }\n\n // Auto-discover via Prisma config\n const migrationsPath = await discoverMigrationsPath(options.configRoot);\n\n if (migrationsPath) {\n const sql = tryReadMigrationFiles(migrationsPath);\n if (sql) return sql;\n }\n\n throw new Error(\n 'No migration files found. Run `prisma migrate dev` to generate them, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n};\n\n/**\n * Creates a Prisma adapter backed by an in-process PGlite instance.\n *\n * Applies the schema and returns a ready-to-use adapter + a `resetDb`\n * function for clearing tables between tests.\n */\nexport const createPgliteAdapter = async (\n options: CreatePgliteAdapterOptions = {},\n): Promise<PgliteAdapter> => {\n const sql = await resolveSQL(options);\n const {\n pool,\n pglite,\n close: poolClose,\n } = await createPool({\n dataDir: options.dataDir,\n extensions: options.extensions,\n max: options.max,\n });\n\n try {\n await pglite.exec(sql);\n } catch (err) {\n throw new Error('Failed to apply schema SQL to PGlite. Check your schema or migration files.', {\n cause: err,\n });\n }\n\n const adapter = new PrismaPg(pool);\n\n let cachedTables: string | null = null;\n let hasSnapshot = false;\n\n const discoverTables = async () => {\n if (cachedTables !== null) return cachedTables;\n const { rows } = await pglite.query<{ qualified: string }>(\n `SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified\n FROM pg_tables\n WHERE schemaname NOT IN ('pg_catalog', 'information_schema')\n AND schemaname != '${SNAPSHOT_SCHEMA}'\n AND tablename NOT LIKE '_prisma%'`,\n );\n cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(', ') : '';\n return cachedTables;\n };\n\n const snapshotDb: SnapshotDbFn = async () => {\n await pglite.exec(`DROP SCHEMA IF EXISTS \"${SNAPSHOT_SCHEMA}\" CASCADE`);\n await pglite.exec(`CREATE SCHEMA \"${SNAPSHOT_SCHEMA}\"`);\n\n const { rows: tables } = await pglite.query<{ tablename: string }>(\n `SELECT quote_ident(tablename) AS tablename FROM pg_tables\n WHERE schemaname = 'public'\n AND tablename NOT LIKE '_prisma%'`,\n );\n\n for (const { tablename } of tables) {\n await pglite.exec(\n `CREATE TABLE \"${SNAPSHOT_SCHEMA}\".${tablename} AS SELECT * FROM public.${tablename}`,\n );\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(sequencename) AS name, last_value::text AS value\n FROM pg_sequences WHERE schemaname = 'public' AND last_value IS NOT NULL`,\n );\n\n await pglite.exec(`CREATE TABLE \"${SNAPSHOT_SCHEMA}\".__sequences (name text, value bigint)`);\n for (const { name, value } of seqs) {\n await pglite.exec(`INSERT INTO \"${SNAPSHOT_SCHEMA}\".__sequences VALUES (${name}, ${value})`);\n }\n\n hasSnapshot = true;\n };\n\n const resetSnapshot: ResetSnapshotFn = async () => {\n hasSnapshot = false;\n await pglite.exec(`DROP SCHEMA IF EXISTS \"${SNAPSHOT_SCHEMA}\" CASCADE`);\n };\n\n const resetDb = async () => {\n const tables = await discoverTables();\n\n if (hasSnapshot && tables) {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);\n\n const { rows: snapshotTables } = await pglite.query<{ tablename: string }>(\n `SELECT quote_ident(tablename) AS tablename FROM pg_tables\n WHERE schemaname = '${SNAPSHOT_SCHEMA}'\n AND tablename != '__sequences'`,\n );\n\n for (const { tablename } of snapshotTables) {\n await pglite.exec(\n `INSERT INTO public.${tablename} SELECT * FROM \"${SNAPSHOT_SCHEMA}\".${tablename}`,\n );\n }\n } finally {\n await pglite.exec('SET session_replication_role = DEFAULT');\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(name) AS name, value::text AS value FROM \"${SNAPSHOT_SCHEMA}\".__sequences`,\n );\n\n for (const { name, value } of seqs) {\n await pglite.exec(`SELECT setval(${name}, ${value})`);\n }\n } else if (tables) {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);\n } finally {\n await pglite.exec('SET session_replication_role = DEFAULT');\n }\n }\n\n await pglite.exec('RESET ALL');\n await pglite.exec('DEALLOCATE ALL');\n };\n\n return { adapter, pglite, resetDb, snapshotDb, resetSnapshot, close: poolClose };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["Duplex","PGlite","PrismaPg"],"sources":["../src/session-lock.ts","../src/pglite-bridge.ts","../src/create-pool.ts","../src/create-pglite-adapter.ts"],"sourcesContent":["/**\n * Session-level lock for PGlite's single-session model.\n *\n * PGlite runs PostgreSQL in single-user mode — one session shared by all\n * bridges. runExclusive serializes individual operations, but transactions\n * span multiple operations. Without session-level locking, Bridge A's BEGIN\n * and Bridge B's query interleave, corrupting transaction boundaries.\n *\n * The session lock tracks which bridge owns the session. When PGlite enters\n * transaction state (ReadyForQuery status 'T' or 'E'), the owning bridge\n * gets exclusive access until the transaction completes (status returns to 'I').\n *\n * Non-transactional operations from any bridge are allowed when no transaction\n * is active — they serialize naturally through runExclusive.\n */\n\n// ReadyForQuery status bytes\nconst STATUS_IDLE = 0x49; // 'I' — no transaction\nconst STATUS_IN_TRANSACTION = 0x54; // 'T' — in transaction block\nconst STATUS_FAILED = 0x45; // 'E' — failed transaction block\n\n/** Opaque bridge identity token */\nexport type BridgeId = symbol;\n\nexport const createBridgeId = (): BridgeId => Symbol('bridge');\n\n/**\n * Extracts the ReadyForQuery status byte from a response buffer.\n * Scans from the end since RFQ is always the last message.\n * Returns null if no RFQ found.\n */\nexport const extractRfqStatus = (response: Uint8Array): number | null => {\n // RFQ is always 6 bytes: Z(5a) + length(00000005) + status\n // It's the last message in the response\n if (response.length < 6) return null;\n const i = response.length - 6;\n if (\n response[i] === 0x5a &&\n response[i + 1] === 0x00 &&\n response[i + 2] === 0x00 &&\n response[i + 3] === 0x00 &&\n response[i + 4] === 0x05\n ) {\n return response[i + 5] ?? null;\n }\n return null;\n};\n\n/**\n * Coordinates PGlite access across concurrent pool connections.\n *\n * @remarks\n * PGlite runs PostgreSQL in single-user mode — one session shared by all\n * bridges. The session lock tracks which bridge owns the session during\n * transactions, preventing interleaving. Used internally by {@link PGliteBridge}\n * and created automatically by {@link createPool}. Only instantiate directly\n * if building a custom pool setup.\n */\nexport class SessionLock {\n private owner: BridgeId | null = null;\n private waitQueue: Array<{ id: BridgeId; resolve: () => void }> = [];\n\n /**\n * Acquire access to PGlite. Resolves immediately if no transaction is\n * active or if this bridge owns the current transaction. Queues otherwise.\n */\n async acquire(id: BridgeId): Promise<void> {\n // No owner — session is free\n if (this.owner === null) return;\n // Re-entrant — same bridge already owns the session\n if (this.owner === id) return;\n\n // Another bridge owns the session — wait\n return new Promise<void>((resolve) => {\n this.waitQueue.push({ id, resolve });\n });\n }\n\n /**\n * Update session state based on the ReadyForQuery status byte.\n * Call after every PGlite response that contains RFQ.\n */\n updateStatus(id: BridgeId, status: number): void {\n if (status === STATUS_IN_TRANSACTION || status === STATUS_FAILED) {\n // This bridge now owns the session\n this.owner = id;\n } else if (status === STATUS_IDLE) {\n // Transaction complete — release ownership\n if (this.owner === id) {\n this.owner = null;\n this.drainWaitQueue();\n }\n }\n }\n\n /**\n * Release ownership (e.g., when a bridge is destroyed mid-transaction).\n */\n release(id: BridgeId): void {\n if (this.owner === id) {\n this.owner = null;\n this.drainWaitQueue();\n }\n }\n\n private drainWaitQueue(): void {\n // Release one waiter at a time and grant ownership before resolving.\n // The waiter's operation will call updateStatus when it completes —\n // if IDLE, ownership is cleared and the next waiter is released.\n // This prevents interleaving where multiple waiters race past acquire\n // and one starts a transaction while others proceed unserialized.\n const next = this.waitQueue.shift();\n if (!next) return;\n this.owner = next.id;\n next.resolve();\n }\n}\n","/**\n * PGlite bridge stream.\n *\n * A Duplex stream that replaces the TCP socket in pg.Client, routing\n * wire protocol messages directly to an in-process PGlite instance.\n *\n * pg.Client writes wire protocol bytes → bridge frames messages →\n * PGlite processes via execProtocolRawStream → bridge pushes responses back.\n *\n * Extended Query Protocol pipelines (Parse→Bind→Describe→Execute→Sync) are\n * concatenated into a single buffer and sent as one atomic execProtocolRawStream\n * call within one runExclusive. This prevents portal interleaving between\n * concurrent bridges AND reduces async overhead (1 WASM call instead of 5).\n *\n * The response from a batched pipeline contains spurious ReadyForQuery messages\n * after each sub-message (PGlite's single-user mode). These are stripped,\n * keeping only the final ReadyForQuery after Sync.\n */\nimport { Duplex } from 'node:stream';\nimport type { PGlite } from '@electric-sql/pglite';\nimport {\n type BridgeId,\n createBridgeId,\n extractRfqStatus,\n type SessionLock,\n} from './session-lock.ts';\n\n// Frontend message types\nconst PARSE = 0x50; // P\nconst BIND = 0x42; // B\nconst DESCRIBE = 0x44; // D\nconst EXECUTE = 0x45; // E\nconst CLOSE = 0x43; // C\nconst FLUSH = 0x48; // H\nconst SYNC = 0x53; // S (frontend)\nconst TERMINATE = 0x58; // X\n\n// Backend message type\nconst READY_FOR_QUERY = 0x5a; // Z — 6 bytes: Z + length(5) + status\n\n// Extended Query Protocol message types — must be batched until Sync\nconst EQP_MESSAGES = new Set([PARSE, BIND, DESCRIBE, EXECUTE, CLOSE, FLUSH]);\n\n/**\n * Strips all intermediate ReadyForQuery messages from a response, keeping\n * only the last one. PGlite's single-user mode emits RFQ after every\n * sub-message; pg.Client expects exactly one after Sync.\n *\n * Operates in-place on the response by building a list of byte ranges to\n * keep, then assembling the result. Returns the original buffer (no copy)\n * if there are 0 or 1 RFQ messages.\n */\n/** @internal — exported for testing only */\nexport const stripIntermediateReadyForQuery = (response: Uint8Array): Uint8Array => {\n // Quick scan: count RFQ occurrences and find their positions\n const rfqPositions: number[] = [];\n let offset = 0;\n\n while (offset < response.length) {\n if (offset + 5 >= response.length) break;\n\n if (\n response[offset] === READY_FOR_QUERY &&\n response[offset + 1] === 0x00 &&\n response[offset + 2] === 0x00 &&\n response[offset + 3] === 0x00 &&\n response[offset + 4] === 0x05\n ) {\n rfqPositions.push(offset);\n offset += 6;\n } else {\n // Skip this backend message: type(1) + length(4, big-endian)\n const b1 = response[offset + 1];\n const b2 = response[offset + 2];\n const b3 = response[offset + 3];\n const b4 = response[offset + 4];\n if (b1 === undefined || b2 === undefined || b3 === undefined || b4 === undefined) break;\n const msgLen = ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n if (msgLen < 4) break; // malformed — minimum length field is 4 (includes itself)\n offset += 1 + msgLen;\n }\n }\n\n if (rfqPositions.length <= 1) return response;\n\n // Build result: copy everything except intermediate RFQ messages (all but last)\n const removeCount = rfqPositions.length - 1;\n const resultLen = response.length - removeCount * 6;\n const result = new Uint8Array(resultLen);\n let src = 0;\n let dst = 0;\n let removeIdx = 0;\n\n while (src < response.length) {\n const nextRemove =\n removeIdx < removeCount ? (rfqPositions[removeIdx] ?? response.length) : response.length;\n if (src < nextRemove) {\n const copyLen = nextRemove - src;\n result.set(response.subarray(src, src + copyLen), dst);\n dst += copyLen;\n src += copyLen;\n }\n if (removeIdx < removeCount && src === rfqPositions[removeIdx]) {\n src += 6;\n removeIdx++;\n }\n }\n\n return result;\n};\n\n/**\n * Concatenates multiple Uint8Array views into one contiguous buffer.\n */\nconst concat = (parts: Uint8Array[]): Uint8Array => {\n if (parts.length === 1) return parts[0] ?? new Uint8Array(0);\n const total = parts.reduce((sum, p) => sum + p.length, 0);\n const result = new Uint8Array(total);\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n};\n\n/**\n * Duplex stream that bridges `pg.Client` to an in-process PGlite instance.\n *\n * Replaces the TCP socket in `pg.Client` via the `stream` option. Speaks\n * PostgreSQL wire protocol directly to PGlite — no TCP, no serialization\n * overhead beyond what the wire protocol requires.\n *\n * Pass to `pg.Client` or use via `createPool()` / `createPgliteAdapter()`:\n *\n * ```typescript\n * const client = new pg.Client({\n * stream: () => new PGliteBridge(pglite),\n * });\n * ```\n */\nexport class PGliteBridge extends Duplex {\n private readonly pglite: PGlite;\n private readonly sessionLock: SessionLock | null;\n private readonly bridgeId: BridgeId;\n /** Incoming bytes not yet compacted into buf */\n private pending: Buffer[] = [];\n /** Compacted input buffer for message framing */\n private buf: Buffer = Buffer.alloc(0);\n private phase: 'pre_startup' | 'ready' = 'pre_startup';\n private draining = false;\n private tornDown = false;\n /** Callbacks waiting for drain to process their data */\n private drainQueue: Array<(error?: Error | null) => void> = [];\n /** Buffered EQP messages awaiting Sync */\n private pipeline: Uint8Array[] = [];\n\n constructor(pglite: PGlite, sessionLock?: SessionLock) {\n super();\n this.pglite = pglite;\n this.sessionLock = sessionLock ?? null;\n this.bridgeId = createBridgeId();\n }\n\n // ── Socket compatibility (called by pg's Connection) ──\n\n connect(): this {\n setImmediate(() => this.emit('connect'));\n return this;\n }\n\n setKeepAlive(): this {\n return this;\n }\n\n setNoDelay(): this {\n return this;\n }\n\n setTimeout(): this {\n return this;\n }\n\n ref(): this {\n return this;\n }\n\n unref(): this {\n return this;\n }\n\n // ── Duplex implementation ──\n\n override _read(): void {\n // Data is pushed proactively when PGlite responses arrive\n }\n\n override _write(\n chunk: Buffer,\n _encoding: BufferEncoding,\n callback: (error?: Error | null) => void,\n ): void {\n this.pending.push(chunk);\n this.enqueue(callback);\n }\n\n /** Handles corked batches — pg.Client corks during prepared queries (P+B+D+E+S) */\n override _writev(\n chunks: Array<{ chunk: Buffer; encoding: BufferEncoding }>,\n callback: (error?: Error | null) => void,\n ): void {\n for (const { chunk } of chunks) {\n this.pending.push(chunk);\n }\n this.enqueue(callback);\n }\n\n override _final(callback: (error?: Error | null) => void): void {\n this.sessionLock?.release(this.bridgeId);\n this.push(null);\n callback();\n }\n\n override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {\n this.tornDown = true;\n this.pipeline.length = 0;\n this.pending.length = 0;\n this.sessionLock?.release(this.bridgeId);\n\n // Flush pending write callbacks so pg.Client doesn't hang\n const callbacks = this.drainQueue;\n this.drainQueue = [];\n for (const cb of callbacks) {\n cb(error);\n }\n\n callback(error);\n }\n\n // ── Message processing ──\n\n /** Merge pending chunks into buf only when needed for framing */\n private compact(): void {\n if (this.pending.length === 0) return;\n if (this.buf.length === 0 && this.pending.length === 1) {\n this.buf = this.pending[0] as Buffer;\n } else {\n this.buf = Buffer.concat([this.buf, ...this.pending]);\n }\n this.pending.length = 0;\n }\n\n /**\n * Enqueue a write callback and start draining if not already running.\n * The callback is NOT called until drain has processed the data.\n */\n private enqueue(callback: (error?: Error | null) => void): void {\n this.drainQueue.push(callback);\n if (!this.draining) {\n // Errors are propagated through drainQueue callbacks, not through this promise\n this.drain().catch(() => {});\n }\n }\n\n /**\n * Process all pending data, looping until no new data arrives.\n * Fires all queued callbacks on completion or error.\n */\n private async drain(): Promise<void> {\n if (this.draining) return;\n this.draining = true;\n\n let error: Error | null = null;\n\n try {\n // Loop until no more pending data to process\n while (this.pending.length > 0 || this.buf.length > 0) {\n if (this.tornDown) break;\n\n if (this.phase === 'pre_startup') {\n await this.processPreStartup();\n }\n if (this.phase === 'ready') {\n await this.processMessages();\n }\n\n // If processMessages couldn't consume anything (incomplete message),\n // stop looping — more data will arrive via _write\n if (this.pending.length === 0) break;\n }\n } catch (err) {\n error = err instanceof Error ? err : new Error(String(err));\n // Release session lock on error — prevents permanent deadlock if\n // PGlite crashes mid-transaction (other bridges would wait forever)\n this.sessionLock?.release(this.bridgeId);\n } finally {\n this.draining = false;\n\n // Fire all waiting callbacks\n const callbacks = this.drainQueue;\n this.drainQueue = [];\n for (const cb of callbacks) {\n cb(error);\n }\n }\n }\n\n /**\n * Frames and processes the startup message.\n *\n * Format: [4 bytes: total length] [4 bytes: protocol version] [key\\0value\\0 pairs]\n * No type byte — length includes itself.\n */\n private async processPreStartup(): Promise<void> {\n this.compact();\n if (this.buf.length < 4) return;\n const len = this.buf.readInt32BE(0);\n if (this.buf.length < len) return;\n\n const message = this.buf.subarray(0, len);\n this.buf = this.buf.subarray(len);\n\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n await this.execAndPush(message);\n });\n\n this.phase = 'ready';\n }\n\n /**\n * Frames and processes regular wire protocol messages.\n *\n * Extended Query Protocol messages (Parse, Bind, Describe, Execute, Close,\n * Flush) are buffered in `this.pipeline`. When Sync arrives, the entire\n * pipeline is concatenated and sent to PGlite as one atomic\n * execProtocolRawStream call within one runExclusive.\n *\n * SimpleQuery messages are sent directly (they're self-contained).\n */\n private async processMessages(): Promise<void> {\n this.compact();\n while (this.buf.length >= 5) {\n const len = 1 + this.buf.readInt32BE(1);\n if (len < 5 || this.buf.length < len) break;\n\n const message = this.buf.subarray(0, len);\n this.buf = this.buf.subarray(len);\n const msgType = message[0] ?? 0;\n\n if (msgType === TERMINATE) {\n this.sessionLock?.release(this.bridgeId);\n this.push(null);\n return;\n }\n\n if (EQP_MESSAGES.has(msgType)) {\n this.pipeline.push(message);\n continue;\n }\n\n if (msgType === SYNC) {\n this.pipeline.push(message);\n await this.flushPipeline();\n continue;\n }\n\n // SimpleQuery or other standalone message\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n await this.execAndPush(message);\n });\n }\n }\n\n /**\n * Sends the accumulated EQP pipeline as one atomic operation.\n *\n * All buffered messages are concatenated into a single buffer and sent\n * as one execProtocolRawStream call. This is both correct (prevents\n * portal interleaving) and fast (1 WASM call + 1 async boundary instead\n * of 5). Intermediate ReadyForQuery messages are stripped from the\n * combined response.\n */\n private async flushPipeline(): Promise<void> {\n const messages = this.pipeline;\n this.pipeline = [];\n const batch = concat(messages);\n\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n const chunks: Uint8Array[] = [];\n\n await this.pglite.execProtocolRawStream(batch, {\n onRawData: (chunk: Uint8Array) => chunks.push(chunk),\n });\n\n if (this.tornDown || chunks.length === 0) return;\n\n // Single chunk: strip intermediate RFQ and push\n if (chunks.length === 1) {\n const raw = chunks[0] ?? new Uint8Array(0);\n this.trackSessionStatus(raw);\n const cleaned = stripIntermediateReadyForQuery(raw);\n if (cleaned.length > 0) this.push(cleaned);\n return;\n }\n\n // Multiple chunks: concat first, then strip\n const combined = concat(chunks);\n this.trackSessionStatus(combined);\n const cleaned = stripIntermediateReadyForQuery(combined);\n if (cleaned.length > 0) this.push(cleaned);\n });\n }\n\n /**\n * Sends a message to PGlite and pushes response chunks directly to the\n * stream as they arrive. Avoids collecting and concatenating for large\n * multi-row responses (e.g., findMany 500 rows = ~503 onRawData chunks).\n *\n * Must be called inside runExclusive.\n */\n private async execAndPush(message: Uint8Array): Promise<void> {\n let lastChunk: Uint8Array | null = null;\n await this.pglite.execProtocolRawStream(message, {\n onRawData: (chunk: Uint8Array) => {\n if (!this.tornDown && chunk.length > 0) {\n this.push(chunk);\n lastChunk = chunk;\n }\n },\n });\n if (lastChunk) this.trackSessionStatus(lastChunk);\n }\n\n // ── Session lock helpers ──\n\n private async acquireSession(): Promise<void> {\n await this.sessionLock?.acquire(this.bridgeId);\n }\n\n private trackSessionStatus(response: Uint8Array): void {\n if (!this.sessionLock) return;\n const status = extractRfqStatus(response);\n if (status !== null) {\n this.sessionLock.updateStatus(this.bridgeId, status);\n }\n }\n}\n","/**\n * Pool factory — creates a pg.Pool backed by an in-process PGlite instance.\n *\n * Each pool connection gets its own PGliteBridge stream, all sharing the\n * same PGlite WASM instance and SessionLock. The session lock ensures\n * transaction isolation: when one bridge starts a transaction (BEGIN),\n * it gets exclusive PGlite access until COMMIT/ROLLBACK. Non-transactional\n * operations from any bridge serialize through PGlite's runExclusive mutex.\n */\nimport { type Extensions, PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { PGliteBridge } from './pglite-bridge.ts';\nimport { SessionLock } from './session-lock.ts';\n\nconst { Client, Pool } = pg;\n\nexport interface CreatePoolOptions {\n /** PGlite data directory. Omit for in-memory. */\n dataDir?: string;\n\n /** PGlite extensions (e.g., `{ uuid_ossp: uuidOssp() }`) */\n extensions?: Extensions;\n\n /** Maximum pool connections (default: 5) */\n max?: number;\n\n /** Existing PGlite instance to use instead of creating one */\n pglite?: PGlite;\n}\n\nexport interface PoolResult {\n /** pg.Pool backed by PGlite — pass to PrismaPg */\n pool: pg.Pool;\n\n /** The underlying PGlite instance */\n pglite: PGlite;\n\n /** Shut down pool and PGlite */\n close: () => Promise<void>;\n}\n\n/**\n * Creates a pg.Pool where every connection is an in-process PGlite bridge.\n *\n * Most users should prefer {@link createPgliteAdapter}, which wraps this\n * function and also handles schema application and reset/snapshot lifecycle.\n *\n * ```typescript\n * import { createPool } from 'prisma-pglite-bridge';\n * import { PrismaPg } from '@prisma/adapter-pg';\n * import { PrismaClient } from '@prisma/client';\n *\n * const { pool, close } = await createPool();\n * const adapter = new PrismaPg(pool);\n * const prisma = new PrismaClient({ adapter });\n * ```\n *\n * @see {@link createPgliteAdapter} for the higher-level API with schema management.\n */\nexport const createPool = async (options: CreatePoolOptions = {}): Promise<PoolResult> => {\n const { dataDir, extensions, max = 5 } = options;\n const ownsInstance = !options.pglite;\n\n const pglite = options.pglite ?? new PGlite(dataDir, extensions ? { extensions } : undefined);\n await pglite.waitReady;\n\n const sessionLock = new SessionLock();\n\n // Subclass pg.Client to inject PGliteBridge as the stream\n const BridgedClient = class extends Client {\n constructor(config?: string | pg.ClientConfig) {\n const cfg = typeof config === 'string' ? { connectionString: config } : (config ?? {});\n super({\n ...cfg,\n user: 'postgres',\n database: 'postgres',\n stream: (() => new PGliteBridge(pglite, sessionLock)) as pg.ClientConfig['stream'],\n });\n }\n };\n\n const pool = new Pool({\n Client: BridgedClient as typeof Client,\n max,\n });\n\n const close = async () => {\n await pool.end();\n if (ownsInstance) {\n await pglite.close();\n }\n };\n\n return { pool, pglite, close };\n};\n","/**\n * Creates a Prisma adapter backed by in-process PGlite.\n *\n * No TCP, no Docker, no worker threads — everything runs in the same process.\n * Works for testing, development, seeding, and scripts.\n *\n * ```typescript\n * import { createPgliteAdapter } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const { adapter, resetDb } = await createPgliteAdapter();\n * const prisma = new PrismaClient({ adapter });\n *\n * beforeEach(() => resetDb());\n * ```\n */\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { createPool } from './create-pool.ts';\n\nconst SNAPSHOT_SCHEMA = '_pglite_snapshot';\nconst SENTINEL_SCHEMA = '_pglite_bridge';\nconst SENTINEL_TABLE = '__initialized';\nconst SENTINEL_MARKER = 'prisma-pglite-bridge:init:v1';\n\nconst USER_TABLES_WHERE = `schemaname NOT IN ('pg_catalog', 'information_schema')\n AND schemaname != '${SNAPSHOT_SCHEMA}'\n AND schemaname != '${SENTINEL_SCHEMA}'\n AND tablename NOT LIKE '_prisma%'`;\n\nconst escapeLiteral = (s: string) => `'${s.replace(/'/g, \"''\")}'`;\n\nconst isValidSentinelRow = (rows: Array<{ marker: string; version: number }>) =>\n rows.length === 1 && rows[0]?.marker === SENTINEL_MARKER && rows[0]?.version === 1;\n\nexport interface CreatePgliteAdapterOptions {\n /** Path to prisma/migrations/ directory (auto-discovered via prisma.config.ts if omitted) */\n migrationsPath?: string;\n\n /** Pre-generated SQL to apply instead of auto-generating from schema */\n sql?: string;\n\n /** Root directory for prisma.config.ts discovery (default: process.cwd()). Set this in monorepos where tests run from the workspace root. */\n configRoot?: string;\n\n /** PGlite data directory. Omit for in-memory. */\n dataDir?: string;\n\n /** PGlite extensions (e.g., `{ uuid_ossp: uuidOssp() }`) */\n extensions?: import('@electric-sql/pglite').Extensions;\n\n /** Maximum pool connections (default: 5) */\n max?: number;\n}\n\n/** Clear all user tables. Call in `beforeEach` for per-test isolation. */\nexport type ResetDbFn = () => Promise<void>;\n\nexport type SnapshotDbFn = () => Promise<void>;\n\nexport type ResetSnapshotFn = () => Promise<void>;\n\nexport interface PgliteAdapter {\n /** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */\n adapter: PrismaPg;\n\n /**\n * The underlying PGlite instance for direct SQL, snapshots, or extensions.\n *\n * @remarks\n * Direct `pglite.exec()` / `pglite.query()` calls bypass the pool's\n * {@link SessionLock}. Avoid mixing direct calls with Prisma operations\n * inside a transaction — use them only for setup, teardown, or utilities\n * that run outside active Prisma transactions.\n */\n pglite: import('@electric-sql/pglite').PGlite;\n\n /** Clear all user tables. Call in `beforeEach` for per-test isolation. */\n resetDb: ResetDbFn;\n\n /** Snapshot current DB state. Subsequent `resetDb` calls restore to this snapshot. */\n snapshotDb: SnapshotDbFn;\n\n /** Discard the current snapshot. Subsequent `resetDb` calls truncate to empty. */\n resetSnapshot: ResetSnapshotFn;\n\n /** Shut down pool and PGlite. Not needed in tests (process exit handles it). */\n close: () => Promise<void>;\n}\n\n/**\n * Discover the migrations directory via Prisma's config API.\n * Uses the same resolution as `prisma migrate dev` — reads prisma.config.ts,\n * resolves paths relative to config file location.\n *\n * Returns null if @prisma/config is not available or config cannot be loaded.\n */\nconst discoverMigrationsPath = async (configRoot?: string): Promise<string | null> => {\n try {\n const { loadConfigFromFile } = await import('@prisma/config');\n const { config, error } = await loadConfigFromFile({ configRoot: configRoot ?? process.cwd() });\n if (error) return null;\n\n // Explicit migrations path from prisma.config.ts\n if (config.migrations?.path) return config.migrations.path;\n\n // Fallback: Prisma convention is {schemaDir}/migrations\n const schemaPath = config.schema;\n if (schemaPath) return join(dirname(schemaPath), 'migrations');\n\n return null;\n } catch {\n return null;\n }\n};\n\n/**\n * Read migration SQL files from a migrations directory in directory order.\n * Returns null if the directory doesn't exist or has no migration files.\n */\nconst tryReadMigrationFiles = (migrationsPath: string): string | null => {\n if (!existsSync(migrationsPath)) return null;\n\n const dirs = readdirSync(migrationsPath)\n .filter((d) => statSync(join(migrationsPath, d)).isDirectory())\n .sort();\n\n const sqlParts: string[] = [];\n for (const dir of dirs) {\n const sqlPath = join(migrationsPath, dir, 'migration.sql');\n if (existsSync(sqlPath)) {\n sqlParts.push(readFileSync(sqlPath, 'utf8'));\n }\n }\n\n return sqlParts.length > 0 ? sqlParts.join('\\n') : null;\n};\n\n/**\n * Resolve schema SQL. Priority:\n * 1. Explicit `sql` option — use directly\n * 2. Explicit `migrationsPath` — read migration files\n * 3. Auto-discovered migrations (via prisma.config.ts) — read migration files\n * 4. Error — tell the user to generate migration files\n */\nconst resolveSQL = async (options: CreatePgliteAdapterOptions): Promise<string> => {\n if (options.sql) return options.sql;\n\n // Explicit migrationsPath\n if (options.migrationsPath) {\n const sql = tryReadMigrationFiles(options.migrationsPath);\n if (sql) return sql;\n throw new Error(\n `No migration.sql files found in ${options.migrationsPath}. Run \\`prisma migrate dev\\` to generate migration files.`,\n );\n }\n\n // Auto-discover via Prisma config\n const migrationsPath = await discoverMigrationsPath(options.configRoot);\n\n if (migrationsPath) {\n const sql = tryReadMigrationFiles(migrationsPath);\n if (sql) return sql;\n\n throw new Error(\n `No migration.sql files found in auto-discovered path ${migrationsPath}. ` +\n 'Run `prisma migrate dev` to generate migration files, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n }\n\n if (options.configRoot) {\n throw new Error(\n `prisma.config.ts loaded from configRoot (${options.configRoot}) but no schema ` +\n 'or migrations path could be resolved. Ensure your config specifies a schema path, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n }\n\n throw new Error(\n 'No migration files found and no prisma.config.ts could be loaded. ' +\n 'Run `prisma migrate dev` to generate them, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n};\n\n/**\n * Creates a Prisma adapter backed by an in-process PGlite instance.\n *\n * Applies the schema and returns a ready-to-use adapter + a `resetDb`\n * function for clearing tables between tests.\n */\nexport const createPgliteAdapter = async (\n options: CreatePgliteAdapterOptions = {},\n): Promise<PgliteAdapter> => {\n const {\n pool,\n pglite,\n close: poolClose,\n } = await createPool({\n dataDir: options.dataDir,\n extensions: options.extensions,\n max: options.max,\n });\n\n const sentinelStatements = [\n `CREATE SCHEMA IF NOT EXISTS \"${SENTINEL_SCHEMA}\"`,\n `CREATE TABLE IF NOT EXISTS \"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\" (marker text PRIMARY KEY, version int NOT NULL)`,\n `INSERT INTO \"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\" (marker, version) VALUES (${escapeLiteral(SENTINEL_MARKER)}, 1) ON CONFLICT (marker) DO NOTHING`,\n ];\n\n const collisionError = () =>\n `\"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\" exists but is not owned by prisma-pglite-bridge. ` +\n `The \"${SENTINEL_SCHEMA}\" schema is reserved for library metadata.`;\n\n const writeSentinel = async () => {\n let committed = false;\n try {\n await pglite.exec(`BEGIN;\\n${sentinelStatements.join(';\\n')}`);\n\n const { rows } = await pglite.query<{ marker: string; version: number }>(\n `SELECT marker, version FROM \"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\"`,\n );\n if (!isValidSentinelRow(rows)) throw new Error(collisionError());\n await pglite.exec('COMMIT');\n committed = true;\n } catch (err) {\n if (!committed) await pglite.exec('ROLLBACK');\n throw err instanceof Error && err.message === collisionError()\n ? err\n : new Error(collisionError(), { cause: err });\n }\n };\n\n const isInitialized = async () => {\n const { rows: tableExists } = await pglite.query<{ found: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM pg_tables\n WHERE schemaname = '${SENTINEL_SCHEMA}' AND tablename = '${SENTINEL_TABLE}'\n ) AS found`,\n );\n\n if (tableExists[0]?.found) {\n try {\n const { rows: allRows } = await pglite.query<{ marker: string; version: number }>(\n `SELECT marker, version FROM \"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\"`,\n );\n if (isValidSentinelRow(allRows)) return true;\n } catch {\n // Table has incompatible columns — fall through to collision error\n }\n\n throw new Error(collisionError());\n }\n\n const { rows: schemaExists } = await pglite.query<{ found: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM pg_namespace WHERE nspname = '${SENTINEL_SCHEMA}'\n ) AS found`,\n );\n if (schemaExists[0]?.found) {\n throw new Error(\n `Schema \"${SENTINEL_SCHEMA}\" exists but is not owned by prisma-pglite-bridge. ` +\n `The \"${SENTINEL_SCHEMA}\" schema is reserved for library metadata.`,\n );\n }\n\n const { rows: legacy } = await pglite.query<{ initialized: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid\n WHERE n.nspname = 'public'\n UNION ALL\n SELECT 1 FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid\n WHERE n.nspname = 'public' AND t.typtype NOT IN ('b', 'p')\n UNION ALL\n SELECT 1 FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid\n WHERE n.nspname = 'public'\n UNION ALL\n SELECT 1 FROM pg_namespace\n WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'public')\n ) AS initialized`,\n );\n if (legacy[0]?.initialized) {\n await writeSentinel();\n return true;\n }\n\n return false;\n };\n\n if (!options.dataDir || !(await isInitialized())) {\n const sql = await resolveSQL(options);\n const isMigrationSQL = !options.sql;\n\n if (isMigrationSQL) {\n let committed = false;\n try {\n await pglite.exec(`BEGIN;\\n${sql};\\n${sentinelStatements.join(';\\n')}`);\n\n const { rows: verify } = await pglite.query<{ marker: string; version: number }>(\n `SELECT marker, version FROM \"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\"`,\n );\n if (!isValidSentinelRow(verify)) throw new Error(collisionError());\n await pglite.exec('COMMIT');\n committed = true;\n } catch (err) {\n if (!committed) await pglite.exec('ROLLBACK');\n if (err instanceof Error && err.message === collisionError()) throw err;\n throw new Error(\n 'Failed to apply schema SQL to PGlite. Check your schema or migration files.',\n { cause: err },\n );\n }\n } else {\n try {\n await pglite.exec(sql);\n } catch (err) {\n throw new Error(\n 'Failed to apply schema SQL to PGlite. Check your schema or migration files.',\n { cause: err },\n );\n }\n await writeSentinel();\n }\n }\n\n const adapter = new PrismaPg(pool);\n\n let cachedTables: string | null = null;\n let hasSnapshot = false;\n\n const discoverTables = async () => {\n if (cachedTables !== null) return cachedTables;\n const { rows } = await pglite.query<{ qualified: string }>(\n `SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified\n FROM pg_tables\n WHERE ${USER_TABLES_WHERE}`,\n );\n cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(', ') : '';\n return cachedTables;\n };\n\n const snapshotDb: SnapshotDbFn = async () => {\n await pglite.exec(`DROP SCHEMA IF EXISTS \"${SNAPSHOT_SCHEMA}\" CASCADE`);\n\n try {\n await pglite.exec('BEGIN');\n await pglite.exec(`CREATE SCHEMA \"${SNAPSHOT_SCHEMA}\"`);\n\n const { rows: tables } = await pglite.query<{\n schemaname: string;\n tablename: string;\n qualified: string;\n }>(\n `SELECT schemaname, tablename,\n quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified\n FROM pg_tables\n WHERE ${USER_TABLES_WHERE}`,\n );\n\n await pglite.exec(\n `CREATE TABLE \"${SNAPSHOT_SCHEMA}\".__tables (snap_name text, source_schema text, source_table text)`,\n );\n\n for (const [i, { schemaname, tablename, qualified }] of tables.entries()) {\n const snapName = `_snap_${i}`;\n await pglite.exec(\n `CREATE TABLE \"${SNAPSHOT_SCHEMA}\".\"${snapName}\" AS SELECT * FROM ${qualified}`,\n );\n await pglite.exec(\n `INSERT INTO \"${SNAPSHOT_SCHEMA}\".__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`,\n );\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(schemaname || '.' || sequencename) AS name, last_value::text AS value\n FROM pg_sequences\n WHERE schemaname NOT IN ('pg_catalog', 'information_schema')\n AND schemaname != '${SNAPSHOT_SCHEMA}'\n AND last_value IS NOT NULL`,\n );\n\n await pglite.exec(`CREATE TABLE \"${SNAPSHOT_SCHEMA}\".__sequences (name text, value bigint)`);\n for (const { name, value } of seqs) {\n await pglite.exec(\n `INSERT INTO \"${SNAPSHOT_SCHEMA}\".__sequences VALUES (${name}, ${value})`,\n );\n }\n\n await pglite.exec('COMMIT');\n } catch (err) {\n await pglite.exec('ROLLBACK');\n await pglite.exec(`DROP SCHEMA IF EXISTS \"${SNAPSHOT_SCHEMA}\" CASCADE`);\n throw err;\n }\n\n hasSnapshot = true;\n };\n\n const resetSnapshot: ResetSnapshotFn = async () => {\n hasSnapshot = false;\n await pglite.exec(`DROP SCHEMA IF EXISTS \"${SNAPSHOT_SCHEMA}\" CASCADE`);\n };\n\n const resetDb = async () => {\n cachedTables = null;\n const tables = await discoverTables();\n\n if (hasSnapshot && tables) {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);\n\n const { rows: snapshotTables } = await pglite.query<{\n snap_name: string;\n qualified: string;\n }>(\n `SELECT snap_name, quote_ident(source_schema) || '.' || quote_ident(source_table) AS qualified\n FROM \"${SNAPSHOT_SCHEMA}\".__tables`,\n );\n\n for (const { snap_name, qualified } of snapshotTables) {\n await pglite.exec(\n `INSERT INTO ${qualified} SELECT * FROM \"${SNAPSHOT_SCHEMA}\".\"${snap_name}\"`,\n );\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(name) AS name, value::text AS value FROM \"${SNAPSHOT_SCHEMA}\".__sequences`,\n );\n\n for (const { name, value } of seqs) {\n await pglite.exec(`SELECT setval(${name}, ${value})`);\n }\n } finally {\n await pglite.exec('SET session_replication_role = DEFAULT');\n }\n } else if (tables) {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);\n } finally {\n await pglite.exec('SET session_replication_role = DEFAULT');\n }\n }\n\n await pglite.exec('RESET ALL');\n await pglite.exec('DEALLOCATE ALL');\n };\n\n return { adapter, pglite, resetDb, snapshotDb, resetSnapshot, close: poolClose };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBA,MAAM,cAAc;AACpB,MAAM,wBAAwB;AAC9B,MAAM,gBAAgB;AAKtB,MAAa,uBAAiC,OAAO,SAAS;;;;;;AAO9D,MAAa,oBAAoB,aAAwC;AAGvE,KAAI,SAAS,SAAS,EAAG,QAAO;CAChC,MAAM,IAAI,SAAS,SAAS;AAC5B,KACE,SAAS,OAAO,MAChB,SAAS,IAAI,OAAO,KACpB,SAAS,IAAI,OAAO,KACpB,SAAS,IAAI,OAAO,KACpB,SAAS,IAAI,OAAO,EAEpB,QAAO,SAAS,IAAI,MAAM;AAE5B,QAAO;;;;;;;;;;;;AAaT,IAAa,cAAb,MAAyB;CACvB,QAAiC;CACjC,YAAkE,EAAE;;;;;CAMpE,MAAM,QAAQ,IAA6B;AAEzC,MAAI,KAAK,UAAU,KAAM;AAEzB,MAAI,KAAK,UAAU,GAAI;AAGvB,SAAO,IAAI,SAAe,YAAY;AACpC,QAAK,UAAU,KAAK;IAAE;IAAI;IAAS,CAAC;IACpC;;;;;;CAOJ,aAAa,IAAc,QAAsB;AAC/C,MAAI,WAAW,yBAAyB,WAAW,cAEjD,MAAK,QAAQ;WACJ,WAAW;OAEhB,KAAK,UAAU,IAAI;AACrB,SAAK,QAAQ;AACb,SAAK,gBAAgB;;;;;;;CAQ3B,QAAQ,IAAoB;AAC1B,MAAI,KAAK,UAAU,IAAI;AACrB,QAAK,QAAQ;AACb,QAAK,gBAAgB;;;CAIzB,iBAA+B;EAM7B,MAAM,OAAO,KAAK,UAAU,OAAO;AACnC,MAAI,CAAC,KAAM;AACX,OAAK,QAAQ,KAAK;AAClB,OAAK,SAAS;;;;;;;;;;;;;;;;;;;;;;;ACtFlB,MAAM,QAAQ;AACd,MAAM,OAAO;AACb,MAAM,WAAW;AACjB,MAAM,UAAU;AAChB,MAAM,QAAQ;AACd,MAAM,QAAQ;AACd,MAAM,OAAO;AACb,MAAM,YAAY;AAGlB,MAAM,kBAAkB;AAGxB,MAAM,eAAe,IAAI,IAAI;CAAC;CAAO;CAAM;CAAU;CAAS;CAAO;CAAM,CAAC;;;;;;;;;;;AAY5E,MAAa,kCAAkC,aAAqC;CAElF,MAAM,eAAyB,EAAE;CACjC,IAAI,SAAS;AAEb,QAAO,SAAS,SAAS,QAAQ;AAC/B,MAAI,SAAS,KAAK,SAAS,OAAQ;AAEnC,MACE,SAAS,YAAY,mBACrB,SAAS,SAAS,OAAO,KACzB,SAAS,SAAS,OAAO,KACzB,SAAS,SAAS,OAAO,KACzB,SAAS,SAAS,OAAO,GACzB;AACA,gBAAa,KAAK,OAAO;AACzB,aAAU;SACL;GAEL,MAAM,KAAK,SAAS,SAAS;GAC7B,MAAM,KAAK,SAAS,SAAS;GAC7B,MAAM,KAAK,SAAS,SAAS;GAC7B,MAAM,KAAK,SAAS,SAAS;AAC7B,OAAI,OAAO,KAAA,KAAa,OAAO,KAAA,KAAa,OAAO,KAAA,KAAa,OAAO,KAAA,EAAW;GAClF,MAAM,UAAW,MAAM,KAAO,MAAM,KAAO,MAAM,IAAK,QAAQ;AAC9D,OAAI,SAAS,EAAG;AAChB,aAAU,IAAI;;;AAIlB,KAAI,aAAa,UAAU,EAAG,QAAO;CAGrC,MAAM,cAAc,aAAa,SAAS;CAC1C,MAAM,YAAY,SAAS,SAAS,cAAc;CAClD,MAAM,SAAS,IAAI,WAAW,UAAU;CACxC,IAAI,MAAM;CACV,IAAI,MAAM;CACV,IAAI,YAAY;AAEhB,QAAO,MAAM,SAAS,QAAQ;EAC5B,MAAM,aACJ,YAAY,cAAe,aAAa,cAAc,SAAS,SAAU,SAAS;AACpF,MAAI,MAAM,YAAY;GACpB,MAAM,UAAU,aAAa;AAC7B,UAAO,IAAI,SAAS,SAAS,KAAK,MAAM,QAAQ,EAAE,IAAI;AACtD,UAAO;AACP,UAAO;;AAET,MAAI,YAAY,eAAe,QAAQ,aAAa,YAAY;AAC9D,UAAO;AACP;;;AAIJ,QAAO;;;;;AAMT,MAAM,UAAU,UAAoC;AAClD,KAAI,MAAM,WAAW,EAAG,QAAO,MAAM,MAAM,IAAI,WAAW,EAAE;CAC5D,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,EAAE;CACzD,MAAM,SAAS,IAAI,WAAW,MAAM;CACpC,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,OAAO;AACxB,SAAO,IAAI,MAAM,OAAO;AACxB,YAAU,KAAK;;AAEjB,QAAO;;;;;;;;;;;;;;;;;AAkBT,IAAa,eAAb,cAAkCA,YAAAA,OAAO;CACvC;CACA;CACA;;CAEA,UAA4B,EAAE;;CAE9B,MAAsB,OAAO,MAAM,EAAE;CACrC,QAAyC;CACzC,WAAmB;CACnB,WAAmB;;CAEnB,aAA4D,EAAE;;CAE9D,WAAiC,EAAE;CAEnC,YAAY,QAAgB,aAA2B;AACrD,SAAO;AACP,OAAK,SAAS;AACd,OAAK,cAAc,eAAe;AAClC,OAAK,WAAW,gBAAgB;;CAKlC,UAAgB;AACd,qBAAmB,KAAK,KAAK,UAAU,CAAC;AACxC,SAAO;;CAGT,eAAqB;AACnB,SAAO;;CAGT,aAAmB;AACjB,SAAO;;CAGT,aAAmB;AACjB,SAAO;;CAGT,MAAY;AACV,SAAO;;CAGT,QAAc;AACZ,SAAO;;CAKT,QAAuB;CAIvB,OACE,OACA,WACA,UACM;AACN,OAAK,QAAQ,KAAK,MAAM;AACxB,OAAK,QAAQ,SAAS;;;CAIxB,QACE,QACA,UACM;AACN,OAAK,MAAM,EAAE,WAAW,OACtB,MAAK,QAAQ,KAAK,MAAM;AAE1B,OAAK,QAAQ,SAAS;;CAGxB,OAAgB,UAAgD;AAC9D,OAAK,aAAa,QAAQ,KAAK,SAAS;AACxC,OAAK,KAAK,KAAK;AACf,YAAU;;CAGZ,SAAkB,OAAqB,UAAgD;AACrF,OAAK,WAAW;AAChB,OAAK,SAAS,SAAS;AACvB,OAAK,QAAQ,SAAS;AACtB,OAAK,aAAa,QAAQ,KAAK,SAAS;EAGxC,MAAM,YAAY,KAAK;AACvB,OAAK,aAAa,EAAE;AACpB,OAAK,MAAM,MAAM,UACf,IAAG,MAAM;AAGX,WAAS,MAAM;;;CAMjB,UAAwB;AACtB,MAAI,KAAK,QAAQ,WAAW,EAAG;AAC/B,MAAI,KAAK,IAAI,WAAW,KAAK,KAAK,QAAQ,WAAW,EACnD,MAAK,MAAM,KAAK,QAAQ;MAExB,MAAK,MAAM,OAAO,OAAO,CAAC,KAAK,KAAK,GAAG,KAAK,QAAQ,CAAC;AAEvD,OAAK,QAAQ,SAAS;;;;;;CAOxB,QAAgB,UAAgD;AAC9D,OAAK,WAAW,KAAK,SAAS;AAC9B,MAAI,CAAC,KAAK,SAER,MAAK,OAAO,CAAC,YAAY,GAAG;;;;;;CAQhC,MAAc,QAAuB;AACnC,MAAI,KAAK,SAAU;AACnB,OAAK,WAAW;EAEhB,IAAI,QAAsB;AAE1B,MAAI;AAEF,UAAO,KAAK,QAAQ,SAAS,KAAK,KAAK,IAAI,SAAS,GAAG;AACrD,QAAI,KAAK,SAAU;AAEnB,QAAI,KAAK,UAAU,cACjB,OAAM,KAAK,mBAAmB;AAEhC,QAAI,KAAK,UAAU,QACjB,OAAM,KAAK,iBAAiB;AAK9B,QAAI,KAAK,QAAQ,WAAW,EAAG;;WAE1B,KAAK;AACZ,WAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC;AAG3D,QAAK,aAAa,QAAQ,KAAK,SAAS;YAChC;AACR,QAAK,WAAW;GAGhB,MAAM,YAAY,KAAK;AACvB,QAAK,aAAa,EAAE;AACpB,QAAK,MAAM,MAAM,UACf,IAAG,MAAM;;;;;;;;;CAWf,MAAc,oBAAmC;AAC/C,OAAK,SAAS;AACd,MAAI,KAAK,IAAI,SAAS,EAAG;EACzB,MAAM,MAAM,KAAK,IAAI,YAAY,EAAE;AACnC,MAAI,KAAK,IAAI,SAAS,IAAK;EAE3B,MAAM,UAAU,KAAK,IAAI,SAAS,GAAG,IAAI;AACzC,OAAK,MAAM,KAAK,IAAI,SAAS,IAAI;AAEjC,QAAM,KAAK,gBAAgB;AAC3B,QAAM,KAAK,OAAO,aAAa,YAAY;AACzC,SAAM,KAAK,YAAY,QAAQ;IAC/B;AAEF,OAAK,QAAQ;;;;;;;;;;;;CAaf,MAAc,kBAAiC;AAC7C,OAAK,SAAS;AACd,SAAO,KAAK,IAAI,UAAU,GAAG;GAC3B,MAAM,MAAM,IAAI,KAAK,IAAI,YAAY,EAAE;AACvC,OAAI,MAAM,KAAK,KAAK,IAAI,SAAS,IAAK;GAEtC,MAAM,UAAU,KAAK,IAAI,SAAS,GAAG,IAAI;AACzC,QAAK,MAAM,KAAK,IAAI,SAAS,IAAI;GACjC,MAAM,UAAU,QAAQ,MAAM;AAE9B,OAAI,YAAY,WAAW;AACzB,SAAK,aAAa,QAAQ,KAAK,SAAS;AACxC,SAAK,KAAK,KAAK;AACf;;AAGF,OAAI,aAAa,IAAI,QAAQ,EAAE;AAC7B,SAAK,SAAS,KAAK,QAAQ;AAC3B;;AAGF,OAAI,YAAY,MAAM;AACpB,SAAK,SAAS,KAAK,QAAQ;AAC3B,UAAM,KAAK,eAAe;AAC1B;;AAIF,SAAM,KAAK,gBAAgB;AAC3B,SAAM,KAAK,OAAO,aAAa,YAAY;AACzC,UAAM,KAAK,YAAY,QAAQ;KAC/B;;;;;;;;;;;;CAaN,MAAc,gBAA+B;EAC3C,MAAM,WAAW,KAAK;AACtB,OAAK,WAAW,EAAE;EAClB,MAAM,QAAQ,OAAO,SAAS;AAE9B,QAAM,KAAK,gBAAgB;AAC3B,QAAM,KAAK,OAAO,aAAa,YAAY;GACzC,MAAM,SAAuB,EAAE;AAE/B,SAAM,KAAK,OAAO,sBAAsB,OAAO,EAC7C,YAAY,UAAsB,OAAO,KAAK,MAAM,EACrD,CAAC;AAEF,OAAI,KAAK,YAAY,OAAO,WAAW,EAAG;AAG1C,OAAI,OAAO,WAAW,GAAG;IACvB,MAAM,MAAM,OAAO,MAAM,IAAI,WAAW,EAAE;AAC1C,SAAK,mBAAmB,IAAI;IAC5B,MAAM,UAAU,+BAA+B,IAAI;AACnD,QAAI,QAAQ,SAAS,EAAG,MAAK,KAAK,QAAQ;AAC1C;;GAIF,MAAM,WAAW,OAAO,OAAO;AAC/B,QAAK,mBAAmB,SAAS;GACjC,MAAM,UAAU,+BAA+B,SAAS;AACxD,OAAI,QAAQ,SAAS,EAAG,MAAK,KAAK,QAAQ;IAC1C;;;;;;;;;CAUJ,MAAc,YAAY,SAAoC;EAC5D,IAAI,YAA+B;AACnC,QAAM,KAAK,OAAO,sBAAsB,SAAS,EAC/C,YAAY,UAAsB;AAChC,OAAI,CAAC,KAAK,YAAY,MAAM,SAAS,GAAG;AACtC,SAAK,KAAK,MAAM;AAChB,gBAAY;;KAGjB,CAAC;AACF,MAAI,UAAW,MAAK,mBAAmB,UAAU;;CAKnD,MAAc,iBAAgC;AAC5C,QAAM,KAAK,aAAa,QAAQ,KAAK,SAAS;;CAGhD,mBAA2B,UAA4B;AACrD,MAAI,CAAC,KAAK,YAAa;EACvB,MAAM,SAAS,iBAAiB,SAAS;AACzC,MAAI,WAAW,KACb,MAAK,YAAY,aAAa,KAAK,UAAU,OAAO;;;;;;;;;;;;;;AChb1D,MAAM,EAAE,QAAQ,SAAS,GAAA;;;;;;;;;;;;;;;;;;;AA6CzB,MAAa,aAAa,OAAO,UAA6B,EAAE,KAA0B;CACxF,MAAM,EAAE,SAAS,YAAY,MAAM,MAAM;CACzC,MAAM,eAAe,CAAC,QAAQ;CAE9B,MAAM,SAAS,QAAQ,UAAU,IAAIC,qBAAAA,OAAO,SAAS,aAAa,EAAE,YAAY,GAAG,KAAA,EAAU;AAC7F,OAAM,OAAO;CAEb,MAAM,cAAc,IAAI,aAAa;CAGrC,MAAM,gBAAgB,cAAc,OAAO;EACzC,YAAY,QAAmC;AAE7C,SAAM;IACJ,GAFU,OAAO,WAAW,WAAW,EAAE,kBAAkB,QAAQ,GAAI,UAAU,EAAE;IAGnF,MAAM;IACN,UAAU;IACV,eAAe,IAAI,aAAa,QAAQ,YAAY;IACrD,CAAC;;;CAIN,MAAM,OAAO,IAAI,KAAK;EACpB,QAAQ;EACR;EACD,CAAC;CAEF,MAAM,QAAQ,YAAY;AACxB,QAAM,KAAK,KAAK;AAChB,MAAI,aACF,OAAM,OAAO,OAAO;;AAIxB,QAAO;EAAE;EAAM;EAAQ;EAAO;;;;;;;;;;;;;;;;;;;;ACxEhC,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;AAExB,MAAM,oBAAoB;4BACE,gBAAgB;4BAChB,gBAAgB;;AAG5C,MAAM,iBAAiB,MAAc,IAAI,EAAE,QAAQ,MAAM,KAAK,CAAC;AAE/D,MAAM,sBAAsB,SAC1B,KAAK,WAAW,KAAK,KAAK,IAAI,WAAW,mBAAmB,KAAK,IAAI,YAAY;;;;;;;;AAgEnF,MAAM,yBAAyB,OAAO,eAAgD;AACpF,KAAI;EACF,MAAM,EAAE,uBAAuB,MAAM,OAAO;EAC5C,MAAM,EAAE,QAAQ,UAAU,MAAM,mBAAmB,EAAE,YAAY,cAAc,QAAQ,KAAK,EAAE,CAAC;AAC/F,MAAI,MAAO,QAAO;AAGlB,MAAI,OAAO,YAAY,KAAM,QAAO,OAAO,WAAW;EAGtD,MAAM,aAAa,OAAO;AAC1B,MAAI,WAAY,SAAA,GAAA,UAAA,OAAA,GAAA,UAAA,SAAoB,WAAW,EAAE,aAAa;AAE9D,SAAO;SACD;AACN,SAAO;;;;;;;AAQX,MAAM,yBAAyB,mBAA0C;AACvE,KAAI,EAAA,GAAA,QAAA,YAAY,eAAe,CAAE,QAAO;CAExC,MAAM,QAAA,GAAA,QAAA,aAAmB,eAAe,CACrC,QAAQ,OAAA,GAAA,QAAA,WAAA,GAAA,UAAA,MAAoB,gBAAgB,EAAE,CAAC,CAAC,aAAa,CAAC,CAC9D,MAAM;CAET,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,WAAA,GAAA,UAAA,MAAe,gBAAgB,KAAK,gBAAgB;AAC1D,OAAA,GAAA,QAAA,YAAe,QAAQ,CACrB,UAAS,MAAA,GAAA,QAAA,cAAkB,SAAS,OAAO,CAAC;;AAIhD,QAAO,SAAS,SAAS,IAAI,SAAS,KAAK,KAAK,GAAG;;;;;;;;;AAUrD,MAAM,aAAa,OAAO,YAAyD;AACjF,KAAI,QAAQ,IAAK,QAAO,QAAQ;AAGhC,KAAI,QAAQ,gBAAgB;EAC1B,MAAM,MAAM,sBAAsB,QAAQ,eAAe;AACzD,MAAI,IAAK,QAAO;AAChB,QAAM,IAAI,MACR,mCAAmC,QAAQ,eAAe,2DAC3D;;CAIH,MAAM,iBAAiB,MAAM,uBAAuB,QAAQ,WAAW;AAEvE,KAAI,gBAAgB;EAClB,MAAM,MAAM,sBAAsB,eAAe;AACjD,MAAI,IAAK,QAAO;AAEhB,QAAM,IAAI,MACR,wDAAwD,eAAe,6GAGxE;;AAGH,KAAI,QAAQ,WACV,OAAM,IAAI,MACR,4CAA4C,QAAQ,WAAW,qJAGhE;AAGH,OAAM,IAAI,MACR,+JAGD;;;;;;;;AASH,MAAa,sBAAsB,OACjC,UAAsC,EAAE,KACb;CAC3B,MAAM,EACJ,MACA,QACA,OAAO,cACL,MAAM,WAAW;EACnB,SAAS,QAAQ;EACjB,YAAY,QAAQ;EACpB,KAAK,QAAQ;EACd,CAAC;CAEF,MAAM,qBAAqB;EACzB,gCAAgC,gBAAgB;EAChD,+BAA+B,gBAAgB,KAAK,eAAe;EACnE,gBAAgB,gBAAgB,KAAK,eAAe,8BAA8B,cAAc,gBAAgB,CAAC;EAClH;CAED,MAAM,uBACJ,IAAI,gBAAgB,KAAK,eAAe,0DAChC,gBAAgB;CAE1B,MAAM,gBAAgB,YAAY;EAChC,IAAI,YAAY;AAChB,MAAI;AACF,SAAM,OAAO,KAAK,WAAW,mBAAmB,KAAK,MAAM,GAAG;GAE9D,MAAM,EAAE,SAAS,MAAM,OAAO,MAC5B,gCAAgC,gBAAgB,KAAK,eAAe,GACrE;AACD,OAAI,CAAC,mBAAmB,KAAK,CAAE,OAAM,IAAI,MAAM,gBAAgB,CAAC;AAChE,SAAM,OAAO,KAAK,SAAS;AAC3B,eAAY;WACL,KAAK;AACZ,OAAI,CAAC,UAAW,OAAM,OAAO,KAAK,WAAW;AAC7C,SAAM,eAAe,SAAS,IAAI,YAAY,gBAAgB,GAC1D,MACA,IAAI,MAAM,gBAAgB,EAAE,EAAE,OAAO,KAAK,CAAC;;;CAInD,MAAM,gBAAgB,YAAY;EAChC,MAAM,EAAE,MAAM,gBAAgB,MAAM,OAAO,MACzC;;iCAE2B,gBAAgB,qBAAqB,eAAe;mBAEhF;AAED,MAAI,YAAY,IAAI,OAAO;AACzB,OAAI;IACF,MAAM,EAAE,MAAM,YAAY,MAAM,OAAO,MACrC,gCAAgC,gBAAgB,KAAK,eAAe,GACrE;AACD,QAAI,mBAAmB,QAAQ,CAAE,QAAO;WAClC;AAIR,SAAM,IAAI,MAAM,gBAAgB,CAAC;;EAGnC,MAAM,EAAE,MAAM,iBAAiB,MAAM,OAAO,MAC1C;uDACiD,gBAAgB;mBAElE;AACD,MAAI,aAAa,IAAI,MACnB,OAAM,IAAI,MACR,WAAW,gBAAgB,0DACjB,gBAAgB,4CAC3B;EAGH,MAAM,EAAE,MAAM,WAAW,MAAM,OAAO,MACpC;;;;;;;;;;;;yBAaD;AACD,MAAI,OAAO,IAAI,aAAa;AAC1B,SAAM,eAAe;AACrB,UAAO;;AAGT,SAAO;;AAGT,KAAI,CAAC,QAAQ,WAAW,CAAE,MAAM,eAAe,EAAG;EAChD,MAAM,MAAM,MAAM,WAAW,QAAQ;AAGrC,MAFuB,CAAC,QAAQ,KAEZ;GAClB,IAAI,YAAY;AAChB,OAAI;AACF,UAAM,OAAO,KAAK,WAAW,IAAI,KAAK,mBAAmB,KAAK,MAAM,GAAG;IAEvE,MAAM,EAAE,MAAM,WAAW,MAAM,OAAO,MACpC,gCAAgC,gBAAgB,KAAK,eAAe,GACrE;AACD,QAAI,CAAC,mBAAmB,OAAO,CAAE,OAAM,IAAI,MAAM,gBAAgB,CAAC;AAClE,UAAM,OAAO,KAAK,SAAS;AAC3B,gBAAY;YACL,KAAK;AACZ,QAAI,CAAC,UAAW,OAAM,OAAO,KAAK,WAAW;AAC7C,QAAI,eAAe,SAAS,IAAI,YAAY,gBAAgB,CAAE,OAAM;AACpE,UAAM,IAAI,MACR,+EACA,EAAE,OAAO,KAAK,CACf;;SAEE;AACL,OAAI;AACF,UAAM,OAAO,KAAK,IAAI;YACf,KAAK;AACZ,UAAM,IAAI,MACR,+EACA,EAAE,OAAO,KAAK,CACf;;AAEH,SAAM,eAAe;;;CAIzB,MAAM,UAAU,IAAIC,mBAAAA,SAAS,KAAK;CAElC,IAAI,eAA8B;CAClC,IAAI,cAAc;CAElB,MAAM,iBAAiB,YAAY;AACjC,MAAI,iBAAiB,KAAM,QAAO;EAClC,MAAM,EAAE,SAAS,MAAM,OAAO,MAC5B;;eAES,oBACV;AACD,iBAAe,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,EAAE,UAAU,CAAC,KAAK,KAAK,GAAG;AAC3E,SAAO;;CAGT,MAAM,aAA2B,YAAY;AAC3C,QAAM,OAAO,KAAK,0BAA0B,gBAAgB,WAAW;AAEvE,MAAI;AACF,SAAM,OAAO,KAAK,QAAQ;AAC1B,SAAM,OAAO,KAAK,kBAAkB,gBAAgB,GAAG;GAEvD,MAAM,EAAE,MAAM,WAAW,MAAM,OAAO,MAKpC;;;iBAGS,oBACV;AAED,SAAM,OAAO,KACX,iBAAiB,gBAAgB,oEAClC;AAED,QAAK,MAAM,CAAC,GAAG,EAAE,YAAY,WAAW,gBAAgB,OAAO,SAAS,EAAE;IACxE,MAAM,WAAW,SAAS;AAC1B,UAAM,OAAO,KACX,iBAAiB,gBAAgB,KAAK,SAAS,qBAAqB,YACrE;AACD,UAAM,OAAO,KACX,gBAAgB,gBAAgB,qBAAqB,cAAc,SAAS,CAAC,IAAI,cAAc,WAAW,CAAC,IAAI,cAAc,UAAU,CAAC,GACzI;;GAGH,MAAM,EAAE,MAAM,SAAS,MAAM,OAAO,MAClC;;;8BAGsB,gBAAgB;qCAEvC;AAED,SAAM,OAAO,KAAK,iBAAiB,gBAAgB,yCAAyC;AAC5F,QAAK,MAAM,EAAE,MAAM,WAAW,KAC5B,OAAM,OAAO,KACX,gBAAgB,gBAAgB,wBAAwB,KAAK,IAAI,MAAM,GACxE;AAGH,SAAM,OAAO,KAAK,SAAS;WACpB,KAAK;AACZ,SAAM,OAAO,KAAK,WAAW;AAC7B,SAAM,OAAO,KAAK,0BAA0B,gBAAgB,WAAW;AACvE,SAAM;;AAGR,gBAAc;;CAGhB,MAAM,gBAAiC,YAAY;AACjD,gBAAc;AACd,QAAM,OAAO,KAAK,0BAA0B,gBAAgB,WAAW;;CAGzE,MAAM,UAAU,YAAY;AAC1B,iBAAe;EACf,MAAM,SAAS,MAAM,gBAAgB;AAErC,MAAI,eAAe,OACjB,KAAI;AACF,SAAM,OAAO,KAAK,yCAAyC;AAC3D,SAAM,OAAO,KAAK,kBAAkB,OAAO,2BAA2B;GAEtE,MAAM,EAAE,MAAM,mBAAmB,MAAM,OAAO,MAI5C;mBACS,gBAAgB,YAC1B;AAED,QAAK,MAAM,EAAE,WAAW,eAAe,eACrC,OAAM,OAAO,KACX,eAAe,UAAU,kBAAkB,gBAAgB,KAAK,UAAU,GAC3E;GAGH,MAAM,EAAE,MAAM,SAAS,MAAM,OAAO,MAClC,kEAAkE,gBAAgB,eACnF;AAED,QAAK,MAAM,EAAE,MAAM,WAAW,KAC5B,OAAM,OAAO,KAAK,iBAAiB,KAAK,IAAI,MAAM,GAAG;YAE/C;AACR,SAAM,OAAO,KAAK,yCAAyC;;WAEpD,OACT,KAAI;AACF,SAAM,OAAO,KAAK,yCAAyC;AAC3D,SAAM,OAAO,KAAK,kBAAkB,OAAO,2BAA2B;YAC9D;AACR,SAAM,OAAO,KAAK,yCAAyC;;AAI/D,QAAM,OAAO,KAAK,YAAY;AAC9B,QAAM,OAAO,KAAK,iBAAiB;;AAGrC,QAAO;EAAE;EAAS;EAAQ;EAAS;EAAY;EAAe,OAAO;EAAW"}
package/dist/index.d.cts CHANGED
@@ -26,7 +26,15 @@ type ResetSnapshotFn = () => Promise<void>;
26
26
  interface PgliteAdapter {
27
27
  /** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */
28
28
  adapter: PrismaPg;
29
- /** The underlying PGlite instance for direct SQL, snapshots, or extensions. */
29
+ /**
30
+ * The underlying PGlite instance for direct SQL, snapshots, or extensions.
31
+ *
32
+ * @remarks
33
+ * Direct `pglite.exec()` / `pglite.query()` calls bypass the pool's
34
+ * {@link SessionLock}. Avoid mixing direct calls with Prisma operations
35
+ * inside a transaction — use them only for setup, teardown, or utilities
36
+ * that run outside active Prisma transactions.
37
+ */
30
38
  pglite: _$_electric_sql_pglite0.PGlite;
31
39
  /** Clear all user tables. Call in `beforeEach` for per-test isolation. */
32
40
  resetDb: ResetDbFn;
@@ -67,6 +75,9 @@ interface PoolResult {
67
75
  /**
68
76
  * Creates a pg.Pool where every connection is an in-process PGlite bridge.
69
77
  *
78
+ * Most users should prefer {@link createPgliteAdapter}, which wraps this
79
+ * function and also handles schema application and reset/snapshot lifecycle.
80
+ *
70
81
  * ```typescript
71
82
  * import { createPool } from 'prisma-pglite-bridge';
72
83
  * import { PrismaPg } from '@prisma/adapter-pg';
@@ -76,12 +87,24 @@ interface PoolResult {
76
87
  * const adapter = new PrismaPg(pool);
77
88
  * const prisma = new PrismaClient({ adapter });
78
89
  * ```
90
+ *
91
+ * @see {@link createPgliteAdapter} for the higher-level API with schema management.
79
92
  */
80
93
  declare const createPool: (options?: CreatePoolOptions) => Promise<PoolResult>;
81
94
  //#endregion
82
95
  //#region src/session-lock.d.ts
83
96
  /** Opaque bridge identity token */
84
97
  type BridgeId = symbol;
98
+ /**
99
+ * Coordinates PGlite access across concurrent pool connections.
100
+ *
101
+ * @remarks
102
+ * PGlite runs PostgreSQL in single-user mode — one session shared by all
103
+ * bridges. The session lock tracks which bridge owns the session during
104
+ * transactions, preventing interleaving. Used internally by {@link PGliteBridge}
105
+ * and created automatically by {@link createPool}. Only instantiate directly
106
+ * if building a custom pool setup.
107
+ */
85
108
  declare class SessionLock {
86
109
  private owner;
87
110
  private waitQueue;
@@ -124,7 +147,6 @@ declare class PGliteBridge extends Duplex {
124
147
  private readonly bridgeId;
125
148
  /** Incoming bytes not yet compacted into buf */
126
149
  private pending;
127
- private pendingLen;
128
150
  /** Compacted input buffer for message framing */
129
151
  private buf;
130
152
  private phase;
@@ -134,7 +156,6 @@ declare class PGliteBridge extends Duplex {
134
156
  private drainQueue;
135
157
  /** Buffered EQP messages awaiting Sync */
136
158
  private pipeline;
137
- private pipelineLen;
138
159
  constructor(pglite: PGlite, sessionLock?: SessionLock);
139
160
  connect(): this;
140
161
  setKeepAlive(): this;