prisma-pglite-bridge 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -535,6 +535,9 @@ const createPool = async (options = {}) => {
535
535
  * ```
536
536
  */
537
537
  const SNAPSHOT_SCHEMA = "_pglite_snapshot";
538
+ const SENTINEL_SCHEMA = "_pglite_bridge";
539
+ const SENTINEL_TABLE = "__initialized";
540
+ const SENTINEL_MARKER = "prisma-pglite-bridge:init:v1";
538
541
  /**
539
542
  * Discover the migrations directory via Prisma's config API.
540
543
  * Uses the same resolution as `prisma migrate dev` — reads prisma.config.ts,
@@ -597,18 +600,92 @@ const resolveSQL = async (options) => {
597
600
  * function for clearing tables between tests.
598
601
  */
599
602
  const createPgliteAdapter = async (options = {}) => {
600
- const sql = await resolveSQL(options);
601
603
  const { pool, pglite, close: poolClose } = await createPool({
602
604
  dataDir: options.dataDir,
603
605
  extensions: options.extensions,
604
606
  max: options.max
605
607
  });
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 });
608
+ const sentinelStatements = [
609
+ `CREATE SCHEMA IF NOT EXISTS "${SENTINEL_SCHEMA}"`,
610
+ `CREATE TABLE IF NOT EXISTS "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" (marker text PRIMARY KEY, version int NOT NULL)`,
611
+ `INSERT INTO "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" (marker, version) VALUES ('${SENTINEL_MARKER}', 1) ON CONFLICT (marker) DO NOTHING`
612
+ ];
613
+ const collisionError = () => `"${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" exists but is not owned by prisma-pglite-bridge. The "${SENTINEL_SCHEMA}" schema is reserved for library metadata.`;
614
+ const writeSentinel = async () => {
615
+ try {
616
+ await pglite.exec(`BEGIN;\n${sentinelStatements.join(";\n")}`);
617
+ } catch (err) {
618
+ await pglite.exec("ROLLBACK");
619
+ throw new Error(collisionError(), { cause: err });
620
+ }
621
+ const { rows } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
622
+ if (rows.length !== 1 || rows[0]?.marker !== SENTINEL_MARKER || rows[0]?.version !== 1) {
623
+ await pglite.exec("ROLLBACK");
624
+ throw new Error(collisionError());
625
+ }
626
+ await pglite.exec("COMMIT");
627
+ };
628
+ const isInitialized = async () => {
629
+ const { rows: tableExists } = await pglite.query(`SELECT EXISTS (
630
+ SELECT 1 FROM pg_tables
631
+ WHERE schemaname = '${SENTINEL_SCHEMA}' AND tablename = '${SENTINEL_TABLE}'
632
+ ) AS found`);
633
+ if (tableExists[0]?.found) {
634
+ try {
635
+ const { rows: allRows } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
636
+ if (allRows.length === 1 && allRows[0]?.marker === SENTINEL_MARKER && allRows[0]?.version === 1) return true;
637
+ } catch {}
638
+ throw new Error(collisionError());
639
+ }
640
+ const { rows: schemaExists } = await pglite.query(`SELECT EXISTS (
641
+ SELECT 1 FROM pg_namespace WHERE nspname = '${SENTINEL_SCHEMA}'
642
+ ) AS found`);
643
+ 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.`);
644
+ const { rows: legacy } = await pglite.query(`SELECT EXISTS (
645
+ SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
646
+ WHERE n.nspname = 'public'
647
+ UNION ALL
648
+ SELECT 1 FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid
649
+ WHERE n.nspname = 'public' AND t.typtype NOT IN ('b', 'p')
650
+ UNION ALL
651
+ SELECT 1 FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
652
+ WHERE n.nspname = 'public'
653
+ UNION ALL
654
+ SELECT 1 FROM pg_namespace
655
+ WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'public')
656
+ ) AS initialized`);
657
+ if (legacy[0]?.initialized) {
658
+ await writeSentinel();
659
+ return true;
660
+ }
661
+ return false;
662
+ };
663
+ if (!options.dataDir || !await isInitialized()) {
664
+ const sql = await resolveSQL(options);
665
+ if (!options.sql) {
666
+ try {
667
+ await pglite.exec(`BEGIN;\n${sql};\n${sentinelStatements.join(";\n")}`);
668
+ } catch (err) {
669
+ await pglite.exec("ROLLBACK");
670
+ throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
671
+ }
672
+ const { rows: verify } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
673
+ if (verify.length !== 1 || verify[0]?.marker !== SENTINEL_MARKER || verify[0]?.version !== 1) {
674
+ await pglite.exec("ROLLBACK");
675
+ throw new Error(collisionError());
676
+ }
677
+ await pglite.exec("COMMIT");
678
+ } else {
679
+ try {
680
+ await pglite.exec(sql);
681
+ } catch (err) {
682
+ throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
683
+ }
684
+ await writeSentinel();
685
+ }
610
686
  }
