prisma-pglite-bridge 0.1.0 → 0.3.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 +67 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +67 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -466,6 +466,7 @@ var createPool = async (options = {}) => {
|
|
|
466
466
|
var import_node_fs = require("fs");
|
|
467
467
|
var import_node_path = require("path");
|
|
468
468
|
var import_adapter_pg = require("@prisma/adapter-pg");
|
|
469
|
+
var SNAPSHOT_SCHEMA = "_pglite_snapshot";
|
|
469
470
|
var discoverMigrationsPath = async (configRoot) => {
|
|
470
471
|
try {
|
|
471
472
|
const { loadConfigFromFile } = await import("@prisma/config");
|
|
@@ -529,20 +530,75 @@ var createPgliteAdapter = async (options = {}) => {
|
|
|
529
530
|
}
|
|
530
531
|
const adapter = new import_adapter_pg.PrismaPg(pool);
|
|
531
532
|
let cachedTables = null;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
533
|
+
let hasSnapshot = false;
|
|
534
|
+
const discoverTables = async () => {
|
|
535
|
+
if (cachedTables !== null) return cachedTables;
|
|
536
|
+
const { rows } = await pglite.query(
|
|
537
|
+
`SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified
|
|
538
|
+
FROM pg_tables
|
|
539
|
+
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
|
|
540
|
+
AND schemaname != '${SNAPSHOT_SCHEMA}'
|
|
541
|
+
AND tablename NOT LIKE '_prisma%'`
|
|
542
|
+
);
|
|
543
|
+
cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(", ") : "";
|
|
544
|
+
return cachedTables;
|
|
545
|
+
};
|
|
546
|
+
const snapshotDb = async () => {
|
|
547
|
+
await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
|
|
548
|
+
await pglite.exec(`CREATE SCHEMA "${SNAPSHOT_SCHEMA}"`);
|
|
549
|
+
const { rows: tables } = await pglite.query(
|
|
550
|
+
`SELECT quote_ident(tablename) AS tablename FROM pg_tables
|
|
551
|
+
WHERE schemaname = 'public'
|
|
552
|
+
AND tablename NOT LIKE '_prisma%'`
|
|
553
|
+
);
|
|
554
|
+
for (const { tablename } of tables) {
|
|
555
|
+
await pglite.exec(
|
|
556
|
+
`CREATE TABLE "${SNAPSHOT_SCHEMA}".${tablename} AS SELECT * FROM public.${tablename}`
|
|
539
557
|
);
|
|
540
|
-
cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(", ") : "";
|
|
541
558
|
}
|
|
542
|
-
|
|
559
|
+
const { rows: seqs } = await pglite.query(
|
|
560
|
+
`SELECT quote_literal(sequencename) AS name, last_value::text AS value
|
|
561
|
+
FROM pg_sequences WHERE schemaname = 'public' AND last_value IS NOT NULL`
|
|
562
|
+
);
|
|
563
|
+
await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__sequences (name text, value bigint)`);
|
|
564
|
+
for (const { name, value } of seqs) {
|
|
565
|
+
await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__sequences VALUES (${name}, ${value})`);
|
|
566
|
+
}
|
|
567
|
+
hasSnapshot = true;
|
|
568
|
+
};
|
|
569
|
+
const resetSnapshot = async () => {
|
|
570
|
+
hasSnapshot = false;
|
|
571
|
+
await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
|
|
572
|
+
};
|
|
573
|
+
const resetDb = async () => {
|
|
574
|
+
const tables = await discoverTables();
|
|
575
|
+
if (hasSnapshot && tables) {
|
|
576
|
+
try {
|
|
577
|
+
await pglite.exec("SET session_replication_role = replica");
|
|
578
|
+
await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
|
|
579
|
+
const { rows: snapshotTables } = await pglite.query(
|
|
580
|
+
`SELECT quote_ident(tablename) AS tablename FROM pg_tables
|
|
581
|
+
WHERE schemaname = '${SNAPSHOT_SCHEMA}'
|
|
582
|
+
AND tablename != '__sequences'`
|
|
583
|
+
);
|
|
584
|
+
for (const { tablename } of snapshotTables) {
|
|
585
|
+
await pglite.exec(
|
|
586
|
+
`INSERT INTO public.${tablename} SELECT * FROM "${SNAPSHOT_SCHEMA}".${tablename}`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
} finally {
|
|
590
|
+
await pglite.exec("SET session_replication_role = DEFAULT");
|
|
591
|
+
}
|
|
592
|
+
const { rows: seqs } = await pglite.query(
|
|
593
|
+
`SELECT quote_literal(name) AS name, value::text AS value FROM "${SNAPSHOT_SCHEMA}".__sequences`
|
|
594
|
+
);
|
|
595
|
+
for (const { name, value } of seqs) {
|
|
596
|
+
await pglite.exec(`SELECT setval(${name}, ${value})`);
|
|
597
|
+
}
|
|
598
|
+
} else if (tables) {
|
|
543
599
|
try {
|
|
544
600
|
await pglite.exec("SET session_replication_role = replica");
|
|
545
|
-
await pglite.exec(`TRUNCATE TABLE ${
|
|
601
|
+
await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
|
|
546
602
|
} finally {
|
|
547
603
|
await pglite.exec("SET session_replication_role = DEFAULT");
|
|
548
604
|
}
|
|
@@ -550,7 +606,7 @@ var createPgliteAdapter = async (options = {}) => {
|
|
|
550
606
|
await pglite.exec("RESET ALL");
|
|
551
607
|
await pglite.exec("DEALLOCATE ALL");
|
|
552
608
|
};
|
|
553
|
-
return { adapter, pglite, resetDb, close: poolClose };
|
|
609
|
+
return { adapter, pglite, resetDb, snapshotDb, resetSnapshot, close: poolClose };
|
|
554
610
|
};
|
|
555
611
|
// Annotate the CommonJS export names for ESM import in node:
|
|
556
612
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/pglite-bridge.ts","../src/session-lock.ts","../src/create-pool.ts","../src/create-pglite-adapter.ts"],"sourcesContent":["/**\n * prisma-pglite-bridge — in-process PGlite bridge for Prisma.\n *\n * @example\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 *\n * @packageDocumentation\n */\nexport { PGliteBridge } from './pglite-bridge.ts';\nexport { createPool } from './create-pool.ts';\nexport type { CreatePoolOptions, PoolResult } from './create-pool.ts';\nexport { createPgliteAdapter } from './create-pglite-adapter.ts';\nexport type {\n CreatePgliteAdapterOptions,\n PgliteAdapter,\n ResetDbFn,\n} from './create-pglite-adapter.ts';\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 type SessionLock,\n createBridgeId,\n extractRfqStatus,\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 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 * 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 * 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, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { createPool } from './create-pool.ts';\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 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 /** 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 (~0ms)\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\n const resetDb = async () => {\n if (cachedTables === null) {\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 tablename NOT LIKE '_prisma%'`,\n );\n cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(', ') : '';\n }\n\n if (cachedTables) {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await pglite.exec(`TRUNCATE TABLE ${cachedTables} 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, close: poolClose };\n};\n"],"mappings":"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","names":["cleaned","pg"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/pglite-bridge.ts","../src/session-lock.ts","../src/create-pool.ts","../src/create-pglite-adapter.ts"],"sourcesContent":["/**\n * prisma-pglite-bridge — in-process PGlite bridge for Prisma.\n *\n * @example\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 *\n * @packageDocumentation\n */\nexport { PGliteBridge } from './pglite-bridge.ts';\nexport { createPool } from './create-pool.ts';\nexport type { CreatePoolOptions, PoolResult } from './create-pool.ts';\nexport { createPgliteAdapter } from './create-pglite-adapter.ts';\nexport type {\n CreatePgliteAdapterOptions,\n PgliteAdapter,\n ResetDbFn,\n SnapshotDbFn,\n ResetSnapshotFn,\n} from './create-pglite-adapter.ts';\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 type SessionLock,\n createBridgeId,\n extractRfqStatus,\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 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 * 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 * 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, readFileSync, readdirSync, 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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","names":["cleaned","pg"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -219,6 +219,8 @@ interface CreatePgliteAdapterOptions {
|
|
|
219
219
|
}
|
|
220
220
|
/** Clear all user tables. Call in `beforeEach` for per-test isolation. */
|
|
221
221
|
type ResetDbFn = () => Promise<void>;
|
|
222
|
+
type SnapshotDbFn = () => Promise<void>;
|
|
223
|
+
type ResetSnapshotFn = () => Promise<void>;
|
|
222
224
|
interface PgliteAdapter {
|
|
223
225
|
/** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */
|
|
224
226
|
adapter: PrismaPg;
|
|
@@ -226,6 +228,10 @@ interface PgliteAdapter {
|
|
|
226
228
|
pglite: _electric_sql_pglite.PGlite;
|
|
227
229
|
/** Clear all user tables. Call in `beforeEach` for per-test isolation. */
|
|
228
230
|
resetDb: ResetDbFn;
|
|
231
|
+
/** Snapshot current DB state. Subsequent `resetDb` calls restore to this snapshot. */
|
|
232
|
+
snapshotDb: SnapshotDbFn;
|
|
233
|
+
/** Discard the current snapshot. Subsequent `resetDb` calls truncate to empty. */
|
|
234
|
+
resetSnapshot: ResetSnapshotFn;
|
|
229
235
|
/** Shut down pool and PGlite. Not needed in tests (process exit handles it). */
|
|
230
236
|
close: () => Promise<void>;
|
|
231
237
|
}
|
|
@@ -237,4 +243,4 @@ interface PgliteAdapter {
|
|
|
237
243
|
*/
|
|
238
244
|
declare const createPgliteAdapter: (options?: CreatePgliteAdapterOptions) => Promise<PgliteAdapter>;
|
|
239
245
|
|
|
240
|
-
export { type CreatePgliteAdapterOptions, type CreatePoolOptions, PGliteBridge, type PgliteAdapter, type PoolResult, type ResetDbFn, createPgliteAdapter, createPool };
|
|
246
|
+
export { type CreatePgliteAdapterOptions, type CreatePoolOptions, PGliteBridge, type PgliteAdapter, type PoolResult, type ResetDbFn, type ResetSnapshotFn, type SnapshotDbFn, createPgliteAdapter, createPool };
|
package/dist/index.d.ts
CHANGED
|
@@ -219,6 +219,8 @@ interface CreatePgliteAdapterOptions {
|
|
|
219
219
|
}
|
|
220
220
|
/** Clear all user tables. Call in `beforeEach` for per-test isolation. */
|
|
221
221
|
type ResetDbFn = () => Promise<void>;
|
|
222
|
+
type SnapshotDbFn = () => Promise<void>;
|
|
223
|
+
type ResetSnapshotFn = () => Promise<void>;
|
|
222
224
|
interface PgliteAdapter {
|
|
223
225
|
/** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */
|
|
224
226
|
adapter: PrismaPg;
|
|
@@ -226,6 +228,10 @@ interface PgliteAdapter {
|
|
|
226
228
|
pglite: _electric_sql_pglite.PGlite;
|
|
227
229
|
/** Clear all user tables. Call in `beforeEach` for per-test isolation. */
|
|
228
230
|
resetDb: ResetDbFn;
|
|
231
|
+
/** Snapshot current DB state. Subsequent `resetDb` calls restore to this snapshot. */
|
|
232
|
+
snapshotDb: SnapshotDbFn;
|
|
233
|
+
/** Discard the current snapshot. Subsequent `resetDb` calls truncate to empty. */
|
|
234
|
+
resetSnapshot: ResetSnapshotFn;
|
|
229
235
|
/** Shut down pool and PGlite. Not needed in tests (process exit handles it). */
|
|
230
236
|
close: () => Promise<void>;
|
|
231
237
|
}
|
|
@@ -237,4 +243,4 @@ interface PgliteAdapter {
|
|
|
237
243
|
*/
|
|
238
244
|
declare const createPgliteAdapter: (options?: CreatePgliteAdapterOptions) => Promise<PgliteAdapter>;
|
|
239
245
|
|
|
240
|
-
export { type CreatePgliteAdapterOptions, type CreatePoolOptions, PGliteBridge, type PgliteAdapter, type PoolResult, type ResetDbFn, createPgliteAdapter, createPool };
|
|
246
|
+
export { type CreatePgliteAdapterOptions, type CreatePoolOptions, PGliteBridge, type PgliteAdapter, type PoolResult, type ResetDbFn, type ResetSnapshotFn, type SnapshotDbFn, createPgliteAdapter, createPool };
|
package/dist/index.js
CHANGED
|
@@ -428,6 +428,7 @@ var createPool = async (options = {}) => {
|
|
|
428
428
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
429
429
|
import { dirname, join } from "path";
|
|
430
430
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
431
|
+
var SNAPSHOT_SCHEMA = "_pglite_snapshot";
|
|
431
432
|
var discoverMigrationsPath = async (configRoot) => {
|
|
432
433
|
try {
|
|
433
434
|
const { loadConfigFromFile } = await import("@prisma/config");
|
|
@@ -491,20 +492,75 @@ var createPgliteAdapter = async (options = {}) => {
|
|
|
491
492
|
}
|
|
492
493
|
const adapter = new PrismaPg(pool);
|
|
493
494
|
let cachedTables = null;
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
495
|
+
let hasSnapshot = false;
|
|
496
|
+
const discoverTables = async () => {
|
|
497
|
+
if (cachedTables !== null) return cachedTables;
|
|
498
|
+
const { rows } = await pglite.query(
|
|
499
|
+
`SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified
|
|
500
|
+
FROM pg_tables
|
|
501
|
+
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
|
|
502
|
+
AND schemaname != '${SNAPSHOT_SCHEMA}'
|
|
503
|
+
AND tablename NOT LIKE '_prisma%'`
|
|
504
|
+
);
|
|
505
|
+
cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(", ") : "";
|
|
506
|
+
return cachedTables;
|
|
507
|
+
};
|
|
508
|
+
const snapshotDb = async () => {
|
|
509
|
+
await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
|
|
510
|
+
await pglite.exec(`CREATE SCHEMA "${SNAPSHOT_SCHEMA}"`);
|
|
511
|
+
const { rows: tables } = await pglite.query(
|
|
512
|
+
`SELECT quote_ident(tablename) AS tablename FROM pg_tables
|
|
513
|
+
WHERE schemaname = 'public'
|
|
514
|
+
AND tablename NOT LIKE '_prisma%'`
|
|
515
|
+
);
|
|
516
|
+
for (const { tablename } of tables) {
|
|
517
|
+
await pglite.exec(
|
|
518
|
+
`CREATE TABLE "${SNAPSHOT_SCHEMA}".${tablename} AS SELECT * FROM public.${tablename}`
|
|
501
519
|
);
|
|
502
|
-
cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(", ") : "";
|
|
503
520
|
}
|
|
504
|
-
|
|
521
|
+
const { rows: seqs } = await pglite.query(
|
|
522
|
+
`SELECT quote_literal(sequencename) AS name, last_value::text AS value
|
|
523
|
+
FROM pg_sequences WHERE schemaname = 'public' AND last_value IS NOT NULL`
|
|
524
|
+
);
|
|
525
|
+
await pglite.exec(`CREATE TABLE "${SNAPSHOT_SCHEMA}".__sequences (name text, value bigint)`);
|
|
526
|
+
for (const { name, value } of seqs) {
|
|
527
|
+
await pglite.exec(`INSERT INTO "${SNAPSHOT_SCHEMA}".__sequences VALUES (${name}, ${value})`);
|
|
528
|
+
}
|
|
529
|
+
hasSnapshot = true;
|
|
530
|
+
};
|
|
531
|
+
const resetSnapshot = async () => {
|
|
532
|
+
hasSnapshot = false;
|
|
533
|
+
await pglite.exec(`DROP SCHEMA IF EXISTS "${SNAPSHOT_SCHEMA}" CASCADE`);
|
|
534
|
+
};
|
|
535
|
+
const resetDb = async () => {
|
|
536
|
+
const tables = await discoverTables();
|
|
537
|
+
if (hasSnapshot && tables) {
|
|
538
|
+
try {
|
|
539
|
+
await pglite.exec("SET session_replication_role = replica");
|
|
540
|
+
await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
|
|
541
|
+
const { rows: snapshotTables } = await pglite.query(
|
|
542
|
+
`SELECT quote_ident(tablename) AS tablename FROM pg_tables
|
|
543
|
+
WHERE schemaname = '${SNAPSHOT_SCHEMA}'
|
|
544
|
+
AND tablename != '__sequences'`
|
|
545
|
+
);
|
|
546
|
+
for (const { tablename } of snapshotTables) {
|
|
547
|
+
await pglite.exec(
|
|
548
|
+
`INSERT INTO public.${tablename} SELECT * FROM "${SNAPSHOT_SCHEMA}".${tablename}`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
} finally {
|
|
552
|
+
await pglite.exec("SET session_replication_role = DEFAULT");
|
|
553
|
+
}
|
|
554
|
+
const { rows: seqs } = await pglite.query(
|
|
555
|
+
`SELECT quote_literal(name) AS name, value::text AS value FROM "${SNAPSHOT_SCHEMA}".__sequences`
|
|
556
|
+
);
|
|
557
|
+
for (const { name, value } of seqs) {
|
|
558
|
+
await pglite.exec(`SELECT setval(${name}, ${value})`);
|
|
559
|
+
}
|
|
560
|
+
} else if (tables) {
|
|
505
561
|
try {
|
|
506
562
|
await pglite.exec("SET session_replication_role = replica");
|
|
507
|
-
await pglite.exec(`TRUNCATE TABLE ${
|
|
563
|
+
await pglite.exec(`TRUNCATE TABLE ${tables} CASCADE`);
|
|
508
564
|
} finally {
|
|
509
565
|
await pglite.exec("SET session_replication_role = DEFAULT");
|
|
510
566
|
}
|
|
@@ -512,7 +568,7 @@ var createPgliteAdapter = async (options = {}) => {
|
|
|
512
568
|
await pglite.exec("RESET ALL");
|
|
513
569
|
await pglite.exec("DEALLOCATE ALL");
|
|
514
570
|
};
|
|
515
|
-
return { adapter, pglite, resetDb, close: poolClose };
|
|
571
|
+
return { adapter, pglite, resetDb, snapshotDb, resetSnapshot, close: poolClose };
|
|
516
572
|
};
|
|
517
573
|
export {
|
|
518
574
|
PGliteBridge,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pglite-bridge.ts","../src/session-lock.ts","../src/create-pool.ts","../src/create-pglite-adapter.ts"],"sourcesContent":["/**\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 type SessionLock,\n createBridgeId,\n extractRfqStatus,\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 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 * 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 * 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, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { createPool } from './create-pool.ts';\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 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 /** 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 (~0ms)\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\n const resetDb = async () => {\n if (cachedTables === null) {\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 tablename NOT LIKE '_prisma%'`,\n );\n cachedTables = rows.length > 0 ? rows.map((r) => r.qualified).join(', ') : '';\n }\n\n if (cachedTables) {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await pglite.exec(`TRUNCATE TABLE ${cachedTables} 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, close: poolClose };\n};\n"],"mappings":"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","names":["cleaned"]}
|
|
1
|
+
{"version":3,"sources":["../src/pglite-bridge.ts","../src/session-lock.ts","../src/create-pool.ts","../src/create-pglite-adapter.ts"],"sourcesContent":["/**\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 type SessionLock,\n createBridgeId,\n extractRfqStatus,\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 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 * 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 * 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, readFileSync, readdirSync, 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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","names":["cleaned"]}
|