611
687
  const adapter = new _prisma_adapter_pg.PrismaPg(pool);
688
+ const escapeLiteral = (s) => `'${s.replace(/'/g, "''")}'`;
612
689
  let cachedTables = null;
613
690
  let hasSnapshot = false;
614
691
  const discoverTables = async () => {
@@ -617,6 +694,7 @@ const createPgliteAdapter = async (options = {}) => {
617
694
  FROM pg_tables
618
695
  WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
619
696
  AND schemaname != '${SNAPSHOT_SCHEMA}'
697
+ AND schemaname != '${SENTINEL_SCHEMA}'
620
698
  AND tablename NOT LIKE '_prisma%'`);
621
699
  cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(", ") : "";
622
700
  return cachedTables;
@@ -624,12 +702,23 @@ const createPgliteAdapter = async (options = {}) => {
624
702
  const snapshotDb = async () => {
625
703
  await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
626
704
  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'
705
+ const { rows: tables } = await pglite.query(`SELECT quote_ident(schemaname) AS schemaname, quote_ident(tablename) AS tablename
706
+ FROM pg_tables
707
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
708
+ AND schemaname != '${SNAPSHOT_SCHEMA}'
709
+ AND schemaname != '${SENTINEL_SCHEMA}'
629
710
  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`);
711
+ await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__tables (snap_name text, source_schema text, source_table text)`);
712
+ for (const [i, { schemaname, tablename }] of tables.entries()) {
713
+ const snapName = `_snap_${i}`;
714
+ await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}"."${snapName}" AS SELECT * FROM ${schemaname}.${tablename}`);
715
+ await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`);
716
+ }
717
+ const { rows: seqs } = await pglite.query(`SELECT quote_literal(schemaname || '.' || sequencename) AS name, last_value::text AS value
718
+ FROM pg_sequences
719
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
720
+ AND schemaname != '${SNAPSHOT_SCHEMA}'
721
+ AND last_value IS NOT NULL`);
633
722
  await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__sequences (name text, value bigint)`);
634
723
  for (const { name, value } of seqs) await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__sequences VALUES (${name}, ${value})`);
635
724
  hasSnapshot = true;
@@ -639,15 +728,14 @@ const createPgliteAdapter = async (options = {}) => {
639
728
  await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
640
729
  };
641
730
  const resetDb = async () => {
731
+ cachedTables = null;
642
732
  const tables = await discoverTables();
643
733
  if (hasSnapshot && tables) {
644
734
  try {
645
735
  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}`);
736
+ await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);
737
+ const { rows: snapshotTables } = await pglite.query(`SELECT snap_name, source_schema, source_table FROM "${SNAPSHOT_SCHEMA}".__tables`);
738
+ for (const { snap_name, source_schema, source_table } of snapshotTables) await pglite.exec(`INSERT INTO ${source_schema}.${source_table} SELECT * FROM "${SNAPSHOT_SCHEMA}"."${snap_name}"`);
651
739
  } finally {
652
740
  await pglite.exec("SET session_replication_role = DEFAULT");
653
741
  }
@@ -655,7 +743,7 @@ const createPgliteAdapter = async (options = {}) => {
655
743
  for (const { name, value } of seqs) await pglite.exec(`SELECT setval(${name}, ${value})`);
656
744
  } else if (tables) try {
657
745
  await pglite.exec("SET session_replication_role = replica");
658
- await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
746
+ await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);
659
747
  } finally {
660
748
  await pglite.exec("SET session_replication_role = DEFAULT");
661
749
  }
@@ -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\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';\nconst SENTINEL_SCHEMA = '_pglite_bridge';\nconst SENTINEL_TABLE = '__initialized';\nconst SENTINEL_MARKER = 'prisma-pglite-bridge:init:v1';\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 {\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 ('${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 try {\n await pglite.exec(`BEGIN;\\n${sentinelStatements.join(';\\n')}`);\n } catch (err) {\n await pglite.exec('ROLLBACK');\n throw new Error(collisionError(), { cause: err });\n }\n\n const { rows } = await pglite.query<{ marker: string; version: number }>(\n `SELECT marker, version FROM \"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\"`,\n );\n if (rows.length !== 1 || rows[0]?.marker !== SENTINEL_MARKER || rows[0]?.version !== 1) {\n await pglite.exec('ROLLBACK');\n throw new Error(collisionError());\n }\n await pglite.exec('COMMIT');\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 (\n allRows.length === 1 &&\n allRows[0]?.marker === SENTINEL_MARKER &&\n allRows[0]?.version === 1\n )\n 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 try {\n await pglite.exec(`BEGIN;\\n${sql};\\n${sentinelStatements.join(';\\n')}`);\n } catch (err) {\n await pglite.exec('ROLLBACK');\n throw new Error(\n 'Failed to apply schema SQL to PGlite. Check your schema or migration files.',\n { cause: err },\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 (\n verify.length !== 1 ||\n verify[0]?.marker !== SENTINEL_MARKER ||\n verify[0]?.version !== 1\n ) {\n await pglite.exec('ROLLBACK');\n throw new Error(collisionError());\n }\n await pglite.exec('COMMIT');\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 const escapeLiteral = (s: string) => `'${s.replace(/'/g, \"''\")}'`;\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 schemaname != '${SENTINEL_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<{\n schemaname: string;\n tablename: string;\n }>(\n `SELECT quote_ident(schemaname) AS schemaname, quote_ident(tablename) AS tablename\n FROM pg_tables\n 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 );\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 }] of tables.entries()) {\n const snapName = `_snap_${i}`;\n await pglite.exec(\n `CREATE TABLE \"${SNAPSHOT_SCHEMA}\".\"${snapName}\" AS SELECT * FROM ${schemaname}.${tablename}`,\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(`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 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 source_schema: string;\n source_table: string;\n }>(`SELECT snap_name, source_schema, source_table FROM \"${SNAPSHOT_SCHEMA}\".__tables`);\n\n for (const { snap_name, source_schema, source_table } of snapshotTables) {\n await pglite.exec(\n `INSERT INTO ${source_schema}.${source_table} SELECT * FROM \"${SNAPSHOT_SCHEMA}\".\"${snap_name}\"`,\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} 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":"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"}
package/dist/index.mjs CHANGED
@@ -511,6 +511,9 @@ const createPool = async (options = {}) => {
511
511
  * ```
512
512
  */
513
513
  const SNAPSHOT_SCHEMA = "_pglite_snapshot";
514
+ const SENTINEL_SCHEMA = "_pglite_bridge";
515
+ const SENTINEL_TABLE = "__initialized";
516
+ const SENTINEL_MARKER = "prisma-pglite-bridge:init:v1";
514
517
  /**
515
518
  * Discover the migrations directory via Prisma's config API.
516
519
  * Uses the same resolution as `prisma migrate dev` — reads prisma.config.ts,
@@ -573,18 +576,92 @@ const resolveSQL = async (options) => {
573
576
  * function for clearing tables between tests.
574
577
  */
575
578
  const createPgliteAdapter = async (options = {}) => {
576
- const sql = await resolveSQL(options);
577
579
  const { pool, pglite, close: poolClose } = await createPool({
578
580
  dataDir: options.dataDir,
579
581
  extensions: options.extensions,
580
582
  max: options.max
581
583
  });
582
- try {
583
- await pglite.exec(sql);
584
- } catch (err) {
585
- throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
584
+ const sentinelStatements = [
585
+ `CREATE SCHEMA IF NOT EXISTS "${SENTINEL_SCHEMA}"`,
586
+ `CREATE TABLE IF NOT EXISTS "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" (marker text PRIMARY KEY, version int NOT NULL)`,
587
+ `INSERT INTO "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" (marker, version) VALUES ('${SENTINEL_MARKER}', 1) ON CONFLICT (marker) DO NOTHING`
588
+ ];
589
+ const collisionError = () => `"${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}" exists but is not owned by prisma-pglite-bridge. The "${SENTINEL_SCHEMA}" schema is reserved for library metadata.`;
590
+ const writeSentinel = async () => {
591
+ try {
592
+ await pglite.exec(`BEGIN;\n${sentinelStatements.join(";\n")}`);
593
+ } catch (err) {
594
+ await pglite.exec("ROLLBACK");
595
+ throw new Error(collisionError(), { cause: err });
596
+ }
597
+ const { rows } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
598
+ if (rows.length !== 1 || rows[0]?.marker !== SENTINEL_MARKER || rows[0]?.version !== 1) {
599
+ await pglite.exec("ROLLBACK");
600
+ throw new Error(collisionError());
601
+ }
602
+ await pglite.exec("COMMIT");
603
+ };
604
+ const isInitialized = async () => {
605
+ const { rows: tableExists } = await pglite.query(`SELECT EXISTS (
606
+ SELECT 1 FROM pg_tables
607
+ WHERE schemaname = '${SENTINEL_SCHEMA}' AND tablename = '${SENTINEL_TABLE}'
608
+ ) AS found`);
609
+ if (tableExists[0]?.found) {
610
+ try {
611
+ const { rows: allRows } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
612
+ if (allRows.length === 1 && allRows[0]?.marker === SENTINEL_MARKER && allRows[0]?.version === 1) return true;
613
+ } catch {}
614
+ throw new Error(collisionError());
615
+ }
616
+ const { rows: schemaExists } = await pglite.query(`SELECT EXISTS (
617
+ SELECT 1 FROM pg_namespace WHERE nspname = '${SENTINEL_SCHEMA}'
618
+ ) AS found`);
619
+ 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.`);
620
+ const { rows: legacy } = await pglite.query(`SELECT EXISTS (
621
+ SELECT 1 FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
622
+ WHERE n.nspname = 'public'
623
+ UNION ALL
624
+ SELECT 1 FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid
625
+ WHERE n.nspname = 'public' AND t.typtype NOT IN ('b', 'p')
626
+ UNION ALL
627
+ SELECT 1 FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
628
+ WHERE n.nspname = 'public'
629
+ UNION ALL
630
+ SELECT 1 FROM pg_namespace
631
+ WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'public')
632
+ ) AS initialized`);
633
+ if (legacy[0]?.initialized) {
634
+ await writeSentinel();
635
+ return true;
636
+ }
637
+ return false;
638
+ };
639
+ if (!options.dataDir || !await isInitialized()) {
640
+ const sql = await resolveSQL(options);
641
+ if (!options.sql) {
642
+ try {
643
+ await pglite.exec(`BEGIN;\n${sql};\n${sentinelStatements.join(";\n")}`);
644
+ } catch (err) {
645
+ await pglite.exec("ROLLBACK");
646
+ throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
647
+ }
648
+ const { rows: verify } = await pglite.query(`SELECT marker, version FROM "${SENTINEL_SCHEMA}"."${SENTINEL_TABLE}"`);
649
+ if (verify.length !== 1 || verify[0]?.marker !== SENTINEL_MARKER || verify[0]?.version !== 1) {
650
+ await pglite.exec("ROLLBACK");
651
+ throw new Error(collisionError());
652
+ }
653
+ await pglite.exec("COMMIT");
654
+ } else {
655
+ try {
656
+ await pglite.exec(sql);
657
+ } catch (err) {
658
+ throw new Error("Failed to apply schema SQL to PGlite. Check your schema or migration files.", { cause: err });
659
+ }
660
+ await writeSentinel();
661
+ }
586
662
  }
587
663
  const adapter = new PrismaPg(pool);
664
+ const escapeLiteral = (s) => `'${s.replace(/'/g, "''")}'`;
588
665
  let cachedTables = null;
589
666
  let hasSnapshot = false;
590
667
  const discoverTables = async () => {
@@ -593,6 +670,7 @@ const createPgliteAdapter = async (options = {}) => {
593
670
  FROM pg_tables
594
671
  WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
595
672
  AND schemaname != '${SNAPSHOT_SCHEMA}'
673
+ AND schemaname != '${SENTINEL_SCHEMA}'
596
674
  AND tablename NOT LIKE '_prisma%'`);
597
675
  cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(", ") : "";
598
676
  return cachedTables;
@@ -600,12 +678,23 @@ const createPgliteAdapter = async (options = {}) => {
600
678
  const snapshotDb = async () => {
601
679
  await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
602
680
  await pglite.exec(`CREATE SCHEMA "${SNAPSHOT_SCHEMA}"`);
603
- const { rows: tables } = await pglite.query(`SELECT quote_ident(tablename) AS tablename FROM pg_tables
604
- WHERE schemaname = 'public'
681
+ const { rows: tables } = await pglite.query(`SELECT quote_ident(schemaname) AS schemaname, quote_ident(tablename) AS tablename
682
+ FROM pg_tables
683
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
684
+ AND schemaname != '${SNAPSHOT_SCHEMA}'
685
+ AND schemaname != '${SENTINEL_SCHEMA}'
605
686
  AND tablename NOT LIKE '_prisma%'`);
606
- for (const { tablename } of tables) await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".${tablename} AS SELECT * FROM public.${tablename}`);
607
- const { rows: seqs } = await pglite.query(`SELECT quote_literal(sequencename) AS name, last_value::text AS value
608
- FROM pg_sequences WHERE schemaname = 'public' AND last_value IS NOT NULL`);
687
+ await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__tables (snap_name text, source_schema text, source_table text)`);
688
+ for (const [i, { schemaname, tablename }] of tables.entries()) {
689
+ const snapName = `_snap_${i}`;
690
+ await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}"."${snapName}" AS SELECT * FROM ${schemaname}.${tablename}`);
691
+ await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`);
692
+ }
693
+ const { rows: seqs } = await pglite.query(`SELECT quote_literal(schemaname || '.' || sequencename) AS name, last_value::text AS value
694
+ FROM pg_sequences
695
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
696
+ AND schemaname != '${SNAPSHOT_SCHEMA}'
697
+ AND last_value IS NOT NULL`);
609
698
  await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__sequences (name text, value bigint)`);
610
699
  for (const { name, value } of seqs) await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__sequences VALUES (${name}, ${value})`);
611
700
  hasSnapshot = true;
@@ -615,15 +704,14 @@ const createPgliteAdapter = async (options = {}) => {
615
704
  await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
616
705
  };
617
706
  const resetDb = async () => {
707
+ cachedTables = null;
618
708
  const tables = await discoverTables();
619
709
  if (hasSnapshot && tables) {
620
710
  try {
621
711
  await pglite.exec("SET session_replication_role = replica");
622
- await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
623
- const { rows: snapshotTables } = await pglite.query(`SELECT quote_ident(tablename) AS tablename FROM pg_tables
624
- WHERE schemaname = '${SNAPSHOT_SCHEMA}'
625
- AND tablename != '__sequences'`);
626
- for (const { tablename } of snapshotTables) await pglite.exec(`INSERT INTO public.${tablename} SELECT * FROM "${SNAPSHOT_SCHEMA}".${tablename}`);
712
+ await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);
713
+ const { rows: snapshotTables } = await pglite.query(`SELECT snap_name, source_schema, source_table FROM "${SNAPSHOT_SCHEMA}".__tables`);
714
+ for (const { snap_name, source_schema, source_table } of snapshotTables) await pglite.exec(`INSERT INTO ${source_schema}.${source_table} SELECT * FROM "${SNAPSHOT_SCHEMA}"."${snap_name}"`);
627
715
  } finally {
628
716
  await pglite.exec("SET session_replication_role = DEFAULT");
629
717
  }
@@ -631,7 +719,7 @@ const createPgliteAdapter = async (options = {}) => {
631
719
  for (const { name, value } of seqs) await pglite.exec(`SELECT setval(${name}, ${value})`);
632
720
  } else if (tables) try {
633
721
  await pglite.exec("SET session_replication_role = replica");
634
- await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
722
+ await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);
635
723
  } finally {
636
724
  await pglite.exec("SET session_replication_role = DEFAULT");
637
725
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"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.mjs","names":[],"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';\nconst SENTINEL_SCHEMA = '_pglite_bridge';\nconst SENTINEL_TABLE = '__initialized';\nconst SENTINEL_MARKER = 'prisma-pglite-bridge:init:v1';\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 {\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 ('${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 try {\n await pglite.exec(`BEGIN;\\n${sentinelStatements.join(';\\n')}`);\n } catch (err) {\n await pglite.exec('ROLLBACK');\n throw new Error(collisionError(), { cause: err });\n }\n\n const { rows } = await pglite.query<{ marker: string; version: number }>(\n `SELECT marker, version FROM \"${SENTINEL_SCHEMA}\".\"${SENTINEL_TABLE}\"`,\n );\n if (rows.length !== 1 || rows[0]?.marker !== SENTINEL_MARKER || rows[0]?.version !== 1) {\n await pglite.exec('ROLLBACK');\n throw new Error(collisionError());\n }\n await pglite.exec('COMMIT');\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 (\n allRows.length === 1 &&\n allRows[0]?.marker === SENTINEL_MARKER &&\n allRows[0]?.version === 1\n )\n 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 try {\n await pglite.exec(`BEGIN;\\n${sql};\\n${sentinelStatements.join(';\\n')}`);\n } catch (err) {\n await pglite.exec('ROLLBACK');\n throw new Error(\n 'Failed to apply schema SQL to PGlite. Check your schema or migration files.',\n { cause: err },\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 (\n verify.length !== 1 ||\n verify[0]?.marker !== SENTINEL_MARKER ||\n verify[0]?.version !== 1\n ) {\n await pglite.exec('ROLLBACK');\n throw new Error(collisionError());\n }\n await pglite.exec('COMMIT');\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 const escapeLiteral = (s: string) => `'${s.replace(/'/g, \"''\")}'`;\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 schemaname != '${SENTINEL_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<{\n schemaname: string;\n tablename: string;\n }>(\n `SELECT quote_ident(schemaname) AS schemaname, quote_ident(tablename) AS tablename\n FROM pg_tables\n 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 );\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 }] of tables.entries()) {\n const snapName = `_snap_${i}`;\n await pglite.exec(\n `CREATE TABLE \"${SNAPSHOT_SCHEMA}\".\"${snapName}\" AS SELECT * FROM ${schemaname}.${tablename}`,\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(`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 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 source_schema: string;\n source_table: string;\n }>(`SELECT snap_name, source_schema, source_table FROM \"${SNAPSHOT_SCHEMA}\".__tables`);\n\n for (const { snap_name, source_schema, source_table } of snapshotTables) {\n await pglite.exec(\n `INSERT INTO ${source_schema}.${source_table} SELECT * FROM \"${SNAPSHOT_SCHEMA}\".\"${snap_name}\"`,\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} 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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prisma-pglite-bridge",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "In-process PGlite bridge for Prisma — zero-TCP, zero-Docker PostgreSQL",
5
5
  "keywords": [
6
6
  "prisma",