prisma-pglite-bridge 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/ppb.mjs +1 -1
- package/dist/ppb.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1191,7 +1191,7 @@ const createSnapshotManager = (pglite) => {
|
|
|
1191
1191
|
await pglite.exec(`CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.${quoteIdent$1(snapName)} AS SELECT * FROM ${qualified}`);
|
|
1192
1192
|
await pglite.exec(`INSERT INTO ${SNAPSHOT_SCHEMA_IDENT}.__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`);
|
|
1193
1193
|
}
|
|
1194
|
-
const { rows: seqs } = await pglite.query(`SELECT quote_literal(schemaname || '.' || sequencename) AS name, last_value::text AS value
|
|
1194
|
+
const { rows: seqs } = await pglite.query(`SELECT quote_literal(quote_ident(schemaname) || '.' || quote_ident(sequencename)) AS name, last_value::text AS value
|
|
1195
1195
|
FROM pg_sequences
|
|
1196
1196
|
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
|
|
1197
1197
|
AND schemaname != '${SNAPSHOT_SCHEMA}'
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["diagnostics_channel","Duplex","createPool","quoteIdent","createPool","PrismaPg","createBasePool"],"sources":["../src/utils/diagnostics.ts","../src/utils/time.ts","../src/pglite-duplex.ts","../src/bridge-client.ts","../src/utils/session-lock.ts","../src/create-pool.ts","../src/utils/bridge-stats.ts","../src/utils/snapshot.ts","../src/create-pglite-bridge.ts","../src/migrations.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["/**\n * Public `node:diagnostics_channel` surface.\n *\n * The bridge publishes per-query and per-lock-wait events to named\n * channels. External consumers (OpenTelemetry, APM tools, custom\n * loggers) can subscribe without touching the library API. Built-in\n * bridge stats are updated directly from the bridge and are\n * independent of these public channels.\n *\n * Publication is gated by `channel.hasSubscribers`, so the hot path\n * pays no cost when nobody is listening. Subscribing opts the consumer\n * in to the timing/publication overhead.\n *\n * Filter on `bridgeId` to distinguish events from different bridges\n * in the same process — obtain it from the `createPGliteBridge` or\n * `createPool` return value.\n */\nimport diagnostics_channel from 'node:diagnostics_channel';\n\n/**\n * `node:diagnostics_channel` name for per-query events. Subscribers receive\n * a {@link QueryEvent} each time the bridge completes a query.\n */\nexport const QUERY_CHANNEL = 'prisma-pglite-bridge:query';\n\n/**\n * `node:diagnostics_channel` name for per-acquisition session-lock wait\n * events. Subscribers receive a {@link LockWaitEvent} after each\n * acquisition completes.\n */\nexport const LOCK_WAIT_CHANNEL = 'prisma-pglite-bridge:lock-wait';\n\n/** Payload published to {@link QUERY_CHANNEL}. */\nexport interface QueryEvent {\n /** Bridge identity tag — filter on this to isolate one bridge's events. */\n bridgeId: symbol;\n /** Wall-clock duration of the query in milliseconds. */\n durationMs: number;\n /** `false` when the query rejected (protocol or SQL error). */\n succeeded: boolean;\n}\n\n/** Payload published to {@link LOCK_WAIT_CHANNEL}. */\nexport interface LockWaitEvent {\n /** Bridge identity tag — filter on this to isolate one bridge's events. */\n bridgeId: symbol;\n /** Time spent waiting to acquire the session lock, in milliseconds. */\n durationMs: number;\n}\n\nexport const queryChannel: diagnostics_channel.Channel = diagnostics_channel.channel(QUERY_CHANNEL);\nexport const lockWaitChannel: diagnostics_channel.Channel =\n diagnostics_channel.channel(LOCK_WAIT_CHANNEL);\n","/** Convert a nanosecond `bigint` (as returned by `process.hrtime.bigint()`) to milliseconds. */\nexport const nsToMs = (ns: bigint): number => Number(ns) / 1_000_000;\n","/**\n * PGlite duplex 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 → duplex frames messages →\n * PGlite processes via execProtocolRawStream → duplex 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 streams 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 type { TelemetrySink } from './utils/bridge-stats.ts';\nimport { lockWaitChannel, queryChannel } from './utils/diagnostics.ts';\nimport type { BridgeId as DuplexId, SessionLock } from './utils/session-lock.ts';\nimport { nsToMs } from './utils/time.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 types\nconst READY_FOR_QUERY = 0x5a; // Z — 6 bytes: Z + length(5) + status\nconst ERROR_RESPONSE = 0x45; // E — signals in-band SQL error (not a JS throw)\nconst ROW_DESCRIPTION = 0x54; // T — column metadata; rewritten to widen oid 18\n\n// pg_catalog system columns (pg_constraint.contype, pg_type.typtype, pg_class.relkind,\n// etc.) report type oid 18 (\"char\", 1-byte). @prisma/adapter-pg's fieldToColumnType\n// has no case for oid 18 and throws UnsupportedNativeDataType. The bytes on the wire\n// for these single-ASCII-char values decode identically as text, so we widen oid 18\n// to oid 25 (text) in RowDescription frames before pg.Client sees them — but only\n// when the field originates from a pg_catalog relation (tableOID below\n// FirstNormalObjectId). User-defined \"char\" columns are left untouched so the\n// upstream type-mapping gap surfaces instead of being silently papered over with a\n// possibly-lossy text decode. Remove this rewrite once @prisma/adapter-pg gains a\n// fieldToColumnType case for oid 18.\nconst PG_TYPE_OID_CHAR = 18;\nconst PG_TYPE_OID_TEXT = 25;\n// Mirrors PostgreSQL's FirstNormalObjectId (src/include/access/transam.h): every\n// system catalog relation has an OID below this; user-created objects start at it.\nconst PG_FIRST_USER_OID = 16384;\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 * Upper bound on a single backend message length declared in its 4-byte\n * header. PostgreSQL's own wire protocol maxes out around 1 GiB per\n * message; anything larger indicates a corrupted or hostile stream and\n * must not be allocated against.\n */\nconst MAX_BACKEND_MESSAGE_LENGTH = 1_073_741_824;\n\n/**\n * Widens any field whose dataTypeOID is 18 (\"char\") to oid 25 (text) — but\n * only when the field originates from a pg_catalog relation (tableOID is\n * non-zero and below FirstNormalObjectId). Single-ASCII-byte payloads from\n * pg_catalog views (pg_constraint.contype, pg_type.typtype, etc.) decode\n * identically as text, so the row data needs no transformation. Fields with\n * tableOID === 0 (computed expressions) and user-table fields are left\n * untouched, since arbitrary bytes in user \"char\" data can't safely be\n * relabelled as text. Frame length is unchanged because all rewrites are\n * fixed-size in place.\n */\nconst rewriteRowDescriptionInPlace = (buf: Buffer): void => {\n const fieldCount = buf.readInt16BE(5);\n let p = 7;\n for (let i = 0; i < fieldCount; i++) {\n while (p < buf.length && buf[p] !== 0) p++;\n p++; // NUL terminator\n // Per-field fixed suffix is 18 bytes: tableOID(4) + columnAttr(2) +\n // dataTypeOID(4) + dataTypeSize(2) + typeModifier(4) + formatCode(2).\n // Bail if the frame is truncated rather than throwing RangeError mid-loop.\n /* c8 ignore next — defense-in-depth: framer caller passes a complete frame */\n if (p + 18 > buf.length) return;\n const tableOID = buf.readUInt32BE(p);\n p += 4 + 2; // tableOID, columnAttr\n const oid = buf.readUInt32BE(p);\n if (oid === PG_TYPE_OID_CHAR && tableOID !== 0 && tableOID < PG_FIRST_USER_OID) {\n buf.writeUInt32BE(PG_TYPE_OID_TEXT, p);\n buf.writeInt16BE(-1, p + 4);\n }\n p += 4 + 2 + 4 + 2; // dataTypeOID, dataTypeSize, typeModifier, formatCode\n }\n};\n\n/**\n * Concatenates multiple Uint8Array views into one contiguous buffer.\n */\nconst concat = (parts: Uint8Array[]): Uint8Array => {\n /* c8 ignore next — parts[0] defined when length===1 */\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\ntype BackendMessageFramerOptions = {\n suppressIntermediateReadyForQuery?: boolean;\n onChunk: (chunk: Uint8Array) => void;\n onErrorResponse?: () => void;\n onReadyForQuery?: (status: number) => void;\n};\n\n/**\n * Frontend chunk queue that frames messages without repeatedly compacting\n * the full buffered input.\n *\n * @internal — exported for testing only\n */\nexport class FrontendMessageBuffer {\n private chunks: Uint8Array[] = [];\n private headIndex = 0;\n private headOffset = 0;\n private totalLength = 0;\n\n get length(): number {\n return this.totalLength;\n }\n\n push(chunk: Uint8Array): void {\n if (chunk.length === 0) return;\n this.chunks.push(chunk);\n this.totalLength += chunk.length;\n }\n\n clear(): void {\n this.chunks = [];\n this.headIndex = 0;\n this.headOffset = 0;\n this.totalLength = 0;\n }\n\n readInt32BE(offset: number): number | undefined {\n if (offset < 0 || offset + 4 > this.totalLength) return undefined;\n\n const head = this.chunks[this.headIndex];\n /* c8 ignore next — head defined when totalLength > 0 */\n if (head !== undefined) {\n const start = this.headOffset + offset;\n if (start + 4 <= head.length) {\n /* c8 ignore start — bounds guaranteed by `start + 4 <= head.length` */\n const b1 = head[start] ?? 0;\n const b2 = head[start + 1] ?? 0;\n const b3 = head[start + 2] ?? 0;\n const b4 = head[start + 3] ?? 0;\n /* c8 ignore stop */\n return ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n }\n }\n\n let remaining = this.headOffset + offset;\n const bytes = new Uint8Array(4);\n let writeOffset = 0;\n\n for (let i = this.headIndex; i < this.chunks.length && writeOffset < 4; i++) {\n const chunk = this.chunks[i];\n /* c8 ignore next — chunks between headIndex and end are always populated */\n if (chunk === undefined) return undefined;\n if (remaining >= chunk.length) {\n remaining -= chunk.length;\n continue;\n }\n\n const bytesToCopy = Math.min(4 - writeOffset, chunk.length - remaining);\n bytes.set(chunk.subarray(remaining, remaining + bytesToCopy), writeOffset);\n writeOffset += bytesToCopy;\n remaining = 0;\n }\n\n /* c8 ignore start — bytes is a fixed 4-byte Uint8Array */\n const b1 = bytes[0] ?? 0;\n const b2 = bytes[1] ?? 0;\n const b3 = bytes[2] ?? 0;\n const b4 = bytes[3] ?? 0;\n /* c8 ignore stop */\n return ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n }\n\n consume(length: number): Uint8Array {\n if (length < 0 || length > this.totalLength) {\n throw new Error(`Cannot consume ${length} bytes from ${this.totalLength}-byte buffer`);\n }\n if (length === 0) return new Uint8Array(0);\n\n const head = this.chunks[this.headIndex];\n /* c8 ignore next — head defined when totalLength > 0 */\n if (head !== undefined) {\n const headRemaining = head.length - this.headOffset;\n if (headRemaining >= length) {\n const slice = head.subarray(this.headOffset, this.headOffset + length);\n this.headOffset += length;\n this.totalLength -= length;\n if (this.headOffset === head.length) {\n this.headIndex++;\n this.headOffset = 0;\n this.compactChunks();\n }\n return slice;\n }\n }\n\n const result = new Uint8Array(length);\n let writeOffset = 0;\n let remaining = length;\n\n while (remaining > 0) {\n const chunk = this.chunks[this.headIndex];\n /* c8 ignore next 3 — guarded by line-116 length check */\n if (chunk === undefined) {\n throw new Error('FrontendMessageBuffer underflow');\n }\n const available = chunk.length - this.headOffset;\n const bytesToCopy = Math.min(remaining, available);\n result.set(chunk.subarray(this.headOffset, this.headOffset + bytesToCopy), writeOffset);\n writeOffset += bytesToCopy;\n remaining -= bytesToCopy;\n this.headOffset += bytesToCopy;\n this.totalLength -= bytesToCopy;\n if (this.headOffset === chunk.length) {\n this.headIndex++;\n this.headOffset = 0;\n this.compactChunks();\n }\n }\n\n return result;\n }\n\n private compactChunks(): void {\n if (this.headIndex === this.chunks.length) {\n this.chunks = [];\n this.headIndex = 0;\n return;\n }\n\n if (this.headIndex >= 32 && this.headIndex * 2 >= this.chunks.length) {\n this.chunks = this.chunks.slice(this.headIndex);\n this.headIndex = 0;\n }\n }\n}\n\n/**\n * Streams backend protocol messages without materializing whole responses.\n *\n * Non-RFQ payload bytes are forwarded as they arrive. ReadyForQuery frames are\n * tracked only once complete; when suppression is enabled, only the final RFQ\n * is emitted.\n *\n * @internal — exported for testing only\n */\nexport class BackendMessageFramer {\n private readonly suppressIntermediateReadyForQuery: boolean;\n private readonly onChunk: (chunk: Uint8Array) => void;\n private readonly onErrorResponse?: () => void;\n private readonly onReadyForQuery?: (status: number) => void;\n private readonly headerScratch = new Uint8Array(4);\n private readonly heldRfq = new Uint8Array(6);\n private messageType?: number;\n private headerBytesRead = 0;\n private payloadBytesRemaining = 0;\n private rfqBytesRead = 0;\n /** Slow-path RowDescription accumulator. Set once the T-frame header is decoded\n * and a multi-chunk payload starts arriving; cleared after rewrite + emit. */\n private rowDescBuffer?: Buffer;\n private rowDescOffset = 0;\n\n constructor(options: BackendMessageFramerOptions) {\n this.suppressIntermediateReadyForQuery = options.suppressIntermediateReadyForQuery ?? false;\n this.onChunk = options.onChunk;\n this.onErrorResponse = options.onErrorResponse;\n this.onReadyForQuery = options.onReadyForQuery;\n }\n\n write(chunk: Uint8Array): void {\n if (chunk.length === 0) return;\n\n let offset = 0;\n let passthroughStart = -1;\n const flushPassthrough = (end: number): void => {\n if (passthroughStart >= 0 && end > passthroughStart) {\n this.emitChunkSlice(chunk, passthroughStart, end);\n passthroughStart = -1;\n }\n };\n while (offset < chunk.length) {\n if (this.messageType === undefined) {\n // Fast path: if type + 4-byte header + full payload are all in this\n // chunk, emit the whole message as one slice. Avoids the per-message\n // prefix allocation + two downstream pushes that the byte-state-machine\n // path below performs. Falls through to the slow path when the message\n // spans chunks.\n const available = chunk.length - offset;\n if (available >= 5) {\n /* c8 ignore start — bounds guaranteed by `available >= 5` */\n const msgType = chunk[offset] ?? 0;\n const b1 = chunk[offset + 1] ?? 0;\n const b2 = chunk[offset + 2] ?? 0;\n const b3 = chunk[offset + 3] ?? 0;\n const b4 = chunk[offset + 4] ?? 0;\n /* c8 ignore stop */\n const messageLength = ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n if (messageLength < 4) {\n throw new Error(`Malformed backend message length: ${messageLength}`);\n }\n if (messageLength > MAX_BACKEND_MESSAGE_LENGTH) {\n throw new Error(\n `Backend message length ${messageLength} exceeds sanity cap ${MAX_BACKEND_MESSAGE_LENGTH}`,\n );\n }\n const totalLen = 1 + messageLength;\n if (available >= totalLen) {\n if (msgType === ERROR_RESPONSE) {\n this.onErrorResponse?.();\n }\n if (msgType === READY_FOR_QUERY && messageLength === 5) {\n flushPassthrough(offset);\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n /* c8 ignore next — messageLength === 5 for RFQ; payload is 1 byte */\n const status = chunk[offset + 5] ?? 0;\n this.heldRfq[0] = msgType;\n this.heldRfq[1] = b1;\n this.heldRfq[2] = b2;\n this.heldRfq[3] = b3;\n this.heldRfq[4] = b4;\n this.heldRfq[5] = status;\n this.rfqBytesRead = 6;\n this.onReadyForQuery?.(status);\n if (!this.suppressIntermediateReadyForQuery) {\n this.emitReadyForQuery();\n this.rfqBytesRead = 0;\n }\n } else if (msgType === ROW_DESCRIPTION) {\n flushPassthrough(offset);\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n this.emitRewrittenRowDescription(\n Buffer.from(chunk.subarray(offset, offset + totalLen)),\n );\n } else {\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n if (passthroughStart < 0) {\n passthroughStart = offset;\n }\n }\n offset += totalLen;\n continue;\n }\n }\n\n flushPassthrough(offset);\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n /* c8 ignore next — offset < chunk.length guaranteed by outer while */\n this.messageType = chunk[offset] ?? 0;\n this.headerBytesRead = 0;\n this.payloadBytesRemaining = 0;\n this.rfqBytesRead = this.messageType === READY_FOR_QUERY ? 1 : 0;\n if (this.rfqBytesRead === 1) {\n this.heldRfq[0] = this.messageType;\n }\n offset++;\n continue;\n }\n\n if (this.headerBytesRead < 4) {\n const bytesToCopy = Math.min(4 - this.headerBytesRead, chunk.length - offset);\n const headerChunk = chunk.subarray(offset, offset + bytesToCopy);\n this.headerScratch.set(headerChunk, this.headerBytesRead);\n if (this.messageType === READY_FOR_QUERY) {\n this.heldRfq.set(headerChunk, this.rfqBytesRead);\n this.rfqBytesRead += bytesToCopy;\n }\n this.headerBytesRead += bytesToCopy;\n offset += bytesToCopy;\n if (this.headerBytesRead < 4) continue;\n\n /* c8 ignore start — header bytes all populated before read */\n const b1 = this.headerScratch[0] ?? 0;\n const b2 = this.headerScratch[1] ?? 0;\n const b3 = this.headerScratch[2] ?? 0;\n const b4 = this.headerScratch[3] ?? 0;\n /* c8 ignore stop */\n const messageLength = ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n if (messageLength < 4) {\n throw new Error(`Malformed backend message length: ${messageLength}`);\n }\n if (messageLength > MAX_BACKEND_MESSAGE_LENGTH) {\n throw new Error(\n `Backend message length ${messageLength} exceeds sanity cap ${MAX_BACKEND_MESSAGE_LENGTH}`,\n );\n }\n\n this.payloadBytesRemaining = messageLength - 4;\n\n if (this.messageType === ERROR_RESPONSE) {\n this.onErrorResponse?.();\n }\n\n if (this.isReadyForQueryFrame()) {\n continue;\n }\n\n this.dropHeldReadyForQuery();\n if (this.messageType === ROW_DESCRIPTION) {\n // Valid RowDescription always carries at least a 2-byte fieldCount,\n // so payloadBytesRemaining is > 0 here — no zero-payload finalize needed.\n this.rowDescBuffer = Buffer.alloc(5 + this.payloadBytesRemaining);\n this.rowDescBuffer[0] = ROW_DESCRIPTION;\n this.rowDescBuffer.set(this.headerScratch, 1);\n this.rowDescOffset = 5;\n continue;\n }\n this.emitPrefix();\n if (this.payloadBytesRemaining === 0) {\n this.finishMessage();\n }\n continue;\n }\n\n if (this.isReadyForQueryFrame()) {\n const bytesToCopy = Math.min(this.payloadBytesRemaining, chunk.length - offset);\n const payloadChunk = chunk.subarray(offset, offset + bytesToCopy);\n this.heldRfq.set(payloadChunk, this.rfqBytesRead);\n this.rfqBytesRead += bytesToCopy;\n this.payloadBytesRemaining -= bytesToCopy;\n offset += bytesToCopy;\n /* c8 ignore next 3 — bytesToCopy ≥ 1 consumes the 1-byte RFQ payload */\n if (this.payloadBytesRemaining === 0) {\n this.finishReadyForQuery();\n }\n continue;\n }\n\n const bytesToEmit = Math.min(this.payloadBytesRemaining, chunk.length - offset);\n /* c8 ignore next — bytesToEmit always ≥ 1 when reached */\n if (bytesToEmit > 0) {\n if (this.rowDescBuffer !== undefined) {\n this.rowDescBuffer.set(chunk.subarray(offset, offset + bytesToEmit), this.rowDescOffset);\n this.rowDescOffset += bytesToEmit;\n } else {\n this.emitChunkSlice(chunk, offset, offset + bytesToEmit);\n }\n this.payloadBytesRemaining -= bytesToEmit;\n offset += bytesToEmit;\n }\n if (this.payloadBytesRemaining === 0) {\n if (this.rowDescBuffer !== undefined) {\n const buf = this.rowDescBuffer;\n this.rowDescBuffer = undefined;\n this.emitRewrittenRowDescription(buf);\n }\n this.finishMessage();\n }\n }\n\n flushPassthrough(offset);\n }\n\n flush(options?: { dropHeldReadyForQuery?: boolean }): void {\n if (options?.dropHeldReadyForQuery === true) {\n this.dropHeldReadyForQuery();\n } else if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.emitReadyForQuery();\n this.rfqBytesRead = 0;\n }\n }\n\n reset(): void {\n this.messageType = undefined;\n this.headerBytesRead = 0;\n this.payloadBytesRemaining = 0;\n this.rfqBytesRead = 0;\n this.rowDescBuffer = undefined;\n this.rowDescOffset = 0;\n }\n\n private isReadyForQueryFrame(): boolean {\n return this.messageType === READY_FOR_QUERY && this.payloadBytesRemaining === 1;\n }\n\n private finishReadyForQuery(): void {\n const status = this.heldRfq[5];\n /* c8 ignore next — heldRfq[5] always populated before finishReadyForQuery */\n if (status !== undefined) {\n this.onReadyForQuery?.(status);\n }\n\n if (!this.suppressIntermediateReadyForQuery) {\n this.emitReadyForQuery();\n }\n\n this.finishMessage();\n }\n\n private emitReadyForQuery(): void {\n this.onChunk(this.heldRfq.slice(0, 6));\n }\n\n private dropHeldReadyForQuery(): void {\n this.rfqBytesRead = 0;\n }\n\n private emitPrefix(): void {\n const prefix = new Uint8Array(5);\n /* c8 ignore next — messageType always set when emitPrefix is called */\n prefix[0] = this.messageType ?? 0;\n prefix.set(this.headerScratch, 1);\n this.onChunk(prefix);\n }\n\n private emitRewrittenRowDescription(buf: Buffer): void {\n rewriteRowDescriptionInPlace(buf);\n this.onChunk(buf);\n }\n\n private emitChunkSlice(chunk: Uint8Array, start: number, end: number): void {\n const length = end - start;\n /* c8 ignore next — callers pass end > start */\n if (length <= 0) return;\n\n // PGlite already hands us standalone Uint8Array chunks copied out of the\n // WASM heap, so when this chunk owns its full backing store we can hand pg\n // zero-copy Buffer views for arbitrary subranges. We still copy when the\n // chunk is a view into a larger backing buffer (to avoid pinning unrelated\n // trailing bytes) or when the backing store is shared (to prevent the WASM\n // runtime from mutating bytes pg is still consuming).\n if (\n chunk.byteOffset === 0 &&\n chunk.byteLength === chunk.buffer.byteLength &&\n !(chunk.buffer instanceof SharedArrayBuffer)\n ) {\n this.onChunk(Buffer.from(chunk.buffer, start, length));\n return;\n }\n\n const exact = Buffer.from(chunk.subarray(start, end));\n this.onChunk(exact);\n }\n\n private finishMessage(): void {\n this.messageType = undefined;\n this.headerBytesRead = 0;\n this.payloadBytesRemaining = 0;\n if (!this.suppressIntermediateReadyForQuery) {\n this.rfqBytesRead = 0;\n }\n }\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()` / `createPGliteBridge()`:\n *\n * ```typescript\n * const client = new pg.Client({\n * stream: () => new PGliteDuplex(pglite),\n * });\n * ```\n */\nexport class PGliteDuplex extends Duplex {\n private readonly pglite: PGlite;\n private readonly sessionLock?: SessionLock;\n private readonly bridgeId?: symbol;\n private readonly telemetry?: TelemetrySink;\n private readonly syncToFs: boolean;\n private readonly duplexId: DuplexId;\n /** Incoming bytes framed directly from a queued chunk buffer */\n private readonly input = new FrontendMessageBuffer();\n private phase: 'pre_startup' | 'ready' = 'pre_startup';\n private draining = false;\n private tornDown = false;\n /** Callbacks waiting for drain to process their data */\n private drainQueue: Array<(error?: Error | null) => void> = [];\n /** Buffered EQP messages awaiting Sync */\n private pipeline: Uint8Array[] = [];\n\n /**\n * @param pglite PGlite instance to bridge to. The caller owns its lifecycle.\n * @param sessionLock Shared lock that serialises access to the PGlite runtime\n * across multiple duplex streams. Omit for a standalone duplex.\n * @param bridgeId Identity tag published with diagnostics-channel events.\n * Omit to disable channel publication for this duplex.\n * @param telemetry Internal sink used by `createPGliteBridge` for built-in\n * stats. Not a public extension point — subscribe via\n * `node:diagnostics_channel` instead.\n * @param syncToFs Whether each wire-protocol call should force a\n * filesystem sync before returning. Disable only when\n * higher throughput / lower RSS is worth weaker durability.\n */\n constructor(\n pglite: PGlite,\n sessionLock?: SessionLock,\n bridgeId?: symbol,\n telemetry?: TelemetrySink,\n syncToFs = true,\n ) {\n super();\n this.pglite = pglite;\n this.sessionLock = sessionLock;\n this.bridgeId = bridgeId;\n this.telemetry = telemetry;\n this.syncToFs = syncToFs;\n this.duplexId = Symbol('duplex');\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.input.push(chunk);\n this.enqueue(callback);\n }\n\n /** Handles corked batches — pg.Client corks during prepared queries (P+B+D+E+S) */\n override _writev(\n chunks: Array<{ chunk: Buffer; encoding: BufferEncoding }>,\n callback: (error?: Error | null) => void,\n ): void {\n for (const { chunk } of chunks) {\n this.input.push(chunk);\n }\n this.enqueue(callback);\n }\n\n override _final(callback: (error?: Error | null) => void): void {\n this.sessionLock?.release(this.duplexId);\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.input.clear();\n this.sessionLock?.cancel(this.duplexId, error ?? new Error('Bridge destroyed'));\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 /**\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(/* c8 ignore next */ () => {});\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 /* c8 ignore next — enqueue only starts drain when !draining */\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.input.length > 0) {\n /* c8 ignore next — race-only: destroy after a drain iteration resolves */\n if (this.tornDown) break;\n const beforeLength = this.input.length;\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 /* c8 ignore next — loop-continue unreachable: no new input arrives mid-drain */\n if (this.input.length === 0 || this.input.length === beforeLength) 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.duplexId);\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 if (this.input.length < 4) return;\n const len = this.input.readInt32BE(0);\n /* c8 ignore next — len === undefined unreachable once length ≥ 4 */\n if (len === undefined || this.input.length < len) return;\n\n const message = this.input.consume(len);\n\n const session = this.acquireSession();\n if (session) await session;\n await this.pglite.runExclusive(async () => {\n await this.streamProtocol(message, { detectErrors: false, suppressIntermediateRfq: false });\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 while (this.input.length >= 5) {\n const msgLen = this.input.readInt32BE(1);\n /* c8 ignore next — input.length ≥ 5 guarantees readable int32 */\n if (msgLen === undefined) break;\n const len = 1 + msgLen;\n if (len < 5 || this.input.length < len) break;\n\n const message = this.input.consume(len);\n /* c8 ignore next — consume(len ≥ 5) returns non-empty */\n const msgType = message[0] ?? 0;\n\n if (msgType === TERMINATE) {\n this.sessionLock?.release(this.duplexId);\n this.push(null);\n return;\n }\n\n if (EQP_MESSAGES.has(msgType)) {\n this.pipeline.push(message);\n continue;\n }\n\n if (msgType === SYNC) {\n this.pipeline.push(message);\n await this.flushPipeline();\n continue;\n }\n\n // SimpleQuery or other standalone message\n await this.runWithTiming((detectErrors) =>\n this.streamProtocol(message, { detectErrors, suppressIntermediateRfq: false }),\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). A streaming framer suppresses intermediate ReadyForQuery\n * messages while forwarding the rest of the response without\n * materializing it.\n */\n private async flushPipeline(): Promise<void> {\n const messages = this.pipeline;\n this.pipeline = [];\n const batch = concat(messages);\n await this.runWithTiming((detectErrors) =>\n this.streamProtocol(batch, { detectErrors, suppressIntermediateRfq: true }),\n );\n }\n\n /**\n * Acquires the session, runs the op under `pglite.runExclusive`, and\n * updates internal stats and/or publishes diagnostics events when enabled.\n * When neither internal telemetry nor diagnostics subscribers need timing,\n * skips timing entirely.\n *\n * `op` returns `false` when an `ErrorResponse` was seen without throwing\n * (protocol-level failure). Combined with the catch branch, both failure\n * modes flip `succeeded` so both `BridgeStats` and `QUERY_CHANNEL`\n * payloads stay accurate. `detectErrors` is therefore tied to whether\n * either of those consumers is active, not to timing in general.\n */\n private async runWithTiming(op: (detectErrors: boolean) => Promise<boolean>): Promise<void> {\n const wantTelemetry = this.telemetry !== undefined;\n const publishQuery = this.bridgeId !== undefined && queryChannel.hasSubscribers;\n const publishLockWait = this.bridgeId !== undefined && lockWaitChannel.hasSubscribers;\n const wantTiming = wantTelemetry || publishQuery || publishLockWait;\n const detectErrors = wantTelemetry || publishQuery;\n\n if (!wantTiming) {\n const session = this.acquireSession();\n if (session) await session;\n await this.pglite.runExclusive(async () => {\n await op(false);\n });\n return;\n }\n\n const lockStart = process.hrtime.bigint();\n const session = this.acquireSession();\n if (session) await session;\n const queryStart = process.hrtime.bigint();\n const lockWaitMs = nsToMs(queryStart - lockStart);\n if (wantTelemetry) {\n this.telemetry?.recordLockWait(lockWaitMs);\n }\n if (publishLockWait) {\n lockWaitChannel.publish({\n bridgeId: this.bridgeId,\n durationMs: lockWaitMs,\n });\n }\n\n let succeeded = true;\n try {\n await this.pglite.runExclusive(async () => {\n succeeded = await op(detectErrors);\n });\n } catch (err) {\n succeeded = false;\n throw err;\n } finally {\n const queryMs = nsToMs(process.hrtime.bigint() - queryStart);\n if (wantTelemetry) {\n this.telemetry?.recordQuery(queryMs, succeeded);\n }\n if (publishQuery) {\n queryChannel.publish({\n bridgeId: this.bridgeId,\n durationMs: queryMs,\n succeeded,\n });\n }\n }\n }\n\n /**\n * Sends a message (or pipelined batch) to PGlite and pushes response\n * chunks directly to the stream as they arrive. Avoids collecting and\n * concatenating for large multi-row responses (e.g., findMany 500 rows\n * = ~503 onRawData chunks).\n *\n * For pipelined Extended Query batches, pass `suppressIntermediateRfq`\n * so only the final ReadyForQuery reaches the client.\n *\n * Must be called inside runExclusive.\n */\n private async streamProtocol(\n message: Uint8Array,\n options: { detectErrors: boolean; suppressIntermediateRfq: boolean },\n ): Promise<boolean> {\n const { detectErrors, suppressIntermediateRfq } = options;\n let errSeen = false;\n const framer = new BackendMessageFramer({\n suppressIntermediateReadyForQuery: suppressIntermediateRfq,\n onChunk: (chunk) => {\n /* c8 ignore next — race-only: tornDown becomes true mid-stream */\n if (!this.tornDown && chunk.length > 0) {\n this.push(chunk);\n }\n },\n onErrorResponse: () => {\n if (detectErrors) errSeen = true;\n },\n onReadyForQuery: (status) => {\n if (this.sessionLock) {\n this.sessionLock.updateStatus(this.duplexId, status);\n }\n },\n });\n\n await this.pglite.execProtocolRawStream(message, {\n syncToFs: this.syncToFs,\n onRawData: (chunk: Uint8Array) => {\n /* c8 ignore next — race-only: tornDown becomes true mid-stream */\n if (!this.tornDown) {\n framer.write(chunk);\n }\n },\n });\n\n framer.flush({ dropHeldReadyForQuery: this.tornDown });\n return !errSeen;\n }\n\n // ── Session lock helpers ──\n\n private acquireSession(): Promise<void> | undefined {\n return this.sessionLock?.acquire(this.duplexId);\n }\n}\n","import type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { PGliteDuplex } from './pglite-duplex.ts';\nimport type { TelemetrySink } from './utils/bridge-stats.ts';\nimport type { SessionLock } from './utils/session-lock.ts';\n\nexport const bridgeClientOptionsKey: unique symbol = Symbol('bridgeClientOptions');\n\ninterface BridgeClientOptions {\n pglite: PGlite;\n sessionLock?: SessionLock;\n bridgeId: symbol;\n telemetry?: TelemetrySink;\n syncToFs: boolean;\n}\n\ntype BridgeClientConfig = pg.ClientConfig & {\n [bridgeClientOptionsKey]: BridgeClientOptions;\n};\n\nexport type BridgePoolConfig = pg.PoolConfig & {\n [bridgeClientOptionsKey]: BridgeClientOptions;\n};\n\nexport class BridgeClient extends pg.Client {\n private querySubmissionChain: Promise<void> = Promise.resolve();\n\n constructor(config?: BridgeClientConfig) {\n const resolved = config ?? ({} as BridgeClientConfig);\n const { [bridgeClientOptionsKey]: bridge, ...clientConfig } = resolved;\n if (!bridge) {\n throw new Error('BridgeClient requires bridge options');\n }\n\n super({\n ...clientConfig,\n user: 'postgres',\n database: 'postgres',\n stream: () =>\n new PGliteDuplex(\n bridge.pglite,\n bridge.sessionLock,\n bridge.bridgeId,\n bridge.telemetry,\n bridge.syncToFs,\n ),\n });\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: satisfy pg.Client.query's overload union\n override query(...args: unknown[]): any {\n const first = args[0];\n // biome-ignore lint/suspicious/noExplicitAny: pg.Client.query has 7 overloads\n const callSuper = () => (super.query as any).apply(this, args);\n\n // Preserve pg's synchronous TypeError for null/undefined query.\n if (first === null || first === undefined) return callSuper();\n\n // Submittable: terminal signaling isn't uniform across the pg contract.\n // Let pg's internal queue handle it unserialized. adapter-pg never uses\n // this form; users mixing Submittable + Promise forms on one client may\n // still trip the pg queue deprecation.\n if (typeof (first as { submit?: unknown }).submit === 'function') {\n return callSuper();\n }\n\n const prior = this.querySubmissionChain;\n let signalDone!: () => void;\n this.querySubmissionChain = new Promise<void>((resolve) => {\n signalDone = resolve;\n });\n\n const cbIndex = args.findIndex((arg) => typeof arg === 'function');\n if (cbIndex !== -1) {\n const origCb = args[cbIndex] as (err: unknown, res: unknown) => void;\n args[cbIndex] = (err: unknown, res: unknown) => {\n signalDone();\n origCb(err, res);\n };\n prior.then(callSuper).catch((err) => {\n signalDone();\n origCb(err, undefined);\n });\n return undefined;\n }\n\n const p = prior.then(callSuper);\n p.then(signalDone, signalDone);\n return p;\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\n/**\n * Coordinates PGlite access across concurrent pool connections.\n *\n * @remarks\n * PGlite runs PostgreSQL in single-user mode — one session shared by all\n * bridges. The session lock tracks which bridge owns the session during\n * transactions, preventing interleaving. Used internally by {@link PGliteDuplex}\n * and created automatically by {@link createPool}. Only instantiate directly\n * if building a custom pool setup.\n */\nexport class SessionLock {\n private owner?: BridgeId;\n private waitQueue: Array<{ id: BridgeId; resolve: () => void; reject: (error: Error) => void }> =\n [];\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 // Free slot or re-entrant — pass through\n if (this.owner === undefined || this.owner === id) return;\n\n // Another bridge owns the session — wait\n return new Promise<void>((resolve, reject) => {\n this.waitQueue.push({\n id,\n resolve,\n reject,\n });\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 * @returns `true` if ownership transitioned on this call (acquired or\n * released). `false` for no-op updates (e.g., re-entrant status within\n * the same transaction, or IDLE from a non-owning bridge).\n */\n updateStatus(id: BridgeId, status: number): boolean {\n if (status === STATUS_IN_TRANSACTION || status === STATUS_FAILED) {\n if (this.owner === id) return false;\n this.owner = id;\n return true;\n }\n\n // Transaction complete — release ownership\n if (status === STATUS_IDLE && this.owner === id) {\n this.owner = undefined;\n this.drainWaitQueue();\n return true;\n }\n\n return false;\n }\n\n /**\n * Release ownership (e.g., when a bridge is destroyed mid-transaction).\n *\n * @returns `true` if this bridge held ownership and released it. `false`\n * if another bridge (or no one) owned the session.\n */\n release(id: BridgeId): boolean {\n if (this.owner === id) {\n this.owner = undefined;\n this.drainWaitQueue();\n return true;\n }\n\n return false;\n }\n\n /**\n * Cancel this bridge's pending or active claim on the session.\n *\n * Used when a bridge is torn down while blocked in `acquire()` so it cannot\n * later be granted ownership after destruction.\n */\n cancel(id: BridgeId, error: Error = new Error('Session lock acquire cancelled')): boolean {\n let cancelled = false;\n\n if (this.owner === id) {\n this.owner = undefined;\n this.drainWaitQueue();\n cancelled = true;\n }\n\n const remaining: typeof this.waitQueue = [];\n for (const waiter of this.waitQueue) {\n if (waiter.id === id) {\n waiter.reject(error);\n cancelled = true;\n } else {\n remaining.push(waiter);\n }\n }\n this.waitQueue = remaining;\n\n return cancelled;\n }\n\n /**\n * Grant ownership to the next waiter, if any.\n *\n * @returns `true` if a waiter was unblocked; `false` if the queue was empty.\n */\n private drainWaitQueue(): boolean {\n // Release one waiter at a time and grant ownership before resolving.\n // The waiter's operation will call updateStatus when it completes —\n // if IDLE, ownership is cleared and the next waiter is released.\n // This prevents interleaving where multiple waiters race past acquire\n // and one starts a transaction while others proceed unserialized.\n const next = this.waitQueue.shift();\n if (!next) return false;\n\n this.owner = next.id;\n next.resolve();\n return true;\n }\n}\n","/**\n * Pool factory — creates a pg.Pool backed by a caller-supplied PGlite instance.\n *\n * Each pool connection gets its own PGliteDuplex stream, all sharing the\n * same PGlite WASM instance. Pools with multiple connections also share a\n * SessionLock. The session lock ensures transaction isolation: when one\n * bridge starts a transaction (BEGIN), it gets exclusive PGlite access until\n * COMMIT/ROLLBACK. Non-transactional operations from any bridge serialize\n * through PGlite's runExclusive mutex.\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { BridgeClient, type BridgePoolConfig, bridgeClientOptionsKey } from './bridge-client.ts';\nimport type { TelemetrySink } from './utils/bridge-stats.ts';\nimport { SessionLock } from './utils/session-lock.ts';\n\nexport type SyncToFsMode = 'auto' | boolean;\n\nconst resolveSyncToFs = (pglite: PGlite, mode: SyncToFsMode | undefined): boolean => {\n if (mode === true || mode === false) return mode;\n\n const dataDir = pglite.dataDir;\n return !(dataDir === undefined || dataDir === '' || dataDir.startsWith('memory://'));\n};\n\nexport interface CreatePoolOptions {\n /** PGlite instance to bridge to. The caller owns its lifecycle. */\n pglite: PGlite;\n\n /**\n * Maximum pool connections (default: 1). Compatibility knob, not a\n * throughput knob.\n *\n * PGlite's WASM runtime executes queries serially behind a single mutex.\n * Raising `max` above 1 therefore does not add parallelism — queries still\n * run one at a time — and each extra connection costs a full `PGliteDuplex`\n * (its framers and scratch buffers) plus shared session-lock coordination\n * in memory. Leave this at `1` unless your code specifically needs to check\n * out multiple `pg` clients or you are deliberately exercising wait-queue\n * behaviour in a test.\n */\n max?: number;\n\n /**\n * Identity tag published with every diagnostics-channel event. Subscribers\n * filter on this to distinguish events from different bridges in the\n * same process. A fresh `Symbol('bridge')` is generated if omitted.\n */\n bridgeId?: symbol;\n\n /**\n * Filesystem sync policy for bridge-driven wire-protocol calls.\n *\n * - `'auto'` (default): disable per-query sync for clearly in-memory PGlite\n * instances, keep it enabled otherwise.\n * - `true`: always sync before the bridge returns a query result.\n * - `false`: never sync on bridge protocol calls; fastest, but weaker durability.\n *\n * `auto` uses `pglite.dataDir` as a heuristic. If you provide a custom\n * persistent `fs` without a meaningful `dataDir`, pass `true` explicitly.\n */\n syncToFs?: SyncToFsMode;\n\n telemetry?: TelemetrySink;\n}\n\nexport interface PoolResult {\n /** pg.Pool backed by PGlite — pass to PrismaPg */\n pool: pg.Pool;\n\n /**\n * Identity tag carried on every `QUERY_CHANNEL` / `LOCK_WAIT_CHANNEL`\n * event this pool produces. Matches the `bridgeId` option if supplied,\n * otherwise a freshly minted symbol. Filter on it from external\n * subscribers to isolate this pool's events.\n */\n bridgeId: symbol;\n\n /** Shut down the pool. Does not close the caller-owned PGlite instance. */\n close: () => Promise<void>;\n}\n\n/**\n * Creates a pg.Pool where every connection is an in-process PGlite bridge.\n *\n * Most users should prefer {@link createPGliteBridge}, which wraps this\n * function and also handles schema application and reset/snapshot lifecycle.\n *\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPool } from 'prisma-pglite-bridge';\n * import { PrismaPg } from '@prisma/adapter-pg';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const { pool, close } = await createPool({ pglite });\n * const adapter = new PrismaPg(pool);\n * const prisma = new PrismaClient({ adapter });\n * ```\n *\n * @see {@link createPGliteBridge} for the higher-level API with schema management.\n */\nexport const createPool = async (options: CreatePoolOptions): Promise<PoolResult> => {\n const { pglite, max = 1, telemetry } = options;\n const bridgeId = options.bridgeId ?? Symbol('bridge');\n const syncToFs = resolveSyncToFs(pglite, options.syncToFs);\n\n await pglite.waitReady;\n\n const sessionLock = max > 1 ? new SessionLock() : undefined;\n\n const poolConfig: BridgePoolConfig = {\n Client: BridgeClient,\n max,\n [bridgeClientOptionsKey]: {\n pglite,\n sessionLock,\n bridgeId,\n telemetry,\n syncToFs,\n },\n };\n const pool = new pg.Pool(poolConfig);\n const close = () => pool.end();\n\n return { pool, bridgeId, close };\n};\n","/**\n * Private per-bridge stats state used to power `stats()`.\n *\n * Instantiated at level `'basic'` or `'full'` (level `'off'` means no stats\n * object exists).\n * Query-level timing is recorded directly by the bridge; one-shot lifecycle\n * signals (`incrementResetDb`, `freeze`) remain direct method calls invoked\n * by the bridge itself.\n *\n * Percentiles use nearest-rank (no interpolation) over a sliding window of\n * the most recent {@link QUERY_DURATION_WINDOW_SIZE} queries. Lifetime\n * counters (`queryCount`, `totalQueryMs`, `avgQueryMs`) are not windowed.\n * `durationMs` is frozen at the instant `close()` was invoked, via the\n * `closeEntryHrtime` the bridge records as its very first action and\n * passes to {@link freeze}.\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport { nsToMs } from './time.ts';\n\ntype DbSizeQueryable = Pick<PGlite, 'query'>;\n\n/**\n * Stats collection level.\n *\n * - `'off'` — `stats()` returns `undefined`. Zero hot-path overhead.\n * - `'basic'` — timing (`durationMs`), query percentiles, counters, and\n * `dbSizeBytes`.\n * - `'full'` — `'basic'` plus `processRssPeakBytes` and session-lock\n * waits.\n */\nexport type StatsLevel = 'off' | 'basic' | 'full';\n\n/** Internal bridge-facing telemetry contract. */\nexport interface TelemetrySink {\n recordQuery(durationMs: number, succeeded: boolean): void;\n recordLockWait(durationMs: number): void;\n}\n\n/**\n * Maximum number of recent query durations retained for percentile\n * computation. Beyond this window, `recentP50QueryMs`, `recentP95QueryMs`,\n * and `recentMaxQueryMs` reflect only the most recent N queries — lifetime\n * counters (`queryCount`, `totalQueryMs`, `avgQueryMs`) remain complete.\n */\nexport const QUERY_DURATION_WINDOW_SIZE = 10_000;\n\nconst QUERY_DURATION_TRIM_THRESHOLD = QUERY_DURATION_WINDOW_SIZE * 2;\n\ninterface StatsBase {\n durationMs: number;\n /** Lifetime count of recorded queries. Not windowed. */\n queryCount: number;\n failedQueryCount: number;\n /** Lifetime sum of query durations. Not windowed. */\n totalQueryMs: number;\n /** Lifetime mean query duration. Not windowed. */\n avgQueryMs: number;\n /**\n * Nearest-rank 50th percentile over the most recent\n * {@link QUERY_DURATION_WINDOW_SIZE} queries. Compare to `avgQueryMs`\n * with care: the two fields describe different populations on long-lived\n * bridges.\n */\n recentP50QueryMs: number;\n /** Nearest-rank 95th percentile over the recent-query window. */\n recentP95QueryMs: number;\n /** Maximum query duration within the recent-query window. */\n recentMaxQueryMs: number;\n resetDbCalls: number;\n /** Undefined only when the `pg_database_size` query rejected. */\n dbSizeBytes?: number;\n}\n\nexport interface StatsBasic extends StatsBase {\n statsLevel: 'basic';\n}\n\nexport interface StatsFull extends StatsBase {\n statsLevel: 'full';\n /**\n * Process-wide RSS high-water mark since process start, read from\n * `process.resourceUsage().maxRSS` (kernel-tracked, lossless). Reflects\n * the entire Node process — parallel test runners, other bridges, and\n * prior work in the same process all contribute. Use only as an\n * ordering signal, not an absolute measurement.\n *\n * `undefined` on runtimes that don't expose `process.resourceUsage`\n * (e.g. Bun, Deno, edge workers) — matches the field-level-undefined\n * contract of every other `Stats` member.\n */\n processRssPeakBytes: number | undefined;\n totalSessionLockWaitMs: number;\n sessionLockAcquisitionCount: number;\n avgSessionLockWaitMs: number;\n maxSessionLockWaitMs: number;\n}\n\nexport type Stats = StatsBasic | StatsFull;\n\nconst DB_SIZE_QUERY_TIMEOUT_MS = 5_000;\n\n/**\n * `process.resourceUsage().maxRSS` returns kilobytes on every platform\n * Node supports — we convert to bytes so the public `processRssPeakBytes`\n * field matches its name. Returns `undefined` on runtimes that don't\n * expose `resourceUsage` (Bun, Deno, edge workers).\n */\nconst readProcessRssPeakBytes = (): number | undefined => {\n try {\n return process.resourceUsage().maxRSS * 1024;\n } catch {\n return undefined;\n }\n};\n\nconst percentile = (sorted: readonly number[], p: number): number => {\n const n = sorted.length;\n if (n === 0) return 0;\n const index = Math.min(n - 1, Math.max(0, Math.ceil((p / 100) * n) - 1));\n /* c8 ignore next */\n return sorted[index] ?? 0;\n};\n\nexport class BridgeStats implements TelemetrySink {\n private readonly level: 'basic' | 'full';\n private readonly createdAtHrtime: bigint;\n\n private queryDurations: number[] = [];\n private totalQueryMs = 0;\n private queryCount = 0;\n private failedQueryCount = 0;\n private resetDbCalls = 0;\n\n private totalSessionLockWaitMs = 0;\n private maxSessionLockWaitMs = 0;\n private sessionLockAcquisitionCount = 0;\n\n private frozen = false;\n private cachedDurationMs?: number;\n private cachedDbSizeBytes?: number;\n private dbSizeFrozen = false;\n\n constructor(level: 'basic' | 'full') {\n this.level = level;\n this.createdAtHrtime = process.hrtime.bigint();\n }\n\n recordQuery(durationMs: number, succeeded: boolean): void {\n if (this.frozen) return;\n this.queryCount += 1;\n this.totalQueryMs += durationMs;\n this.queryDurations.push(durationMs);\n if (this.queryDurations.length > QUERY_DURATION_TRIM_THRESHOLD) {\n this.queryDurations = this.queryDurations.slice(-QUERY_DURATION_WINDOW_SIZE);\n }\n if (!succeeded) this.failedQueryCount += 1;\n }\n\n recordLockWait(durationMs: number): void {\n if (this.frozen) return;\n if (this.level !== 'full') return;\n this.totalSessionLockWaitMs += durationMs;\n this.sessionLockAcquisitionCount += 1;\n if (durationMs > this.maxSessionLockWaitMs) this.maxSessionLockWaitMs = durationMs;\n }\n\n incrementResetDb(): void {\n if (this.frozen) return;\n this.resetDbCalls += 1;\n }\n\n async snapshot(pglite: DbSizeQueryable): Promise<Stats> {\n const durationMs =\n this.cachedDurationMs ?? nsToMs(process.hrtime.bigint() - this.createdAtHrtime);\n const dbSizeBytes = this.dbSizeFrozen ? this.cachedDbSizeBytes : await this.queryDbSize(pglite);\n\n const sorted = [...this.queryDurations].sort((a, b) => a - b);\n const avgQueryMs = this.queryCount === 0 ? 0 : this.totalQueryMs / this.queryCount;\n\n const base: StatsBase = {\n durationMs,\n queryCount: this.queryCount,\n failedQueryCount: this.failedQueryCount,\n totalQueryMs: this.totalQueryMs,\n avgQueryMs,\n recentP50QueryMs: percentile(sorted, 50),\n recentP95QueryMs: percentile(sorted, 95),\n recentMaxQueryMs: percentile(sorted, 100),\n resetDbCalls: this.resetDbCalls,\n dbSizeBytes,\n };\n\n if (this.level === 'basic') {\n return { ...base, statsLevel: 'basic' };\n }\n\n const count = this.sessionLockAcquisitionCount;\n return {\n ...base,\n statsLevel: 'full',\n processRssPeakBytes: readProcessRssPeakBytes(),\n totalSessionLockWaitMs: this.totalSessionLockWaitMs,\n sessionLockAcquisitionCount: count,\n avgSessionLockWaitMs: count === 0 ? 0 : this.totalSessionLockWaitMs / count,\n maxSessionLockWaitMs: this.maxSessionLockWaitMs,\n };\n }\n\n async freeze(pglite: DbSizeQueryable, closeEntryHrtime: bigint): Promise<void> {\n if (this.frozen) return;\n this.frozen = true;\n\n this.cachedDurationMs = nsToMs(closeEntryHrtime - this.createdAtHrtime);\n try {\n this.cachedDbSizeBytes = await this.queryDbSize(pglite);\n } finally {\n this.dbSizeFrozen = true;\n }\n }\n\n private async queryDbSize(pglite: DbSizeQueryable): Promise<number | undefined> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error('pg_database_size query timed out')),\n DB_SIZE_QUERY_TIMEOUT_MS,\n );\n timer.unref?.();\n });\n const { rows } = await Promise.race([\n pglite.query<{ size: string | null }>(\n 'SELECT pg_database_size(current_database())::text AS size',\n ),\n timeout,\n ]);\n const size = rows[0]?.size;\n if (size == null) return undefined;\n const n = Number(size);\n return Number.isFinite(n) ? n : undefined;\n } catch {\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","import type { PGlite } from '@electric-sql/pglite';\n\nconst SNAPSHOT_SCHEMA = '_pglite_snapshot';\n\nconst USER_TABLES_WHERE = `schemaname NOT IN ('pg_catalog', 'information_schema')\n AND schemaname != '${SNAPSHOT_SCHEMA}'\n AND tablename NOT LIKE '_prisma%'`;\n\nconst escapeLiteral = (s: string) => `'${s.replace(/'/g, \"''\")}'`;\n\n/** JS equivalent of PostgreSQL's `quote_ident()`; matches its escaping rules. */\nconst quoteIdent = (identifier: string): string => `\"${identifier.replace(/\"/g, '\"\"')}\"`;\n\nconst SNAPSHOT_SCHEMA_IDENT = quoteIdent(SNAPSHOT_SCHEMA);\n\ninterface SnapshotManager {\n /**\n * Truncate all user tables. If a snapshot exists, restore its contents and\n * sequence values afterwards; otherwise just truncate and `DISCARD ALL`.\n */\n resetDb: () => Promise<void>;\n /** Drop the saved snapshot, reverting `resetDb` to plain truncation. */\n resetSnapshot: () => Promise<void>;\n /**\n * Capture the current state of all user tables plus sequence values into\n * the `_pglite_snapshot` schema. Replaces any previous snapshot.\n */\n snapshotDb: () => Promise<void>;\n}\n\n/**\n * Snapshot helpers backing `createPGliteBridge`'s `snapshotDb` / `resetDb` /\n * `resetSnapshot` functions. Stores a copy of user tables and sequence\n * values in a dedicated `_pglite_snapshot` schema so tests can reset to a\n * known seed state without re-running migrations.\n *\n * @internal\n */\nexport const createSnapshotManager = (pglite: PGlite): SnapshotManager => {\n let hasSnapshot = false;\n\n const getTables = async () => {\n const { rows } = await pglite.query<{ qualified: string }>(\n `SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified\n FROM pg_tables\n WHERE ${USER_TABLES_WHERE}`,\n );\n return rows.length > 0\n ? rows.map((row: { qualified: string }) => row.qualified).join(', ')\n : '';\n };\n\n const snapshotDb = async () => {\n await pglite.exec(`DROP SCHEMA IF EXISTS ${SNAPSHOT_SCHEMA_IDENT} CASCADE`);\n\n try {\n await pglite.exec('BEGIN');\n await pglite.exec(`CREATE SCHEMA ${SNAPSHOT_SCHEMA_IDENT}`);\n\n const { rows: tables } = await pglite.query<{\n schemaname: string;\n tablename: string;\n qualified: string;\n }>(\n `SELECT schemaname, tablename,\n quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified\n FROM pg_tables\n WHERE ${USER_TABLES_WHERE}`,\n );\n\n await pglite.exec(\n `CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.__tables (snap_name text, source_schema text, source_table text)`,\n );\n\n for (const [i, { schemaname, tablename, qualified }] of tables.entries()) {\n const snapName = `_snap_${i}`;\n await pglite.exec(\n `CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.${quoteIdent(snapName)} AS SELECT * FROM ${qualified}`,\n );\n await pglite.exec(\n `INSERT INTO ${SNAPSHOT_SCHEMA_IDENT}.__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`,\n );\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(schemaname || '.' || sequencename) AS name, last_value::text AS value\n FROM pg_sequences\n WHERE schemaname NOT IN ('pg_catalog', 'information_schema')\n AND schemaname != '${SNAPSHOT_SCHEMA}'\n AND last_value IS NOT NULL`,\n );\n\n await pglite.exec(\n `CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.__sequences (name text, value bigint)`,\n );\n for (const { name, value } of seqs) {\n await pglite.exec(\n `INSERT INTO ${SNAPSHOT_SCHEMA_IDENT}.__sequences VALUES (${name}, ${value})`,\n );\n }\n\n await pglite.exec('COMMIT');\n } catch (err) {\n await pglite.exec('ROLLBACK');\n await pglite.exec(`DROP SCHEMA IF EXISTS ${SNAPSHOT_SCHEMA_IDENT} CASCADE`);\n throw err;\n }\n\n hasSnapshot = true;\n };\n\n const resetSnapshot = async () => {\n hasSnapshot = false;\n await pglite.exec(`DROP SCHEMA IF EXISTS ${SNAPSHOT_SCHEMA_IDENT} CASCADE`);\n };\n\n const withReplicationRoleReplica = async (fn: () => Promise<void>) => {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await fn();\n } finally {\n await pglite.exec('SET session_replication_role = DEFAULT');\n }\n };\n\n const resetDb = async () => {\n const tables = await getTables();\n\n if (tables) {\n await withReplicationRoleReplica(async () => {\n await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);\n\n if (!hasSnapshot) return;\n\n const { rows: snapshotTables } = await pglite.query<{\n snap_name_ident: string;\n qualified: string;\n }>(\n `SELECT quote_ident(snap_name) AS snap_name_ident,\n quote_ident(source_schema) || '.' || quote_ident(source_table) AS qualified\n FROM ${SNAPSHOT_SCHEMA_IDENT}.__tables`,\n );\n\n for (const { snap_name_ident, qualified } of snapshotTables) {\n await pglite.exec(\n `INSERT INTO ${qualified} SELECT * FROM ${SNAPSHOT_SCHEMA_IDENT}.${snap_name_ident}`,\n );\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(name) AS name, value::text AS value FROM ${SNAPSHOT_SCHEMA_IDENT}.__sequences`,\n );\n\n for (const { name, value } of seqs) {\n await pglite.exec(`SELECT setval(${name}, ${value})`);\n }\n });\n }\n\n await pglite.exec('DISCARD ALL');\n };\n\n return { resetDb, resetSnapshot, snapshotDb };\n};\n","/**\n * Creates a PGliteBridge: a Prisma adapter, the underlying PGlite instance,\n * and lifecycle helpers, all backed by a caller-supplied PGlite instance.\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 { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushMigrations } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n * await pushMigrations(bridge, { migrationsPath: './prisma/migrations' });\n *\n * const prisma = new PrismaClient({ adapter: bridge.adapter });\n * beforeEach(() => bridge.resetDb());\n * ```\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { createPool, type SyncToFsMode } from './create-pool.ts';\nimport { BridgeStats, type Stats, type StatsLevel } from './utils/bridge-stats.ts';\nimport { createSnapshotManager } from './utils/snapshot.ts';\n\n/** @internal Exported for testing. */\nexport const emitBridgeLeakWarning = (): void => {\n process.emitWarning(\n 'PGliteBridge was garbage-collected before close() was called. ' +\n 'Call bridge.close() to release the pool and finalize stats().',\n { type: 'PGliteBridgeLeakWarning' },\n );\n};\n\nconst leakRegistry = new FinalizationRegistry<void>(emitBridgeLeakWarning);\n\nexport interface CreatePGliteBridgeOptions {\n /**\n * PGlite instance to bridge to. The caller owns its lifecycle — `close()`\n * shuts down the pool only, not the PGlite instance.\n */\n pglite: PGlite;\n\n /**\n * Maximum pool connections (default: 1). Compatibility knob, not a\n * throughput knob.\n *\n * PGlite serialises queries inside its WASM runtime. Extra pool connections\n * do not add parallelism; they only add bridge/client memory and\n * session-lock coordination. Leave this at `1` unless the code under test\n * specifically needs multiple checked-out `pg` clients.\n */\n max?: number;\n\n /**\n * Collect bridge/query telemetry. Default `'off'` (zero overhead).\n *\n * - `'basic'` — timing (`durationMs`, query percentiles) and counters\n * (`queryCount`, `failedQueryCount`, `resetDbCalls`), plus\n * `dbSizeBytes`.\n * - `'full'` — everything in `'basic'`, plus `processRssPeakBytes`\n * (process-wide, sampled) and session-lock wait statistics.\n *\n * Retrieve via `await bridge.stats()` — returns `undefined` at `'off'`.\n */\n statsLevel?: StatsLevel;\n\n /**\n * Filesystem sync policy for bridge-driven wire-protocol calls.\n *\n * Default `'auto'`: disables per-query sync for clearly in-memory PGlite\n * instances and keeps it enabled otherwise. Set `true` to prefer durability\n * on persistent stores, or `false` to prefer lower RSS / higher throughput.\n *\n * If you provide a custom persistent PGlite `fs` without a meaningful\n * `dataDir`, pass `true` explicitly.\n */\n syncToFs?: SyncToFsMode;\n}\n\n/** Snapshot of bridge/query telemetry. See {@link CreatePGliteBridgeOptions.statsLevel}. */\nexport type StatsFn = () => Promise<Stats | undefined>;\n\n/** Clear all user tables and discard session-local state. 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 type CloseFn = () => Promise<void>;\n\nexport interface PGliteBridge {\n /** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */\n adapter: PrismaPg;\n\n /**\n * The caller-supplied PGlite instance this bridge wraps. Exposed so\n * helpers like {@link pushMigrations} can run SQL directly through\n * `pglite.exec(...)` without going through the bridge pool.\n */\n pglite: PGlite;\n\n /**\n * Identity tag published on every `QUERY_CHANNEL` / `LOCK_WAIT_CHANNEL`\n * diagnostics event produced by this bridge. External subscribers\n * filter on it to isolate events from this bridge in multi-bridge\n * processes.\n */\n bridgeId: symbol;\n\n /** Clear all user tables and discard session-local state. Call in `beforeEach` for per-test isolation. */\n resetDb: ResetDbFn;\n\n /**\n * Snapshot the current DB state into an internal `_pglite_snapshot`\n * schema. Subsequent `resetDb` calls restore from this snapshot instead\n * of truncating to empty.\n *\n * **Concurrency:** runs multiple `exec()` statements directly against\n * the PGlite instance, bypassing the pool's `SessionLock`. Call from a\n * test `beforeAll` after migrations but before Prisma traffic starts;\n * invoking it while another pool connection is inside a transaction is\n * unsafe and may deadlock against PGlite's internal mutex.\n */\n snapshotDb: SnapshotDbFn;\n\n /**\n * Discard the current snapshot. Subsequent `resetDb` calls truncate to\n * empty. Same concurrency requirements as {@link snapshotDb}.\n */\n resetSnapshot: ResetSnapshotFn;\n\n /**\n * Shut down the pool. The caller-owned PGlite instance is not closed.\n *\n * When `statsLevel` is not `'off'`, call `stats()` *after* `close()` to\n * collect the frozen snapshot — `durationMs` and `dbSizeBytes` are cached\n * at the moment `close()` is invoked, and subsequent `stats()` calls are\n * safe.\n */\n close: CloseFn;\n\n /**\n * Retrieve collected telemetry. Returns `undefined` when `statsLevel` was\n * `'off'` (or omitted). Never throws — field-level failures surface as\n * `undefined` values (see {@link Stats}).\n */\n stats: StatsFn;\n}\n\n/**\n * Creates a `PGliteBridge` — a bundle holding a Prisma driver adapter,\n * the underlying PGlite instance, and lifecycle helpers — backed by a\n * caller-supplied PGlite instance.\n *\n * Schema application is a separate concern — call {@link pushMigrations}\n * (raw SQL / migrations directory) or {@link pushSchema} (WASM-engine\n * diff) before issuing Prisma traffic. When reopening a persistent\n * `dataDir`, the PGlite instance is assumed to already hold the schema\n * and no migration step is required.\n */\nexport const createPGliteBridge = async (\n options: CreatePGliteBridgeOptions,\n): Promise<PGliteBridge> => {\n const statsLevel = options.statsLevel ?? 'off';\n if (statsLevel !== 'off' && statsLevel !== 'basic' && statsLevel !== 'full') {\n throw new Error(`statsLevel must be 'off', 'basic', or 'full'; got ${String(statsLevel)}`);\n }\n const bridgeId = Symbol('bridge');\n const bridgeStats = statsLevel === 'off' ? undefined : new BridgeStats(statsLevel);\n\n const { pglite } = options;\n\n const { pool } = await createPool({\n pglite,\n max: options.max,\n bridgeId,\n syncToFs: options.syncToFs,\n telemetry: bridgeStats,\n });\n\n const adapter = new PrismaPg(pool);\n const snapshotManager = createSnapshotManager(pglite);\n\n const resetDb: ResetDbFn = async () => {\n bridgeStats?.incrementResetDb();\n await snapshotManager.resetDb();\n };\n\n const leakToken: object = {};\n\n let closing: Promise<void> | undefined;\n const close: CloseFn = async () => {\n if (!closing) {\n closing = (async () => {\n const closeEntry = bridgeStats ? process.hrtime.bigint() : undefined;\n await pool.end();\n if (bridgeStats && closeEntry !== undefined) {\n await bridgeStats.freeze(pglite, closeEntry);\n }\n leakRegistry.unregister(leakToken);\n })();\n }\n return closing;\n };\n\n const result: PGliteBridge = {\n adapter,\n bridgeId,\n pglite,\n close,\n resetDb,\n resetSnapshot: snapshotManager.resetSnapshot,\n snapshotDb: snapshotManager.snapshotDb,\n stats: async () => (bridgeStats ? bridgeStats.snapshot(pglite) : undefined),\n };\n\n // Track the lifetime of the Prisma adapter instance users actually retain.\n // The wrapper object returned by createPGliteBridge() is often ephemeral\n // (`const adapter = (await createPGliteBridge(...)).adapter`), so\n // registering that wrapper causes false leak warnings while Prisma still\n // holds the live adapter and pool.\n leakRegistry.register(adapter, undefined, leakToken);\n\n return result;\n};\n","/**\n * Apply pre-generated SQL (raw or from `prisma/migrations/`) to a PGlite\n * database. Sibling of {@link pushSchema} — same target shape, but no\n * schema engine and no `@prisma/schema-engine-wasm` import. Use when you\n * already have generated SQL and don't need a live schema diff.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushMigrations } from 'prisma-pglite-bridge';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n * await pushMigrations(bridge, { migrationsPath: './prisma/migrations' });\n * ```\n */\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport type { PGliteBridge } from './create-pglite-bridge.ts';\nimport type { SchemaTarget } from './schema.ts';\n\nexport interface PushMigrationsOptions {\n /** Pre-generated SQL to apply directly. */\n sql?: string;\n /** Path to a `prisma/migrations/` directory (auto-discovered via prisma.config.ts if omitted). */\n migrationsPath?: string;\n /** Root for prisma.config.ts discovery (default: process.cwd()). Set in monorepos where tests run from the workspace root. */\n configRoot?: string;\n}\n\nexport interface PushMigrationsResult {\n /** Wall-clock time of the SQL apply, in milliseconds. */\n durationMs: number;\n}\n\n/**\n * Resolve the migrations directory via Prisma's config API. Uses the same\n * resolution as `prisma migrate dev` — reads prisma.config.ts and resolves\n * paths relative to the config file's location.\n *\n * Returns undefined if @prisma/config is not available or the config\n * cannot be loaded.\n */\nexport const getMigrationsPath = async (configRoot?: string): Promise<string | undefined> => {\n try {\n const { loadConfigFromFile } = await import('@prisma/config');\n const { config, error } = await loadConfigFromFile({ configRoot: configRoot ?? process.cwd() });\n if (error) return undefined;\n\n if (config.migrations?.path) return config.migrations.path;\n\n const schemaPath = config.schema;\n if (schemaPath) return join(dirname(schemaPath), 'migrations');\n\n return undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Read and concatenate every `migration.sql` under a migrations directory in\n * directory order. Returns undefined if the directory doesn't exist or has no\n * migration files.\n */\nexport const readMigrationFiles = (migrationsPath: string): string | undefined => {\n if (!existsSync(migrationsPath)) return undefined;\n\n const dirs = readdirSync(migrationsPath)\n .filter((directory) => statSync(join(migrationsPath, directory)).isDirectory())\n .sort();\n\n const sqlParts: string[] = [];\n for (const directory of dirs) {\n const sqlPath = join(migrationsPath, directory, 'migration.sql');\n if (existsSync(sqlPath)) {\n sqlParts.push(readFileSync(sqlPath, 'utf8'));\n }\n }\n\n return sqlParts.length > 0 ? sqlParts.join('\\n') : undefined;\n};\n\n/**\n * Resolve schema SQL from {@link PushMigrationsOptions}. Priority:\n * 1. Explicit `sql`\n * 2. Explicit `migrationsPath` — read migration files\n * 3. Auto-discovered migrations via prisma.config.ts\n * 4. Throw — tell the caller to generate migration files\n */\nexport const getMigrationSQL = async (options: PushMigrationsOptions): Promise<string> => {\n if (options.sql) return options.sql;\n\n if (options.migrationsPath) {\n const sql = readMigrationFiles(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 const migrationsPath = await getMigrationsPath(options.configRoot);\n if (migrationsPath) {\n const sql = readMigrationFiles(migrationsPath);\n if (sql) return sql;\n\n throw new Error(\n `No migration.sql files found in auto-discovered path ${migrationsPath}. ` +\n 'Run `prisma migrate dev` to generate migration files, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n }\n\n if (options.configRoot) {\n throw new Error(\n `prisma.config.ts loaded from configRoot (${options.configRoot}) but no schema ` +\n 'or migrations path could be resolved. Ensure your config specifies a schema path, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n }\n\n throw new Error(\n 'No migration files found and no prisma.config.ts could be loaded. ' +\n 'Run `prisma migrate dev` to generate them, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n};\n\nconst isPGliteBridge = (target: SchemaTarget): target is PGliteBridge =>\n 'pglite' in target && 'adapter' in target;\n\n/**\n * Apply pre-generated SQL to the PGlite instance backing `target`.\n *\n * Runs the SQL through `pglite.exec(...)` directly, bypassing the bridge\n * pool. No schema engine, no WASM module, no diffing.\n *\n * Currently only {@link PGliteBridge} targets are supported. Raw `PrismaPg`\n * targets have no PGlite reference; pass the {@link PGliteBridge} returned\n * by {@link createPGliteBridge} instead.\n */\nexport const pushMigrations = async (\n target: SchemaTarget,\n options: PushMigrationsOptions = {},\n): Promise<PushMigrationsResult> => {\n if (!isPGliteBridge(target)) {\n throw new Error(\n 'pushMigrations requires a PGliteBridge target. Raw PrismaPg targets have no PGlite reference; pass the bridge returned by createPGliteBridge().',\n );\n }\n const sql = await getMigrationSQL(options);\n const start = process.hrtime.bigint();\n try {\n await target.pglite.exec(sql);\n } catch (err) {\n const dataDir = (target.pglite as { dataDir?: string }).dataDir;\n const where = dataDir ? `PGlite(dataDir=${dataDir})` : 'in-memory PGlite';\n throw new Error(\n `Failed to apply schema SQL to ${where}. Check your schema or migration files.`,\n { cause: err },\n );\n }\n return { durationMs: Number(process.hrtime.bigint() - start) / 1e6 };\n};\n","/**\n * Apply a Prisma schema to a PGlite database in-process via\n * `@prisma/schema-engine-wasm`. No native schema-engine binary, no TCP.\n *\n * `pushSchema` mirrors `prisma db push --skip-generate`: diff the schema\n * against the live database and apply the result. `resetSchema` drops\n * everything reachable through the connection.\n *\n * The schema engine WASM module is dynamically imported so consumers\n * who only use the bridge never load it.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushSchema } from 'prisma-pglite-bridge';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n *\n * await pushSchema(bridge, {\n * schema: await fs.readFile('prisma/schema.prisma', 'utf8'),\n * });\n * ```\n */\nimport type { PrismaPg } from '@prisma/adapter-pg';\n\nimport type { PGliteBridge } from './create-pglite-bridge.ts';\n\n/** A schema-apply target. Either a {@link PGliteBridge} from {@link createPGliteBridge} or a raw {@link PrismaPg}. */\nexport type SchemaTarget = PGliteBridge | PrismaPg;\n\nexport interface PushSchemaOptions {\n /** Inline Prisma schema source. */\n schema: string;\n /**\n * Drop everything reachable through the adapter before applying.\n * Issued as a separate `engine.reset(...)` call. Distinct from\n * {@link acceptDataLoss}. Default: `false`.\n */\n forceReset?: boolean;\n /**\n * Forwarded to `SchemaPushInput.force`. Apply the schema even when\n * the engine reports destructive-change warnings. Has no effect on\n * `unexecutable` steps. Default: `false`.\n */\n acceptDataLoss?: boolean;\n /** Logical filename used in schema-engine error messages. Default: `'schema.prisma'`. */\n filename?: string;\n}\n\nexport interface PushSchemaResult {\n /** Number of migration steps the engine applied. */\n executedSteps: number;\n /**\n * Destructive-change warnings reported by the engine. Suppressed\n * (i.e. applied anyway) when {@link PushSchemaOptions.acceptDataLoss} is true.\n */\n warnings: string[];\n /**\n * Steps the engine refused to run regardless of `acceptDataLoss`.\n * The caller must reshape the schema (e.g. add a default value) and retry.\n */\n unexecutable: string[];\n}\n\nconst unwrap = (target: SchemaTarget): PrismaPg => ('adapter' in target ? target.adapter : target);\n\nconst bindAdapter = async (target: SchemaTarget): Promise<object> => {\n const { bindMigrationAwareSqlAdapterFactory } = await import('@prisma/driver-adapter-utils');\n return bindMigrationAwareSqlAdapterFactory(unwrap(target));\n};\n\nconst emptyFilter = (): { externalTables: string[]; externalEnums: string[] } => ({\n externalTables: [],\n externalEnums: [],\n});\n\nconst quoteIdent = (name: string): string => `\"${name.replace(/\"/g, '\"\"')}\"`;\n\n/**\n * Drop every non-system schema (and recreate `public`). Issued as raw SQL\n * through the adapter rather than `engine.reset(...)` because the engine only\n * clears schemas declared in its datamodel — anything outside that (`base`,\n * test fixtures, etc.) would otherwise leak between resets.\n *\n * Each DROP runs through `executeRaw` rather than a single `executeScript`,\n * because `executeScript` splits on `;` and would mis-parse schema names that\n * contain a semicolon.\n */\nconst dropAllUserSchemas = async (target: SchemaTarget): Promise<void> => {\n const factory = unwrap(target);\n const conn = await factory.connect();\n try {\n const result = await conn.queryRaw({\n sql: `SELECT nspname FROM pg_namespace\n WHERE nspname NOT LIKE 'pg_%' AND nspname <> 'information_schema'`,\n args: [],\n argTypes: [],\n });\n const idx = result.columnNames.indexOf('nspname');\n const names = result.rows.map((row) => String(row[idx]));\n for (const name of names) {\n await conn.executeRaw({\n sql: `DROP SCHEMA IF EXISTS ${quoteIdent(name)} CASCADE`,\n args: [],\n argTypes: [],\n });\n }\n await conn.executeRaw({\n sql: `CREATE SCHEMA IF NOT EXISTS \"public\"`,\n args: [],\n argTypes: [],\n });\n } finally {\n await conn.dispose();\n }\n // The wrapper's snapshot manager keeps in-memory state about whether\n // `_pglite_snapshot` exists. Dropping the schema out from under it would\n // leave `hasSnapshot = true` and the next `resetDb()` call would query a\n // missing schema. `resetSnapshot()` is idempotent — safe to call when no\n // snapshot was ever taken.\n if ('resetSnapshot' in target) {\n await target.resetSnapshot();\n }\n};\n\nexport const pushSchema = async (\n target: SchemaTarget,\n options: PushSchemaOptions,\n): Promise<PushSchemaResult> => {\n const filename = options.filename ?? 'schema.prisma';\n if (options.forceReset) {\n await dropAllUserSchemas(target);\n }\n const { SchemaEngine } = await import('@prisma/schema-engine-wasm');\n const bound = await bindAdapter(target);\n\n const engine = await SchemaEngine.new(\n { datamodels: [[filename, options.schema]] },\n () => {},\n bound,\n );\n try {\n const result = await engine.schemaPush({\n force: options.acceptDataLoss ?? false,\n schema: { files: [{ path: filename, content: options.schema }] },\n filters: emptyFilter(),\n });\n return {\n executedSteps: result.executedSteps,\n warnings: result.warnings,\n unexecutable: result.unexecutable,\n };\n } finally {\n engine.free();\n }\n};\n\nexport const resetSchema = async (target: SchemaTarget): Promise<void> => {\n await dropAllUserSchemas(target);\n};\n","/**\n * prisma-pglite-bridge — in-process PGlite bridge for Prisma.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushMigrations } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n * await pushMigrations(bridge, { migrationsPath: './prisma/migrations' });\n *\n * const prisma = new PrismaClient({ adapter: bridge.adapter });\n * ```\n *\n * @packageDocumentation\n */\n\n// ── High-level API (most users only need this) ──\nexport type {\n CloseFn,\n CreatePGliteBridgeOptions,\n PGliteBridge,\n ResetDbFn,\n ResetSnapshotFn,\n SnapshotDbFn,\n StatsFn,\n} from './create-pglite-bridge.ts';\nexport { createPGliteBridge } from './create-pglite-bridge.ts';\nexport type { PushMigrationsOptions, PushMigrationsResult } from './migrations.ts';\nexport { pushMigrations } from './migrations.ts';\nexport type { PushSchemaOptions, PushSchemaResult, SchemaTarget } from './schema.ts';\nexport { pushSchema, resetSchema } from './schema.ts';\n\n// ── Low-level building blocks ──\nimport {\n type CreatePoolOptions as CreateBasePoolOptions,\n createPool as createBasePool,\n type PoolResult,\n} from './create-pool.ts';\n\nexport type { PoolResult };\n\n/**\n * Options for {@link createPool}. Identical to the internal pool options,\n * minus the library-private `telemetry` sink (consumers subscribe via\n * `node:diagnostics_channel` instead — see {@link QUERY_CHANNEL} and\n * {@link LOCK_WAIT_CHANNEL}).\n */\nexport type CreatePoolOptions = Omit<CreateBasePoolOptions, 'telemetry'>;\nexport type { SyncToFsMode } from './create-pool.ts';\n\n/**\n * Build a `pg.Pool` backed by a caller-supplied PGlite instance. Each pool\n * connection bridges through its own {@link PGliteDuplex} stream while\n * sharing one PGlite WASM runtime and session lock.\n *\n * Use this low-level entry point when you want a raw `pg.Pool` (for example\n * to wire into `@prisma/adapter-pg` yourself). Most users should prefer\n * {@link createPGliteBridge}, which layers schema setup and reset helpers\n * on top.\n */\nexport const createPool = async (options: CreatePoolOptions): Promise<PoolResult> =>\n createBasePool(options);\nexport { PGliteDuplex } from './pglite-duplex.ts';\nexport type { Stats, StatsBasic, StatsFull, StatsLevel } from './utils/bridge-stats.ts';\n// ── Diagnostics channels (public observability surface) ──\nexport {\n LOCK_WAIT_CHANNEL,\n type LockWaitEvent,\n QUERY_CHANNEL,\n type QueryEvent,\n} from './utils/diagnostics.ts';\nexport { SessionLock } from './utils/session-lock.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["diagnostics_channel","Duplex","createPool","quoteIdent","createPool","PrismaPg","createBasePool"],"sources":["../src/utils/diagnostics.ts","../src/utils/time.ts","../src/pglite-duplex.ts","../src/bridge-client.ts","../src/utils/session-lock.ts","../src/create-pool.ts","../src/utils/bridge-stats.ts","../src/utils/snapshot.ts","../src/create-pglite-bridge.ts","../src/migrations.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["/**\n * Public `node:diagnostics_channel` surface.\n *\n * The bridge publishes per-query and per-lock-wait events to named\n * channels. External consumers (OpenTelemetry, APM tools, custom\n * loggers) can subscribe without touching the library API. Built-in\n * bridge stats are updated directly from the bridge and are\n * independent of these public channels.\n *\n * Publication is gated by `channel.hasSubscribers`, so the hot path\n * pays no cost when nobody is listening. Subscribing opts the consumer\n * in to the timing/publication overhead.\n *\n * Filter on `bridgeId` to distinguish events from different bridges\n * in the same process — obtain it from the `createPGliteBridge` or\n * `createPool` return value.\n */\nimport diagnostics_channel from 'node:diagnostics_channel';\n\n/**\n * `node:diagnostics_channel` name for per-query events. Subscribers receive\n * a {@link QueryEvent} each time the bridge completes a query.\n */\nexport const QUERY_CHANNEL = 'prisma-pglite-bridge:query';\n\n/**\n * `node:diagnostics_channel` name for per-acquisition session-lock wait\n * events. Subscribers receive a {@link LockWaitEvent} after each\n * acquisition completes.\n */\nexport const LOCK_WAIT_CHANNEL = 'prisma-pglite-bridge:lock-wait';\n\n/** Payload published to {@link QUERY_CHANNEL}. */\nexport interface QueryEvent {\n /** Bridge identity tag — filter on this to isolate one bridge's events. */\n bridgeId: symbol;\n /** Wall-clock duration of the query in milliseconds. */\n durationMs: number;\n /** `false` when the query rejected (protocol or SQL error). */\n succeeded: boolean;\n}\n\n/** Payload published to {@link LOCK_WAIT_CHANNEL}. */\nexport interface LockWaitEvent {\n /** Bridge identity tag — filter on this to isolate one bridge's events. */\n bridgeId: symbol;\n /** Time spent waiting to acquire the session lock, in milliseconds. */\n durationMs: number;\n}\n\nexport const queryChannel: diagnostics_channel.Channel = diagnostics_channel.channel(QUERY_CHANNEL);\nexport const lockWaitChannel: diagnostics_channel.Channel =\n diagnostics_channel.channel(LOCK_WAIT_CHANNEL);\n","/** Convert a nanosecond `bigint` (as returned by `process.hrtime.bigint()`) to milliseconds. */\nexport const nsToMs = (ns: bigint): number => Number(ns) / 1_000_000;\n","/**\n * PGlite duplex 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 → duplex frames messages →\n * PGlite processes via execProtocolRawStream → duplex 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 streams 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 type { TelemetrySink } from './utils/bridge-stats.ts';\nimport { lockWaitChannel, queryChannel } from './utils/diagnostics.ts';\nimport type { BridgeId as DuplexId, SessionLock } from './utils/session-lock.ts';\nimport { nsToMs } from './utils/time.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 types\nconst READY_FOR_QUERY = 0x5a; // Z — 6 bytes: Z + length(5) + status\nconst ERROR_RESPONSE = 0x45; // E — signals in-band SQL error (not a JS throw)\nconst ROW_DESCRIPTION = 0x54; // T — column metadata; rewritten to widen oid 18\n\n// pg_catalog system columns (pg_constraint.contype, pg_type.typtype, pg_class.relkind,\n// etc.) report type oid 18 (\"char\", 1-byte). @prisma/adapter-pg's fieldToColumnType\n// has no case for oid 18 and throws UnsupportedNativeDataType. The bytes on the wire\n// for these single-ASCII-char values decode identically as text, so we widen oid 18\n// to oid 25 (text) in RowDescription frames before pg.Client sees them — but only\n// when the field originates from a pg_catalog relation (tableOID below\n// FirstNormalObjectId). User-defined \"char\" columns are left untouched so the\n// upstream type-mapping gap surfaces instead of being silently papered over with a\n// possibly-lossy text decode. Remove this rewrite once @prisma/adapter-pg gains a\n// fieldToColumnType case for oid 18.\nconst PG_TYPE_OID_CHAR = 18;\nconst PG_TYPE_OID_TEXT = 25;\n// Mirrors PostgreSQL's FirstNormalObjectId (src/include/access/transam.h): every\n// system catalog relation has an OID below this; user-created objects start at it.\nconst PG_FIRST_USER_OID = 16384;\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 * Upper bound on a single backend message length declared in its 4-byte\n * header. PostgreSQL's own wire protocol maxes out around 1 GiB per\n * message; anything larger indicates a corrupted or hostile stream and\n * must not be allocated against.\n */\nconst MAX_BACKEND_MESSAGE_LENGTH = 1_073_741_824;\n\n/**\n * Widens any field whose dataTypeOID is 18 (\"char\") to oid 25 (text) — but\n * only when the field originates from a pg_catalog relation (tableOID is\n * non-zero and below FirstNormalObjectId). Single-ASCII-byte payloads from\n * pg_catalog views (pg_constraint.contype, pg_type.typtype, etc.) decode\n * identically as text, so the row data needs no transformation. Fields with\n * tableOID === 0 (computed expressions) and user-table fields are left\n * untouched, since arbitrary bytes in user \"char\" data can't safely be\n * relabelled as text. Frame length is unchanged because all rewrites are\n * fixed-size in place.\n */\nconst rewriteRowDescriptionInPlace = (buf: Buffer): void => {\n const fieldCount = buf.readInt16BE(5);\n let p = 7;\n for (let i = 0; i < fieldCount; i++) {\n while (p < buf.length && buf[p] !== 0) p++;\n p++; // NUL terminator\n // Per-field fixed suffix is 18 bytes: tableOID(4) + columnAttr(2) +\n // dataTypeOID(4) + dataTypeSize(2) + typeModifier(4) + formatCode(2).\n // Bail if the frame is truncated rather than throwing RangeError mid-loop.\n /* c8 ignore next — defense-in-depth: framer caller passes a complete frame */\n if (p + 18 > buf.length) return;\n const tableOID = buf.readUInt32BE(p);\n p += 4 + 2; // tableOID, columnAttr\n const oid = buf.readUInt32BE(p);\n if (oid === PG_TYPE_OID_CHAR && tableOID !== 0 && tableOID < PG_FIRST_USER_OID) {\n buf.writeUInt32BE(PG_TYPE_OID_TEXT, p);\n buf.writeInt16BE(-1, p + 4);\n }\n p += 4 + 2 + 4 + 2; // dataTypeOID, dataTypeSize, typeModifier, formatCode\n }\n};\n\n/**\n * Concatenates multiple Uint8Array views into one contiguous buffer.\n */\nconst concat = (parts: Uint8Array[]): Uint8Array => {\n /* c8 ignore next — parts[0] defined when length===1 */\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\ntype BackendMessageFramerOptions = {\n suppressIntermediateReadyForQuery?: boolean;\n onChunk: (chunk: Uint8Array) => void;\n onErrorResponse?: () => void;\n onReadyForQuery?: (status: number) => void;\n};\n\n/**\n * Frontend chunk queue that frames messages without repeatedly compacting\n * the full buffered input.\n *\n * @internal — exported for testing only\n */\nexport class FrontendMessageBuffer {\n private chunks: Uint8Array[] = [];\n private headIndex = 0;\n private headOffset = 0;\n private totalLength = 0;\n\n get length(): number {\n return this.totalLength;\n }\n\n push(chunk: Uint8Array): void {\n if (chunk.length === 0) return;\n this.chunks.push(chunk);\n this.totalLength += chunk.length;\n }\n\n clear(): void {\n this.chunks = [];\n this.headIndex = 0;\n this.headOffset = 0;\n this.totalLength = 0;\n }\n\n readInt32BE(offset: number): number | undefined {\n if (offset < 0 || offset + 4 > this.totalLength) return undefined;\n\n const head = this.chunks[this.headIndex];\n /* c8 ignore next — head defined when totalLength > 0 */\n if (head !== undefined) {\n const start = this.headOffset + offset;\n if (start + 4 <= head.length) {\n /* c8 ignore start — bounds guaranteed by `start + 4 <= head.length` */\n const b1 = head[start] ?? 0;\n const b2 = head[start + 1] ?? 0;\n const b3 = head[start + 2] ?? 0;\n const b4 = head[start + 3] ?? 0;\n /* c8 ignore stop */\n return ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n }\n }\n\n let remaining = this.headOffset + offset;\n const bytes = new Uint8Array(4);\n let writeOffset = 0;\n\n for (let i = this.headIndex; i < this.chunks.length && writeOffset < 4; i++) {\n const chunk = this.chunks[i];\n /* c8 ignore next — chunks between headIndex and end are always populated */\n if (chunk === undefined) return undefined;\n if (remaining >= chunk.length) {\n remaining -= chunk.length;\n continue;\n }\n\n const bytesToCopy = Math.min(4 - writeOffset, chunk.length - remaining);\n bytes.set(chunk.subarray(remaining, remaining + bytesToCopy), writeOffset);\n writeOffset += bytesToCopy;\n remaining = 0;\n }\n\n /* c8 ignore start — bytes is a fixed 4-byte Uint8Array */\n const b1 = bytes[0] ?? 0;\n const b2 = bytes[1] ?? 0;\n const b3 = bytes[2] ?? 0;\n const b4 = bytes[3] ?? 0;\n /* c8 ignore stop */\n return ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n }\n\n consume(length: number): Uint8Array {\n if (length < 0 || length > this.totalLength) {\n throw new Error(`Cannot consume ${length} bytes from ${this.totalLength}-byte buffer`);\n }\n if (length === 0) return new Uint8Array(0);\n\n const head = this.chunks[this.headIndex];\n /* c8 ignore next — head defined when totalLength > 0 */\n if (head !== undefined) {\n const headRemaining = head.length - this.headOffset;\n if (headRemaining >= length) {\n const slice = head.subarray(this.headOffset, this.headOffset + length);\n this.headOffset += length;\n this.totalLength -= length;\n if (this.headOffset === head.length) {\n this.headIndex++;\n this.headOffset = 0;\n this.compactChunks();\n }\n return slice;\n }\n }\n\n const result = new Uint8Array(length);\n let writeOffset = 0;\n let remaining = length;\n\n while (remaining > 0) {\n const chunk = this.chunks[this.headIndex];\n /* c8 ignore next 3 — guarded by line-116 length check */\n if (chunk === undefined) {\n throw new Error('FrontendMessageBuffer underflow');\n }\n const available = chunk.length - this.headOffset;\n const bytesToCopy = Math.min(remaining, available);\n result.set(chunk.subarray(this.headOffset, this.headOffset + bytesToCopy), writeOffset);\n writeOffset += bytesToCopy;\n remaining -= bytesToCopy;\n this.headOffset += bytesToCopy;\n this.totalLength -= bytesToCopy;\n if (this.headOffset === chunk.length) {\n this.headIndex++;\n this.headOffset = 0;\n this.compactChunks();\n }\n }\n\n return result;\n }\n\n private compactChunks(): void {\n if (this.headIndex === this.chunks.length) {\n this.chunks = [];\n this.headIndex = 0;\n return;\n }\n\n if (this.headIndex >= 32 && this.headIndex * 2 >= this.chunks.length) {\n this.chunks = this.chunks.slice(this.headIndex);\n this.headIndex = 0;\n }\n }\n}\n\n/**\n * Streams backend protocol messages without materializing whole responses.\n *\n * Non-RFQ payload bytes are forwarded as they arrive. ReadyForQuery frames are\n * tracked only once complete; when suppression is enabled, only the final RFQ\n * is emitted.\n *\n * @internal — exported for testing only\n */\nexport class BackendMessageFramer {\n private readonly suppressIntermediateReadyForQuery: boolean;\n private readonly onChunk: (chunk: Uint8Array) => void;\n private readonly onErrorResponse?: () => void;\n private readonly onReadyForQuery?: (status: number) => void;\n private readonly headerScratch = new Uint8Array(4);\n private readonly heldRfq = new Uint8Array(6);\n private messageType?: number;\n private headerBytesRead = 0;\n private payloadBytesRemaining = 0;\n private rfqBytesRead = 0;\n /** Slow-path RowDescription accumulator. Set once the T-frame header is decoded\n * and a multi-chunk payload starts arriving; cleared after rewrite + emit. */\n private rowDescBuffer?: Buffer;\n private rowDescOffset = 0;\n\n constructor(options: BackendMessageFramerOptions) {\n this.suppressIntermediateReadyForQuery = options.suppressIntermediateReadyForQuery ?? false;\n this.onChunk = options.onChunk;\n this.onErrorResponse = options.onErrorResponse;\n this.onReadyForQuery = options.onReadyForQuery;\n }\n\n write(chunk: Uint8Array): void {\n if (chunk.length === 0) return;\n\n let offset = 0;\n let passthroughStart = -1;\n const flushPassthrough = (end: number): void => {\n if (passthroughStart >= 0 && end > passthroughStart) {\n this.emitChunkSlice(chunk, passthroughStart, end);\n passthroughStart = -1;\n }\n };\n while (offset < chunk.length) {\n if (this.messageType === undefined) {\n // Fast path: if type + 4-byte header + full payload are all in this\n // chunk, emit the whole message as one slice. Avoids the per-message\n // prefix allocation + two downstream pushes that the byte-state-machine\n // path below performs. Falls through to the slow path when the message\n // spans chunks.\n const available = chunk.length - offset;\n if (available >= 5) {\n /* c8 ignore start — bounds guaranteed by `available >= 5` */\n const msgType = chunk[offset] ?? 0;\n const b1 = chunk[offset + 1] ?? 0;\n const b2 = chunk[offset + 2] ?? 0;\n const b3 = chunk[offset + 3] ?? 0;\n const b4 = chunk[offset + 4] ?? 0;\n /* c8 ignore stop */\n const messageLength = ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n if (messageLength < 4) {\n throw new Error(`Malformed backend message length: ${messageLength}`);\n }\n if (messageLength > MAX_BACKEND_MESSAGE_LENGTH) {\n throw new Error(\n `Backend message length ${messageLength} exceeds sanity cap ${MAX_BACKEND_MESSAGE_LENGTH}`,\n );\n }\n const totalLen = 1 + messageLength;\n if (available >= totalLen) {\n if (msgType === ERROR_RESPONSE) {\n this.onErrorResponse?.();\n }\n if (msgType === READY_FOR_QUERY && messageLength === 5) {\n flushPassthrough(offset);\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n /* c8 ignore next — messageLength === 5 for RFQ; payload is 1 byte */\n const status = chunk[offset + 5] ?? 0;\n this.heldRfq[0] = msgType;\n this.heldRfq[1] = b1;\n this.heldRfq[2] = b2;\n this.heldRfq[3] = b3;\n this.heldRfq[4] = b4;\n this.heldRfq[5] = status;\n this.rfqBytesRead = 6;\n this.onReadyForQuery?.(status);\n if (!this.suppressIntermediateReadyForQuery) {\n this.emitReadyForQuery();\n this.rfqBytesRead = 0;\n }\n } else if (msgType === ROW_DESCRIPTION) {\n flushPassthrough(offset);\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n this.emitRewrittenRowDescription(\n Buffer.from(chunk.subarray(offset, offset + totalLen)),\n );\n } else {\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n if (passthroughStart < 0) {\n passthroughStart = offset;\n }\n }\n offset += totalLen;\n continue;\n }\n }\n\n flushPassthrough(offset);\n if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.dropHeldReadyForQuery();\n }\n /* c8 ignore next — offset < chunk.length guaranteed by outer while */\n this.messageType = chunk[offset] ?? 0;\n this.headerBytesRead = 0;\n this.payloadBytesRemaining = 0;\n this.rfqBytesRead = this.messageType === READY_FOR_QUERY ? 1 : 0;\n if (this.rfqBytesRead === 1) {\n this.heldRfq[0] = this.messageType;\n }\n offset++;\n continue;\n }\n\n if (this.headerBytesRead < 4) {\n const bytesToCopy = Math.min(4 - this.headerBytesRead, chunk.length - offset);\n const headerChunk = chunk.subarray(offset, offset + bytesToCopy);\n this.headerScratch.set(headerChunk, this.headerBytesRead);\n if (this.messageType === READY_FOR_QUERY) {\n this.heldRfq.set(headerChunk, this.rfqBytesRead);\n this.rfqBytesRead += bytesToCopy;\n }\n this.headerBytesRead += bytesToCopy;\n offset += bytesToCopy;\n if (this.headerBytesRead < 4) continue;\n\n /* c8 ignore start — header bytes all populated before read */\n const b1 = this.headerScratch[0] ?? 0;\n const b2 = this.headerScratch[1] ?? 0;\n const b3 = this.headerScratch[2] ?? 0;\n const b4 = this.headerScratch[3] ?? 0;\n /* c8 ignore stop */\n const messageLength = ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) >>> 0;\n if (messageLength < 4) {\n throw new Error(`Malformed backend message length: ${messageLength}`);\n }\n if (messageLength > MAX_BACKEND_MESSAGE_LENGTH) {\n throw new Error(\n `Backend message length ${messageLength} exceeds sanity cap ${MAX_BACKEND_MESSAGE_LENGTH}`,\n );\n }\n\n this.payloadBytesRemaining = messageLength - 4;\n\n if (this.messageType === ERROR_RESPONSE) {\n this.onErrorResponse?.();\n }\n\n if (this.isReadyForQueryFrame()) {\n continue;\n }\n\n this.dropHeldReadyForQuery();\n if (this.messageType === ROW_DESCRIPTION) {\n // Valid RowDescription always carries at least a 2-byte fieldCount,\n // so payloadBytesRemaining is > 0 here — no zero-payload finalize needed.\n this.rowDescBuffer = Buffer.alloc(5 + this.payloadBytesRemaining);\n this.rowDescBuffer[0] = ROW_DESCRIPTION;\n this.rowDescBuffer.set(this.headerScratch, 1);\n this.rowDescOffset = 5;\n continue;\n }\n this.emitPrefix();\n if (this.payloadBytesRemaining === 0) {\n this.finishMessage();\n }\n continue;\n }\n\n if (this.isReadyForQueryFrame()) {\n const bytesToCopy = Math.min(this.payloadBytesRemaining, chunk.length - offset);\n const payloadChunk = chunk.subarray(offset, offset + bytesToCopy);\n this.heldRfq.set(payloadChunk, this.rfqBytesRead);\n this.rfqBytesRead += bytesToCopy;\n this.payloadBytesRemaining -= bytesToCopy;\n offset += bytesToCopy;\n /* c8 ignore next 3 — bytesToCopy ≥ 1 consumes the 1-byte RFQ payload */\n if (this.payloadBytesRemaining === 0) {\n this.finishReadyForQuery();\n }\n continue;\n }\n\n const bytesToEmit = Math.min(this.payloadBytesRemaining, chunk.length - offset);\n /* c8 ignore next — bytesToEmit always ≥ 1 when reached */\n if (bytesToEmit > 0) {\n if (this.rowDescBuffer !== undefined) {\n this.rowDescBuffer.set(chunk.subarray(offset, offset + bytesToEmit), this.rowDescOffset);\n this.rowDescOffset += bytesToEmit;\n } else {\n this.emitChunkSlice(chunk, offset, offset + bytesToEmit);\n }\n this.payloadBytesRemaining -= bytesToEmit;\n offset += bytesToEmit;\n }\n if (this.payloadBytesRemaining === 0) {\n if (this.rowDescBuffer !== undefined) {\n const buf = this.rowDescBuffer;\n this.rowDescBuffer = undefined;\n this.emitRewrittenRowDescription(buf);\n }\n this.finishMessage();\n }\n }\n\n flushPassthrough(offset);\n }\n\n flush(options?: { dropHeldReadyForQuery?: boolean }): void {\n if (options?.dropHeldReadyForQuery === true) {\n this.dropHeldReadyForQuery();\n } else if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) {\n this.emitReadyForQuery();\n this.rfqBytesRead = 0;\n }\n }\n\n reset(): void {\n this.messageType = undefined;\n this.headerBytesRead = 0;\n this.payloadBytesRemaining = 0;\n this.rfqBytesRead = 0;\n this.rowDescBuffer = undefined;\n this.rowDescOffset = 0;\n }\n\n private isReadyForQueryFrame(): boolean {\n return this.messageType === READY_FOR_QUERY && this.payloadBytesRemaining === 1;\n }\n\n private finishReadyForQuery(): void {\n const status = this.heldRfq[5];\n /* c8 ignore next — heldRfq[5] always populated before finishReadyForQuery */\n if (status !== undefined) {\n this.onReadyForQuery?.(status);\n }\n\n if (!this.suppressIntermediateReadyForQuery) {\n this.emitReadyForQuery();\n }\n\n this.finishMessage();\n }\n\n private emitReadyForQuery(): void {\n this.onChunk(this.heldRfq.slice(0, 6));\n }\n\n private dropHeldReadyForQuery(): void {\n this.rfqBytesRead = 0;\n }\n\n private emitPrefix(): void {\n const prefix = new Uint8Array(5);\n /* c8 ignore next — messageType always set when emitPrefix is called */\n prefix[0] = this.messageType ?? 0;\n prefix.set(this.headerScratch, 1);\n this.onChunk(prefix);\n }\n\n private emitRewrittenRowDescription(buf: Buffer): void {\n rewriteRowDescriptionInPlace(buf);\n this.onChunk(buf);\n }\n\n private emitChunkSlice(chunk: Uint8Array, start: number, end: number): void {\n const length = end - start;\n /* c8 ignore next — callers pass end > start */\n if (length <= 0) return;\n\n // PGlite already hands us standalone Uint8Array chunks copied out of the\n // WASM heap, so when this chunk owns its full backing store we can hand pg\n // zero-copy Buffer views for arbitrary subranges. We still copy when the\n // chunk is a view into a larger backing buffer (to avoid pinning unrelated\n // trailing bytes) or when the backing store is shared (to prevent the WASM\n // runtime from mutating bytes pg is still consuming).\n if (\n chunk.byteOffset === 0 &&\n chunk.byteLength === chunk.buffer.byteLength &&\n !(chunk.buffer instanceof SharedArrayBuffer)\n ) {\n this.onChunk(Buffer.from(chunk.buffer, start, length));\n return;\n }\n\n const exact = Buffer.from(chunk.subarray(start, end));\n this.onChunk(exact);\n }\n\n private finishMessage(): void {\n this.messageType = undefined;\n this.headerBytesRead = 0;\n this.payloadBytesRemaining = 0;\n if (!this.suppressIntermediateReadyForQuery) {\n this.rfqBytesRead = 0;\n }\n }\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()` / `createPGliteBridge()`:\n *\n * ```typescript\n * const client = new pg.Client({\n * stream: () => new PGliteDuplex(pglite),\n * });\n * ```\n */\nexport class PGliteDuplex extends Duplex {\n private readonly pglite: PGlite;\n private readonly sessionLock?: SessionLock;\n private readonly bridgeId?: symbol;\n private readonly telemetry?: TelemetrySink;\n private readonly syncToFs: boolean;\n private readonly duplexId: DuplexId;\n /** Incoming bytes framed directly from a queued chunk buffer */\n private readonly input = new FrontendMessageBuffer();\n private phase: 'pre_startup' | 'ready' = 'pre_startup';\n private draining = false;\n private tornDown = false;\n /** Callbacks waiting for drain to process their data */\n private drainQueue: Array<(error?: Error | null) => void> = [];\n /** Buffered EQP messages awaiting Sync */\n private pipeline: Uint8Array[] = [];\n\n /**\n * @param pglite PGlite instance to bridge to. The caller owns its lifecycle.\n * @param sessionLock Shared lock that serialises access to the PGlite runtime\n * across multiple duplex streams. Omit for a standalone duplex.\n * @param bridgeId Identity tag published with diagnostics-channel events.\n * Omit to disable channel publication for this duplex.\n * @param telemetry Internal sink used by `createPGliteBridge` for built-in\n * stats. Not a public extension point — subscribe via\n * `node:diagnostics_channel` instead.\n * @param syncToFs Whether each wire-protocol call should force a\n * filesystem sync before returning. Disable only when\n * higher throughput / lower RSS is worth weaker durability.\n */\n constructor(\n pglite: PGlite,\n sessionLock?: SessionLock,\n bridgeId?: symbol,\n telemetry?: TelemetrySink,\n syncToFs = true,\n ) {\n super();\n this.pglite = pglite;\n this.sessionLock = sessionLock;\n this.bridgeId = bridgeId;\n this.telemetry = telemetry;\n this.syncToFs = syncToFs;\n this.duplexId = Symbol('duplex');\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.input.push(chunk);\n this.enqueue(callback);\n }\n\n /** Handles corked batches — pg.Client corks during prepared queries (P+B+D+E+S) */\n override _writev(\n chunks: Array<{ chunk: Buffer; encoding: BufferEncoding }>,\n callback: (error?: Error | null) => void,\n ): void {\n for (const { chunk } of chunks) {\n this.input.push(chunk);\n }\n this.enqueue(callback);\n }\n\n override _final(callback: (error?: Error | null) => void): void {\n this.sessionLock?.release(this.duplexId);\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.input.clear();\n this.sessionLock?.cancel(this.duplexId, error ?? new Error('Bridge destroyed'));\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 /**\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(/* c8 ignore next */ () => {});\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 /* c8 ignore next — enqueue only starts drain when !draining */\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.input.length > 0) {\n /* c8 ignore next — race-only: destroy after a drain iteration resolves */\n if (this.tornDown) break;\n const beforeLength = this.input.length;\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 /* c8 ignore next — loop-continue unreachable: no new input arrives mid-drain */\n if (this.input.length === 0 || this.input.length === beforeLength) 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.duplexId);\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 if (this.input.length < 4) return;\n const len = this.input.readInt32BE(0);\n /* c8 ignore next — len === undefined unreachable once length ≥ 4 */\n if (len === undefined || this.input.length < len) return;\n\n const message = this.input.consume(len);\n\n const session = this.acquireSession();\n if (session) await session;\n await this.pglite.runExclusive(async () => {\n await this.streamProtocol(message, { detectErrors: false, suppressIntermediateRfq: false });\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 while (this.input.length >= 5) {\n const msgLen = this.input.readInt32BE(1);\n /* c8 ignore next — input.length ≥ 5 guarantees readable int32 */\n if (msgLen === undefined) break;\n const len = 1 + msgLen;\n if (len < 5 || this.input.length < len) break;\n\n const message = this.input.consume(len);\n /* c8 ignore next — consume(len ≥ 5) returns non-empty */\n const msgType = message[0] ?? 0;\n\n if (msgType === TERMINATE) {\n this.sessionLock?.release(this.duplexId);\n this.push(null);\n return;\n }\n\n if (EQP_MESSAGES.has(msgType)) {\n this.pipeline.push(message);\n continue;\n }\n\n if (msgType === SYNC) {\n this.pipeline.push(message);\n await this.flushPipeline();\n continue;\n }\n\n // SimpleQuery or other standalone message\n await this.runWithTiming((detectErrors) =>\n this.streamProtocol(message, { detectErrors, suppressIntermediateRfq: false }),\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). A streaming framer suppresses intermediate ReadyForQuery\n * messages while forwarding the rest of the response without\n * materializing it.\n */\n private async flushPipeline(): Promise<void> {\n const messages = this.pipeline;\n this.pipeline = [];\n const batch = concat(messages);\n await this.runWithTiming((detectErrors) =>\n this.streamProtocol(batch, { detectErrors, suppressIntermediateRfq: true }),\n );\n }\n\n /**\n * Acquires the session, runs the op under `pglite.runExclusive`, and\n * updates internal stats and/or publishes diagnostics events when enabled.\n * When neither internal telemetry nor diagnostics subscribers need timing,\n * skips timing entirely.\n *\n * `op` returns `false` when an `ErrorResponse` was seen without throwing\n * (protocol-level failure). Combined with the catch branch, both failure\n * modes flip `succeeded` so both `BridgeStats` and `QUERY_CHANNEL`\n * payloads stay accurate. `detectErrors` is therefore tied to whether\n * either of those consumers is active, not to timing in general.\n */\n private async runWithTiming(op: (detectErrors: boolean) => Promise<boolean>): Promise<void> {\n const wantTelemetry = this.telemetry !== undefined;\n const publishQuery = this.bridgeId !== undefined && queryChannel.hasSubscribers;\n const publishLockWait = this.bridgeId !== undefined && lockWaitChannel.hasSubscribers;\n const wantTiming = wantTelemetry || publishQuery || publishLockWait;\n const detectErrors = wantTelemetry || publishQuery;\n\n if (!wantTiming) {\n const session = this.acquireSession();\n if (session) await session;\n await this.pglite.runExclusive(async () => {\n await op(false);\n });\n return;\n }\n\n const lockStart = process.hrtime.bigint();\n const session = this.acquireSession();\n if (session) await session;\n const queryStart = process.hrtime.bigint();\n const lockWaitMs = nsToMs(queryStart - lockStart);\n if (wantTelemetry) {\n this.telemetry?.recordLockWait(lockWaitMs);\n }\n if (publishLockWait) {\n lockWaitChannel.publish({\n bridgeId: this.bridgeId,\n durationMs: lockWaitMs,\n });\n }\n\n let succeeded = true;\n try {\n await this.pglite.runExclusive(async () => {\n succeeded = await op(detectErrors);\n });\n } catch (err) {\n succeeded = false;\n throw err;\n } finally {\n const queryMs = nsToMs(process.hrtime.bigint() - queryStart);\n if (wantTelemetry) {\n this.telemetry?.recordQuery(queryMs, succeeded);\n }\n if (publishQuery) {\n queryChannel.publish({\n bridgeId: this.bridgeId,\n durationMs: queryMs,\n succeeded,\n });\n }\n }\n }\n\n /**\n * Sends a message (or pipelined batch) to PGlite and pushes response\n * chunks directly to the stream as they arrive. Avoids collecting and\n * concatenating for large multi-row responses (e.g., findMany 500 rows\n * = ~503 onRawData chunks).\n *\n * For pipelined Extended Query batches, pass `suppressIntermediateRfq`\n * so only the final ReadyForQuery reaches the client.\n *\n * Must be called inside runExclusive.\n */\n private async streamProtocol(\n message: Uint8Array,\n options: { detectErrors: boolean; suppressIntermediateRfq: boolean },\n ): Promise<boolean> {\n const { detectErrors, suppressIntermediateRfq } = options;\n let errSeen = false;\n const framer = new BackendMessageFramer({\n suppressIntermediateReadyForQuery: suppressIntermediateRfq,\n onChunk: (chunk) => {\n /* c8 ignore next — race-only: tornDown becomes true mid-stream */\n if (!this.tornDown && chunk.length > 0) {\n this.push(chunk);\n }\n },\n onErrorResponse: () => {\n if (detectErrors) errSeen = true;\n },\n onReadyForQuery: (status) => {\n if (this.sessionLock) {\n this.sessionLock.updateStatus(this.duplexId, status);\n }\n },\n });\n\n await this.pglite.execProtocolRawStream(message, {\n syncToFs: this.syncToFs,\n onRawData: (chunk: Uint8Array) => {\n /* c8 ignore next — race-only: tornDown becomes true mid-stream */\n if (!this.tornDown) {\n framer.write(chunk);\n }\n },\n });\n\n framer.flush({ dropHeldReadyForQuery: this.tornDown });\n return !errSeen;\n }\n\n // ── Session lock helpers ──\n\n private acquireSession(): Promise<void> | undefined {\n return this.sessionLock?.acquire(this.duplexId);\n }\n}\n","import type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { PGliteDuplex } from './pglite-duplex.ts';\nimport type { TelemetrySink } from './utils/bridge-stats.ts';\nimport type { SessionLock } from './utils/session-lock.ts';\n\nexport const bridgeClientOptionsKey: unique symbol = Symbol('bridgeClientOptions');\n\ninterface BridgeClientOptions {\n pglite: PGlite;\n sessionLock?: SessionLock;\n bridgeId: symbol;\n telemetry?: TelemetrySink;\n syncToFs: boolean;\n}\n\ntype BridgeClientConfig = pg.ClientConfig & {\n [bridgeClientOptionsKey]: BridgeClientOptions;\n};\n\nexport type BridgePoolConfig = pg.PoolConfig & {\n [bridgeClientOptionsKey]: BridgeClientOptions;\n};\n\nexport class BridgeClient extends pg.Client {\n private querySubmissionChain: Promise<void> = Promise.resolve();\n\n constructor(config?: BridgeClientConfig) {\n const resolved = config ?? ({} as BridgeClientConfig);\n const { [bridgeClientOptionsKey]: bridge, ...clientConfig } = resolved;\n if (!bridge) {\n throw new Error('BridgeClient requires bridge options');\n }\n\n super({\n ...clientConfig,\n user: 'postgres',\n database: 'postgres',\n stream: () =>\n new PGliteDuplex(\n bridge.pglite,\n bridge.sessionLock,\n bridge.bridgeId,\n bridge.telemetry,\n bridge.syncToFs,\n ),\n });\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: satisfy pg.Client.query's overload union\n override query(...args: unknown[]): any {\n const first = args[0];\n // biome-ignore lint/suspicious/noExplicitAny: pg.Client.query has 7 overloads\n const callSuper = () => (super.query as any).apply(this, args);\n\n // Preserve pg's synchronous TypeError for null/undefined query.\n if (first === null || first === undefined) return callSuper();\n\n // Submittable: terminal signaling isn't uniform across the pg contract.\n // Let pg's internal queue handle it unserialized. adapter-pg never uses\n // this form; users mixing Submittable + Promise forms on one client may\n // still trip the pg queue deprecation.\n if (typeof (first as { submit?: unknown }).submit === 'function') {\n return callSuper();\n }\n\n const prior = this.querySubmissionChain;\n let signalDone!: () => void;\n this.querySubmissionChain = new Promise<void>((resolve) => {\n signalDone = resolve;\n });\n\n const cbIndex = args.findIndex((arg) => typeof arg === 'function');\n if (cbIndex !== -1) {\n const origCb = args[cbIndex] as (err: unknown, res: unknown) => void;\n args[cbIndex] = (err: unknown, res: unknown) => {\n signalDone();\n origCb(err, res);\n };\n prior.then(callSuper).catch((err) => {\n signalDone();\n origCb(err, undefined);\n });\n return undefined;\n }\n\n const p = prior.then(callSuper);\n p.then(signalDone, signalDone);\n return p;\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\n/**\n * Coordinates PGlite access across concurrent pool connections.\n *\n * @remarks\n * PGlite runs PostgreSQL in single-user mode — one session shared by all\n * bridges. The session lock tracks which bridge owns the session during\n * transactions, preventing interleaving. Used internally by {@link PGliteDuplex}\n * and created automatically by {@link createPool}. Only instantiate directly\n * if building a custom pool setup.\n */\nexport class SessionLock {\n private owner?: BridgeId;\n private waitQueue: Array<{ id: BridgeId; resolve: () => void; reject: (error: Error) => void }> =\n [];\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 // Free slot or re-entrant — pass through\n if (this.owner === undefined || this.owner === id) return;\n\n // Another bridge owns the session — wait\n return new Promise<void>((resolve, reject) => {\n this.waitQueue.push({\n id,\n resolve,\n reject,\n });\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 * @returns `true` if ownership transitioned on this call (acquired or\n * released). `false` for no-op updates (e.g., re-entrant status within\n * the same transaction, or IDLE from a non-owning bridge).\n */\n updateStatus(id: BridgeId, status: number): boolean {\n if (status === STATUS_IN_TRANSACTION || status === STATUS_FAILED) {\n if (this.owner === id) return false;\n this.owner = id;\n return true;\n }\n\n // Transaction complete — release ownership\n if (status === STATUS_IDLE && this.owner === id) {\n this.owner = undefined;\n this.drainWaitQueue();\n return true;\n }\n\n return false;\n }\n\n /**\n * Release ownership (e.g., when a bridge is destroyed mid-transaction).\n *\n * @returns `true` if this bridge held ownership and released it. `false`\n * if another bridge (or no one) owned the session.\n */\n release(id: BridgeId): boolean {\n if (this.owner === id) {\n this.owner = undefined;\n this.drainWaitQueue();\n return true;\n }\n\n return false;\n }\n\n /**\n * Cancel this bridge's pending or active claim on the session.\n *\n * Used when a bridge is torn down while blocked in `acquire()` so it cannot\n * later be granted ownership after destruction.\n */\n cancel(id: BridgeId, error: Error = new Error('Session lock acquire cancelled')): boolean {\n let cancelled = false;\n\n if (this.owner === id) {\n this.owner = undefined;\n this.drainWaitQueue();\n cancelled = true;\n }\n\n const remaining: typeof this.waitQueue = [];\n for (const waiter of this.waitQueue) {\n if (waiter.id === id) {\n waiter.reject(error);\n cancelled = true;\n } else {\n remaining.push(waiter);\n }\n }\n this.waitQueue = remaining;\n\n return cancelled;\n }\n\n /**\n * Grant ownership to the next waiter, if any.\n *\n * @returns `true` if a waiter was unblocked; `false` if the queue was empty.\n */\n private drainWaitQueue(): boolean {\n // Release one waiter at a time and grant ownership before resolving.\n // The waiter's operation will call updateStatus when it completes —\n // if IDLE, ownership is cleared and the next waiter is released.\n // This prevents interleaving where multiple waiters race past acquire\n // and one starts a transaction while others proceed unserialized.\n const next = this.waitQueue.shift();\n if (!next) return false;\n\n this.owner = next.id;\n next.resolve();\n return true;\n }\n}\n","/**\n * Pool factory — creates a pg.Pool backed by a caller-supplied PGlite instance.\n *\n * Each pool connection gets its own PGliteDuplex stream, all sharing the\n * same PGlite WASM instance. Pools with multiple connections also share a\n * SessionLock. The session lock ensures transaction isolation: when one\n * bridge starts a transaction (BEGIN), it gets exclusive PGlite access until\n * COMMIT/ROLLBACK. Non-transactional operations from any bridge serialize\n * through PGlite's runExclusive mutex.\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { BridgeClient, type BridgePoolConfig, bridgeClientOptionsKey } from './bridge-client.ts';\nimport type { TelemetrySink } from './utils/bridge-stats.ts';\nimport { SessionLock } from './utils/session-lock.ts';\n\nexport type SyncToFsMode = 'auto' | boolean;\n\nconst resolveSyncToFs = (pglite: PGlite, mode: SyncToFsMode | undefined): boolean => {\n if (mode === true || mode === false) return mode;\n\n const dataDir = pglite.dataDir;\n return !(dataDir === undefined || dataDir === '' || dataDir.startsWith('memory://'));\n};\n\nexport interface CreatePoolOptions {\n /** PGlite instance to bridge to. The caller owns its lifecycle. */\n pglite: PGlite;\n\n /**\n * Maximum pool connections (default: 1). Compatibility knob, not a\n * throughput knob.\n *\n * PGlite's WASM runtime executes queries serially behind a single mutex.\n * Raising `max` above 1 therefore does not add parallelism — queries still\n * run one at a time — and each extra connection costs a full `PGliteDuplex`\n * (its framers and scratch buffers) plus shared session-lock coordination\n * in memory. Leave this at `1` unless your code specifically needs to check\n * out multiple `pg` clients or you are deliberately exercising wait-queue\n * behaviour in a test.\n */\n max?: number;\n\n /**\n * Identity tag published with every diagnostics-channel event. Subscribers\n * filter on this to distinguish events from different bridges in the\n * same process. A fresh `Symbol('bridge')` is generated if omitted.\n */\n bridgeId?: symbol;\n\n /**\n * Filesystem sync policy for bridge-driven wire-protocol calls.\n *\n * - `'auto'` (default): disable per-query sync for clearly in-memory PGlite\n * instances, keep it enabled otherwise.\n * - `true`: always sync before the bridge returns a query result.\n * - `false`: never sync on bridge protocol calls; fastest, but weaker durability.\n *\n * `auto` uses `pglite.dataDir` as a heuristic. If you provide a custom\n * persistent `fs` without a meaningful `dataDir`, pass `true` explicitly.\n */\n syncToFs?: SyncToFsMode;\n\n telemetry?: TelemetrySink;\n}\n\nexport interface PoolResult {\n /** pg.Pool backed by PGlite — pass to PrismaPg */\n pool: pg.Pool;\n\n /**\n * Identity tag carried on every `QUERY_CHANNEL` / `LOCK_WAIT_CHANNEL`\n * event this pool produces. Matches the `bridgeId` option if supplied,\n * otherwise a freshly minted symbol. Filter on it from external\n * subscribers to isolate this pool's events.\n */\n bridgeId: symbol;\n\n /** Shut down the pool. Does not close the caller-owned PGlite instance. */\n close: () => Promise<void>;\n}\n\n/**\n * Creates a pg.Pool where every connection is an in-process PGlite bridge.\n *\n * Most users should prefer {@link createPGliteBridge}, which wraps this\n * function and also handles schema application and reset/snapshot lifecycle.\n *\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPool } from 'prisma-pglite-bridge';\n * import { PrismaPg } from '@prisma/adapter-pg';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const { pool, close } = await createPool({ pglite });\n * const adapter = new PrismaPg(pool);\n * const prisma = new PrismaClient({ adapter });\n * ```\n *\n * @see {@link createPGliteBridge} for the higher-level API with schema management.\n */\nexport const createPool = async (options: CreatePoolOptions): Promise<PoolResult> => {\n const { pglite, max = 1, telemetry } = options;\n const bridgeId = options.bridgeId ?? Symbol('bridge');\n const syncToFs = resolveSyncToFs(pglite, options.syncToFs);\n\n await pglite.waitReady;\n\n const sessionLock = max > 1 ? new SessionLock() : undefined;\n\n const poolConfig: BridgePoolConfig = {\n Client: BridgeClient,\n max,\n [bridgeClientOptionsKey]: {\n pglite,\n sessionLock,\n bridgeId,\n telemetry,\n syncToFs,\n },\n };\n const pool = new pg.Pool(poolConfig);\n const close = () => pool.end();\n\n return { pool, bridgeId, close };\n};\n","/**\n * Private per-bridge stats state used to power `stats()`.\n *\n * Instantiated at level `'basic'` or `'full'` (level `'off'` means no stats\n * object exists).\n * Query-level timing is recorded directly by the bridge; one-shot lifecycle\n * signals (`incrementResetDb`, `freeze`) remain direct method calls invoked\n * by the bridge itself.\n *\n * Percentiles use nearest-rank (no interpolation) over a sliding window of\n * the most recent {@link QUERY_DURATION_WINDOW_SIZE} queries. Lifetime\n * counters (`queryCount`, `totalQueryMs`, `avgQueryMs`) are not windowed.\n * `durationMs` is frozen at the instant `close()` was invoked, via the\n * `closeEntryHrtime` the bridge records as its very first action and\n * passes to {@link freeze}.\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport { nsToMs } from './time.ts';\n\ntype DbSizeQueryable = Pick<PGlite, 'query'>;\n\n/**\n * Stats collection level.\n *\n * - `'off'` — `stats()` returns `undefined`. Zero hot-path overhead.\n * - `'basic'` — timing (`durationMs`), query percentiles, counters, and\n * `dbSizeBytes`.\n * - `'full'` — `'basic'` plus `processRssPeakBytes` and session-lock\n * waits.\n */\nexport type StatsLevel = 'off' | 'basic' | 'full';\n\n/** Internal bridge-facing telemetry contract. */\nexport interface TelemetrySink {\n recordQuery(durationMs: number, succeeded: boolean): void;\n recordLockWait(durationMs: number): void;\n}\n\n/**\n * Maximum number of recent query durations retained for percentile\n * computation. Beyond this window, `recentP50QueryMs`, `recentP95QueryMs`,\n * and `recentMaxQueryMs` reflect only the most recent N queries — lifetime\n * counters (`queryCount`, `totalQueryMs`, `avgQueryMs`) remain complete.\n */\nexport const QUERY_DURATION_WINDOW_SIZE = 10_000;\n\nconst QUERY_DURATION_TRIM_THRESHOLD = QUERY_DURATION_WINDOW_SIZE * 2;\n\ninterface StatsBase {\n durationMs: number;\n /** Lifetime count of recorded queries. Not windowed. */\n queryCount: number;\n failedQueryCount: number;\n /** Lifetime sum of query durations. Not windowed. */\n totalQueryMs: number;\n /** Lifetime mean query duration. Not windowed. */\n avgQueryMs: number;\n /**\n * Nearest-rank 50th percentile over the most recent\n * {@link QUERY_DURATION_WINDOW_SIZE} queries. Compare to `avgQueryMs`\n * with care: the two fields describe different populations on long-lived\n * bridges.\n */\n recentP50QueryMs: number;\n /** Nearest-rank 95th percentile over the recent-query window. */\n recentP95QueryMs: number;\n /** Maximum query duration within the recent-query window. */\n recentMaxQueryMs: number;\n resetDbCalls: number;\n /** Undefined only when the `pg_database_size` query rejected. */\n dbSizeBytes?: number;\n}\n\nexport interface StatsBasic extends StatsBase {\n statsLevel: 'basic';\n}\n\nexport interface StatsFull extends StatsBase {\n statsLevel: 'full';\n /**\n * Process-wide RSS high-water mark since process start, read from\n * `process.resourceUsage().maxRSS` (kernel-tracked, lossless). Reflects\n * the entire Node process — parallel test runners, other bridges, and\n * prior work in the same process all contribute. Use only as an\n * ordering signal, not an absolute measurement.\n *\n * `undefined` on runtimes that don't expose `process.resourceUsage`\n * (e.g. Bun, Deno, edge workers) — matches the field-level-undefined\n * contract of every other `Stats` member.\n */\n processRssPeakBytes: number | undefined;\n totalSessionLockWaitMs: number;\n sessionLockAcquisitionCount: number;\n avgSessionLockWaitMs: number;\n maxSessionLockWaitMs: number;\n}\n\nexport type Stats = StatsBasic | StatsFull;\n\nconst DB_SIZE_QUERY_TIMEOUT_MS = 5_000;\n\n/**\n * `process.resourceUsage().maxRSS` returns kilobytes on every platform\n * Node supports — we convert to bytes so the public `processRssPeakBytes`\n * field matches its name. Returns `undefined` on runtimes that don't\n * expose `resourceUsage` (Bun, Deno, edge workers).\n */\nconst readProcessRssPeakBytes = (): number | undefined => {\n try {\n return process.resourceUsage().maxRSS * 1024;\n } catch {\n return undefined;\n }\n};\n\nconst percentile = (sorted: readonly number[], p: number): number => {\n const n = sorted.length;\n if (n === 0) return 0;\n const index = Math.min(n - 1, Math.max(0, Math.ceil((p / 100) * n) - 1));\n /* c8 ignore next */\n return sorted[index] ?? 0;\n};\n\nexport class BridgeStats implements TelemetrySink {\n private readonly level: 'basic' | 'full';\n private readonly createdAtHrtime: bigint;\n\n private queryDurations: number[] = [];\n private totalQueryMs = 0;\n private queryCount = 0;\n private failedQueryCount = 0;\n private resetDbCalls = 0;\n\n private totalSessionLockWaitMs = 0;\n private maxSessionLockWaitMs = 0;\n private sessionLockAcquisitionCount = 0;\n\n private frozen = false;\n private cachedDurationMs?: number;\n private cachedDbSizeBytes?: number;\n private dbSizeFrozen = false;\n\n constructor(level: 'basic' | 'full') {\n this.level = level;\n this.createdAtHrtime = process.hrtime.bigint();\n }\n\n recordQuery(durationMs: number, succeeded: boolean): void {\n if (this.frozen) return;\n this.queryCount += 1;\n this.totalQueryMs += durationMs;\n this.queryDurations.push(durationMs);\n if (this.queryDurations.length > QUERY_DURATION_TRIM_THRESHOLD) {\n this.queryDurations = this.queryDurations.slice(-QUERY_DURATION_WINDOW_SIZE);\n }\n if (!succeeded) this.failedQueryCount += 1;\n }\n\n recordLockWait(durationMs: number): void {\n if (this.frozen) return;\n if (this.level !== 'full') return;\n this.totalSessionLockWaitMs += durationMs;\n this.sessionLockAcquisitionCount += 1;\n if (durationMs > this.maxSessionLockWaitMs) this.maxSessionLockWaitMs = durationMs;\n }\n\n incrementResetDb(): void {\n if (this.frozen) return;\n this.resetDbCalls += 1;\n }\n\n async snapshot(pglite: DbSizeQueryable): Promise<Stats> {\n const durationMs =\n this.cachedDurationMs ?? nsToMs(process.hrtime.bigint() - this.createdAtHrtime);\n const dbSizeBytes = this.dbSizeFrozen ? this.cachedDbSizeBytes : await this.queryDbSize(pglite);\n\n const sorted = [...this.queryDurations].sort((a, b) => a - b);\n const avgQueryMs = this.queryCount === 0 ? 0 : this.totalQueryMs / this.queryCount;\n\n const base: StatsBase = {\n durationMs,\n queryCount: this.queryCount,\n failedQueryCount: this.failedQueryCount,\n totalQueryMs: this.totalQueryMs,\n avgQueryMs,\n recentP50QueryMs: percentile(sorted, 50),\n recentP95QueryMs: percentile(sorted, 95),\n recentMaxQueryMs: percentile(sorted, 100),\n resetDbCalls: this.resetDbCalls,\n dbSizeBytes,\n };\n\n if (this.level === 'basic') {\n return { ...base, statsLevel: 'basic' };\n }\n\n const count = this.sessionLockAcquisitionCount;\n return {\n ...base,\n statsLevel: 'full',\n processRssPeakBytes: readProcessRssPeakBytes(),\n totalSessionLockWaitMs: this.totalSessionLockWaitMs,\n sessionLockAcquisitionCount: count,\n avgSessionLockWaitMs: count === 0 ? 0 : this.totalSessionLockWaitMs / count,\n maxSessionLockWaitMs: this.maxSessionLockWaitMs,\n };\n }\n\n async freeze(pglite: DbSizeQueryable, closeEntryHrtime: bigint): Promise<void> {\n if (this.frozen) return;\n this.frozen = true;\n\n this.cachedDurationMs = nsToMs(closeEntryHrtime - this.createdAtHrtime);\n try {\n this.cachedDbSizeBytes = await this.queryDbSize(pglite);\n } finally {\n this.dbSizeFrozen = true;\n }\n }\n\n private async queryDbSize(pglite: DbSizeQueryable): Promise<number | undefined> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error('pg_database_size query timed out')),\n DB_SIZE_QUERY_TIMEOUT_MS,\n );\n timer.unref?.();\n });\n const { rows } = await Promise.race([\n pglite.query<{ size: string | null }>(\n 'SELECT pg_database_size(current_database())::text AS size',\n ),\n timeout,\n ]);\n const size = rows[0]?.size;\n if (size == null) return undefined;\n const n = Number(size);\n return Number.isFinite(n) ? n : undefined;\n } catch {\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","import type { PGlite } from '@electric-sql/pglite';\n\nconst SNAPSHOT_SCHEMA = '_pglite_snapshot';\n\nconst USER_TABLES_WHERE = `schemaname NOT IN ('pg_catalog', 'information_schema')\n AND schemaname != '${SNAPSHOT_SCHEMA}'\n AND tablename NOT LIKE '_prisma%'`;\n\nconst escapeLiteral = (s: string) => `'${s.replace(/'/g, \"''\")}'`;\n\n/** JS equivalent of PostgreSQL's `quote_ident()`; matches its escaping rules. */\nconst quoteIdent = (identifier: string): string => `\"${identifier.replace(/\"/g, '\"\"')}\"`;\n\nconst SNAPSHOT_SCHEMA_IDENT = quoteIdent(SNAPSHOT_SCHEMA);\n\ninterface SnapshotManager {\n /**\n * Truncate all user tables. If a snapshot exists, restore its contents and\n * sequence values afterwards; otherwise just truncate and `DISCARD ALL`.\n */\n resetDb: () => Promise<void>;\n /** Drop the saved snapshot, reverting `resetDb` to plain truncation. */\n resetSnapshot: () => Promise<void>;\n /**\n * Capture the current state of all user tables plus sequence values into\n * the `_pglite_snapshot` schema. Replaces any previous snapshot.\n */\n snapshotDb: () => Promise<void>;\n}\n\n/**\n * Snapshot helpers backing `createPGliteBridge`'s `snapshotDb` / `resetDb` /\n * `resetSnapshot` functions. Stores a copy of user tables and sequence\n * values in a dedicated `_pglite_snapshot` schema so tests can reset to a\n * known seed state without re-running migrations.\n *\n * @internal\n */\nexport const createSnapshotManager = (pglite: PGlite): SnapshotManager => {\n let hasSnapshot = false;\n\n const getTables = async () => {\n const { rows } = await pglite.query<{ qualified: string }>(\n `SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified\n FROM pg_tables\n WHERE ${USER_TABLES_WHERE}`,\n );\n return rows.length > 0\n ? rows.map((row: { qualified: string }) => row.qualified).join(', ')\n : '';\n };\n\n const snapshotDb = async () => {\n await pglite.exec(`DROP SCHEMA IF EXISTS ${SNAPSHOT_SCHEMA_IDENT} CASCADE`);\n\n try {\n await pglite.exec('BEGIN');\n await pglite.exec(`CREATE SCHEMA ${SNAPSHOT_SCHEMA_IDENT}`);\n\n const { rows: tables } = await pglite.query<{\n schemaname: string;\n tablename: string;\n qualified: string;\n }>(\n `SELECT schemaname, tablename,\n quote_ident(schemaname) || '.' || quote_ident(tablename) AS qualified\n FROM pg_tables\n WHERE ${USER_TABLES_WHERE}`,\n );\n\n await pglite.exec(\n `CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.__tables (snap_name text, source_schema text, source_table text)`,\n );\n\n for (const [i, { schemaname, tablename, qualified }] of tables.entries()) {\n const snapName = `_snap_${i}`;\n await pglite.exec(\n `CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.${quoteIdent(snapName)} AS SELECT * FROM ${qualified}`,\n );\n await pglite.exec(\n `INSERT INTO ${SNAPSHOT_SCHEMA_IDENT}.__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`,\n );\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(quote_ident(schemaname) || '.' || quote_ident(sequencename)) AS name, last_value::text AS value\n FROM pg_sequences\n WHERE schemaname NOT IN ('pg_catalog', 'information_schema')\n AND schemaname != '${SNAPSHOT_SCHEMA}'\n AND last_value IS NOT NULL`,\n );\n\n await pglite.exec(\n `CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.__sequences (name text, value bigint)`,\n );\n for (const { name, value } of seqs) {\n await pglite.exec(\n `INSERT INTO ${SNAPSHOT_SCHEMA_IDENT}.__sequences VALUES (${name}, ${value})`,\n );\n }\n\n await pglite.exec('COMMIT');\n } catch (err) {\n await pglite.exec('ROLLBACK');\n await pglite.exec(`DROP SCHEMA IF EXISTS ${SNAPSHOT_SCHEMA_IDENT} CASCADE`);\n throw err;\n }\n\n hasSnapshot = true;\n };\n\n const resetSnapshot = async () => {\n hasSnapshot = false;\n await pglite.exec(`DROP SCHEMA IF EXISTS ${SNAPSHOT_SCHEMA_IDENT} CASCADE`);\n };\n\n const withReplicationRoleReplica = async (fn: () => Promise<void>) => {\n try {\n await pglite.exec('SET session_replication_role = replica');\n await fn();\n } finally {\n await pglite.exec('SET session_replication_role = DEFAULT');\n }\n };\n\n const resetDb = async () => {\n const tables = await getTables();\n\n if (tables) {\n await withReplicationRoleReplica(async () => {\n await pglite.exec(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE`);\n\n if (!hasSnapshot) return;\n\n const { rows: snapshotTables } = await pglite.query<{\n snap_name_ident: string;\n qualified: string;\n }>(\n `SELECT quote_ident(snap_name) AS snap_name_ident,\n quote_ident(source_schema) || '.' || quote_ident(source_table) AS qualified\n FROM ${SNAPSHOT_SCHEMA_IDENT}.__tables`,\n );\n\n for (const { snap_name_ident, qualified } of snapshotTables) {\n await pglite.exec(\n `INSERT INTO ${qualified} SELECT * FROM ${SNAPSHOT_SCHEMA_IDENT}.${snap_name_ident}`,\n );\n }\n\n const { rows: seqs } = await pglite.query<{ name: string; value: string }>(\n `SELECT quote_literal(name) AS name, value::text AS value FROM ${SNAPSHOT_SCHEMA_IDENT}.__sequences`,\n );\n\n for (const { name, value } of seqs) {\n await pglite.exec(`SELECT setval(${name}, ${value})`);\n }\n });\n }\n\n await pglite.exec('DISCARD ALL');\n };\n\n return { resetDb, resetSnapshot, snapshotDb };\n};\n","/**\n * Creates a PGliteBridge: a Prisma adapter, the underlying PGlite instance,\n * and lifecycle helpers, all backed by a caller-supplied PGlite instance.\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 { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushMigrations } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n * await pushMigrations(bridge, { migrationsPath: './prisma/migrations' });\n *\n * const prisma = new PrismaClient({ adapter: bridge.adapter });\n * beforeEach(() => bridge.resetDb());\n * ```\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { createPool, type SyncToFsMode } from './create-pool.ts';\nimport { BridgeStats, type Stats, type StatsLevel } from './utils/bridge-stats.ts';\nimport { createSnapshotManager } from './utils/snapshot.ts';\n\n/** @internal Exported for testing. */\nexport const emitBridgeLeakWarning = (): void => {\n process.emitWarning(\n 'PGliteBridge was garbage-collected before close() was called. ' +\n 'Call bridge.close() to release the pool and finalize stats().',\n { type: 'PGliteBridgeLeakWarning' },\n );\n};\n\nconst leakRegistry = new FinalizationRegistry<void>(emitBridgeLeakWarning);\n\nexport interface CreatePGliteBridgeOptions {\n /**\n * PGlite instance to bridge to. The caller owns its lifecycle — `close()`\n * shuts down the pool only, not the PGlite instance.\n */\n pglite: PGlite;\n\n /**\n * Maximum pool connections (default: 1). Compatibility knob, not a\n * throughput knob.\n *\n * PGlite serialises queries inside its WASM runtime. Extra pool connections\n * do not add parallelism; they only add bridge/client memory and\n * session-lock coordination. Leave this at `1` unless the code under test\n * specifically needs multiple checked-out `pg` clients.\n */\n max?: number;\n\n /**\n * Collect bridge/query telemetry. Default `'off'` (zero overhead).\n *\n * - `'basic'` — timing (`durationMs`, query percentiles) and counters\n * (`queryCount`, `failedQueryCount`, `resetDbCalls`), plus\n * `dbSizeBytes`.\n * - `'full'` — everything in `'basic'`, plus `processRssPeakBytes`\n * (process-wide, sampled) and session-lock wait statistics.\n *\n * Retrieve via `await bridge.stats()` — returns `undefined` at `'off'`.\n */\n statsLevel?: StatsLevel;\n\n /**\n * Filesystem sync policy for bridge-driven wire-protocol calls.\n *\n * Default `'auto'`: disables per-query sync for clearly in-memory PGlite\n * instances and keeps it enabled otherwise. Set `true` to prefer durability\n * on persistent stores, or `false` to prefer lower RSS / higher throughput.\n *\n * If you provide a custom persistent PGlite `fs` without a meaningful\n * `dataDir`, pass `true` explicitly.\n */\n syncToFs?: SyncToFsMode;\n}\n\n/** Snapshot of bridge/query telemetry. See {@link CreatePGliteBridgeOptions.statsLevel}. */\nexport type StatsFn = () => Promise<Stats | undefined>;\n\n/** Clear all user tables and discard session-local state. 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 type CloseFn = () => Promise<void>;\n\nexport interface PGliteBridge {\n /** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */\n adapter: PrismaPg;\n\n /**\n * The caller-supplied PGlite instance this bridge wraps. Exposed so\n * helpers like {@link pushMigrations} can run SQL directly through\n * `pglite.exec(...)` without going through the bridge pool.\n */\n pglite: PGlite;\n\n /**\n * Identity tag published on every `QUERY_CHANNEL` / `LOCK_WAIT_CHANNEL`\n * diagnostics event produced by this bridge. External subscribers\n * filter on it to isolate events from this bridge in multi-bridge\n * processes.\n */\n bridgeId: symbol;\n\n /** Clear all user tables and discard session-local state. Call in `beforeEach` for per-test isolation. */\n resetDb: ResetDbFn;\n\n /**\n * Snapshot the current DB state into an internal `_pglite_snapshot`\n * schema. Subsequent `resetDb` calls restore from this snapshot instead\n * of truncating to empty.\n *\n * **Concurrency:** runs multiple `exec()` statements directly against\n * the PGlite instance, bypassing the pool's `SessionLock`. Call from a\n * test `beforeAll` after migrations but before Prisma traffic starts;\n * invoking it while another pool connection is inside a transaction is\n * unsafe and may deadlock against PGlite's internal mutex.\n */\n snapshotDb: SnapshotDbFn;\n\n /**\n * Discard the current snapshot. Subsequent `resetDb` calls truncate to\n * empty. Same concurrency requirements as {@link snapshotDb}.\n */\n resetSnapshot: ResetSnapshotFn;\n\n /**\n * Shut down the pool. The caller-owned PGlite instance is not closed.\n *\n * When `statsLevel` is not `'off'`, call `stats()` *after* `close()` to\n * collect the frozen snapshot — `durationMs` and `dbSizeBytes` are cached\n * at the moment `close()` is invoked, and subsequent `stats()` calls are\n * safe.\n */\n close: CloseFn;\n\n /**\n * Retrieve collected telemetry. Returns `undefined` when `statsLevel` was\n * `'off'` (or omitted). Never throws — field-level failures surface as\n * `undefined` values (see {@link Stats}).\n */\n stats: StatsFn;\n}\n\n/**\n * Creates a `PGliteBridge` — a bundle holding a Prisma driver adapter,\n * the underlying PGlite instance, and lifecycle helpers — backed by a\n * caller-supplied PGlite instance.\n *\n * Schema application is a separate concern — call {@link pushMigrations}\n * (raw SQL / migrations directory) or {@link pushSchema} (WASM-engine\n * diff) before issuing Prisma traffic. When reopening a persistent\n * `dataDir`, the PGlite instance is assumed to already hold the schema\n * and no migration step is required.\n */\nexport const createPGliteBridge = async (\n options: CreatePGliteBridgeOptions,\n): Promise<PGliteBridge> => {\n const statsLevel = options.statsLevel ?? 'off';\n if (statsLevel !== 'off' && statsLevel !== 'basic' && statsLevel !== 'full') {\n throw new Error(`statsLevel must be 'off', 'basic', or 'full'; got ${String(statsLevel)}`);\n }\n const bridgeId = Symbol('bridge');\n const bridgeStats = statsLevel === 'off' ? undefined : new BridgeStats(statsLevel);\n\n const { pglite } = options;\n\n const { pool } = await createPool({\n pglite,\n max: options.max,\n bridgeId,\n syncToFs: options.syncToFs,\n telemetry: bridgeStats,\n });\n\n const adapter = new PrismaPg(pool);\n const snapshotManager = createSnapshotManager(pglite);\n\n const resetDb: ResetDbFn = async () => {\n bridgeStats?.incrementResetDb();\n await snapshotManager.resetDb();\n };\n\n const leakToken: object = {};\n\n let closing: Promise<void> | undefined;\n const close: CloseFn = async () => {\n if (!closing) {\n closing = (async () => {\n const closeEntry = bridgeStats ? process.hrtime.bigint() : undefined;\n await pool.end();\n if (bridgeStats && closeEntry !== undefined) {\n await bridgeStats.freeze(pglite, closeEntry);\n }\n leakRegistry.unregister(leakToken);\n })();\n }\n return closing;\n };\n\n const result: PGliteBridge = {\n adapter,\n bridgeId,\n pglite,\n close,\n resetDb,\n resetSnapshot: snapshotManager.resetSnapshot,\n snapshotDb: snapshotManager.snapshotDb,\n stats: async () => (bridgeStats ? bridgeStats.snapshot(pglite) : undefined),\n };\n\n // Track the lifetime of the Prisma adapter instance users actually retain.\n // The wrapper object returned by createPGliteBridge() is often ephemeral\n // (`const adapter = (await createPGliteBridge(...)).adapter`), so\n // registering that wrapper causes false leak warnings while Prisma still\n // holds the live adapter and pool.\n leakRegistry.register(adapter, undefined, leakToken);\n\n return result;\n};\n","/**\n * Apply pre-generated SQL (raw or from `prisma/migrations/`) to a PGlite\n * database. Sibling of {@link pushSchema} — same target shape, but no\n * schema engine and no `@prisma/schema-engine-wasm` import. Use when you\n * already have generated SQL and don't need a live schema diff.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushMigrations } from 'prisma-pglite-bridge';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n * await pushMigrations(bridge, { migrationsPath: './prisma/migrations' });\n * ```\n */\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport type { PGliteBridge } from './create-pglite-bridge.ts';\nimport type { SchemaTarget } from './schema.ts';\n\nexport interface PushMigrationsOptions {\n /** Pre-generated SQL to apply directly. */\n sql?: string;\n /** Path to a `prisma/migrations/` directory (auto-discovered via prisma.config.ts if omitted). */\n migrationsPath?: string;\n /** Root for prisma.config.ts discovery (default: process.cwd()). Set in monorepos where tests run from the workspace root. */\n configRoot?: string;\n}\n\nexport interface PushMigrationsResult {\n /** Wall-clock time of the SQL apply, in milliseconds. */\n durationMs: number;\n}\n\n/**\n * Resolve the migrations directory via Prisma's config API. Uses the same\n * resolution as `prisma migrate dev` — reads prisma.config.ts and resolves\n * paths relative to the config file's location.\n *\n * Returns undefined if @prisma/config is not available or the config\n * cannot be loaded.\n */\nexport const getMigrationsPath = async (configRoot?: string): Promise<string | undefined> => {\n try {\n const { loadConfigFromFile } = await import('@prisma/config');\n const { config, error } = await loadConfigFromFile({ configRoot: configRoot ?? process.cwd() });\n if (error) return undefined;\n\n if (config.migrations?.path) return config.migrations.path;\n\n const schemaPath = config.schema;\n if (schemaPath) return join(dirname(schemaPath), 'migrations');\n\n return undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Read and concatenate every `migration.sql` under a migrations directory in\n * directory order. Returns undefined if the directory doesn't exist or has no\n * migration files.\n */\nexport const readMigrationFiles = (migrationsPath: string): string | undefined => {\n if (!existsSync(migrationsPath)) return undefined;\n\n const dirs = readdirSync(migrationsPath)\n .filter((directory) => statSync(join(migrationsPath, directory)).isDirectory())\n .sort();\n\n const sqlParts: string[] = [];\n for (const directory of dirs) {\n const sqlPath = join(migrationsPath, directory, 'migration.sql');\n if (existsSync(sqlPath)) {\n sqlParts.push(readFileSync(sqlPath, 'utf8'));\n }\n }\n\n return sqlParts.length > 0 ? sqlParts.join('\\n') : undefined;\n};\n\n/**\n * Resolve schema SQL from {@link PushMigrationsOptions}. Priority:\n * 1. Explicit `sql`\n * 2. Explicit `migrationsPath` — read migration files\n * 3. Auto-discovered migrations via prisma.config.ts\n * 4. Throw — tell the caller to generate migration files\n */\nexport const getMigrationSQL = async (options: PushMigrationsOptions): Promise<string> => {\n if (options.sql) return options.sql;\n\n if (options.migrationsPath) {\n const sql = readMigrationFiles(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 const migrationsPath = await getMigrationsPath(options.configRoot);\n if (migrationsPath) {\n const sql = readMigrationFiles(migrationsPath);\n if (sql) return sql;\n\n throw new Error(\n `No migration.sql files found in auto-discovered path ${migrationsPath}. ` +\n 'Run `prisma migrate dev` to generate migration files, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n }\n\n if (options.configRoot) {\n throw new Error(\n `prisma.config.ts loaded from configRoot (${options.configRoot}) but no schema ` +\n 'or migrations path could be resolved. Ensure your config specifies a schema path, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n }\n\n throw new Error(\n 'No migration files found and no prisma.config.ts could be loaded. ' +\n 'Run `prisma migrate dev` to generate them, ' +\n 'or pass pre-generated SQL via the `sql` option.',\n );\n};\n\nconst isPGliteBridge = (target: SchemaTarget): target is PGliteBridge =>\n 'pglite' in target && 'adapter' in target;\n\n/**\n * Apply pre-generated SQL to the PGlite instance backing `target`.\n *\n * Runs the SQL through `pglite.exec(...)` directly, bypassing the bridge\n * pool. No schema engine, no WASM module, no diffing.\n *\n * Currently only {@link PGliteBridge} targets are supported. Raw `PrismaPg`\n * targets have no PGlite reference; pass the {@link PGliteBridge} returned\n * by {@link createPGliteBridge} instead.\n */\nexport const pushMigrations = async (\n target: SchemaTarget,\n options: PushMigrationsOptions = {},\n): Promise<PushMigrationsResult> => {\n if (!isPGliteBridge(target)) {\n throw new Error(\n 'pushMigrations requires a PGliteBridge target. Raw PrismaPg targets have no PGlite reference; pass the bridge returned by createPGliteBridge().',\n );\n }\n const sql = await getMigrationSQL(options);\n const start = process.hrtime.bigint();\n try {\n await target.pglite.exec(sql);\n } catch (err) {\n const dataDir = (target.pglite as { dataDir?: string }).dataDir;\n const where = dataDir ? `PGlite(dataDir=${dataDir})` : 'in-memory PGlite';\n throw new Error(\n `Failed to apply schema SQL to ${where}. Check your schema or migration files.`,\n { cause: err },\n );\n }\n return { durationMs: Number(process.hrtime.bigint() - start) / 1e6 };\n};\n","/**\n * Apply a Prisma schema to a PGlite database in-process via\n * `@prisma/schema-engine-wasm`. No native schema-engine binary, no TCP.\n *\n * `pushSchema` mirrors `prisma db push --skip-generate`: diff the schema\n * against the live database and apply the result. `resetSchema` drops\n * everything reachable through the connection.\n *\n * The schema engine WASM module is dynamically imported so consumers\n * who only use the bridge never load it.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushSchema } from 'prisma-pglite-bridge';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n *\n * await pushSchema(bridge, {\n * schema: await fs.readFile('prisma/schema.prisma', 'utf8'),\n * });\n * ```\n */\nimport type { PrismaPg } from '@prisma/adapter-pg';\n\nimport type { PGliteBridge } from './create-pglite-bridge.ts';\n\n/** A schema-apply target. Either a {@link PGliteBridge} from {@link createPGliteBridge} or a raw {@link PrismaPg}. */\nexport type SchemaTarget = PGliteBridge | PrismaPg;\n\nexport interface PushSchemaOptions {\n /** Inline Prisma schema source. */\n schema: string;\n /**\n * Drop everything reachable through the adapter before applying.\n * Issued as a separate `engine.reset(...)` call. Distinct from\n * {@link acceptDataLoss}. Default: `false`.\n */\n forceReset?: boolean;\n /**\n * Forwarded to `SchemaPushInput.force`. Apply the schema even when\n * the engine reports destructive-change warnings. Has no effect on\n * `unexecutable` steps. Default: `false`.\n */\n acceptDataLoss?: boolean;\n /** Logical filename used in schema-engine error messages. Default: `'schema.prisma'`. */\n filename?: string;\n}\n\nexport interface PushSchemaResult {\n /** Number of migration steps the engine applied. */\n executedSteps: number;\n /**\n * Destructive-change warnings reported by the engine. Suppressed\n * (i.e. applied anyway) when {@link PushSchemaOptions.acceptDataLoss} is true.\n */\n warnings: string[];\n /**\n * Steps the engine refused to run regardless of `acceptDataLoss`.\n * The caller must reshape the schema (e.g. add a default value) and retry.\n */\n unexecutable: string[];\n}\n\nconst unwrap = (target: SchemaTarget): PrismaPg => ('adapter' in target ? target.adapter : target);\n\nconst bindAdapter = async (target: SchemaTarget): Promise<object> => {\n const { bindMigrationAwareSqlAdapterFactory } = await import('@prisma/driver-adapter-utils');\n return bindMigrationAwareSqlAdapterFactory(unwrap(target));\n};\n\nconst emptyFilter = (): { externalTables: string[]; externalEnums: string[] } => ({\n externalTables: [],\n externalEnums: [],\n});\n\nconst quoteIdent = (name: string): string => `\"${name.replace(/\"/g, '\"\"')}\"`;\n\n/**\n * Drop every non-system schema (and recreate `public`). Issued as raw SQL\n * through the adapter rather than `engine.reset(...)` because the engine only\n * clears schemas declared in its datamodel — anything outside that (`base`,\n * test fixtures, etc.) would otherwise leak between resets.\n *\n * Each DROP runs through `executeRaw` rather than a single `executeScript`,\n * because `executeScript` splits on `;` and would mis-parse schema names that\n * contain a semicolon.\n */\nconst dropAllUserSchemas = async (target: SchemaTarget): Promise<void> => {\n const factory = unwrap(target);\n const conn = await factory.connect();\n try {\n const result = await conn.queryRaw({\n sql: `SELECT nspname FROM pg_namespace\n WHERE nspname NOT LIKE 'pg_%' AND nspname <> 'information_schema'`,\n args: [],\n argTypes: [],\n });\n const idx = result.columnNames.indexOf('nspname');\n const names = result.rows.map((row) => String(row[idx]));\n for (const name of names) {\n await conn.executeRaw({\n sql: `DROP SCHEMA IF EXISTS ${quoteIdent(name)} CASCADE`,\n args: [],\n argTypes: [],\n });\n }\n await conn.executeRaw({\n sql: `CREATE SCHEMA IF NOT EXISTS \"public\"`,\n args: [],\n argTypes: [],\n });\n } finally {\n await conn.dispose();\n }\n // The wrapper's snapshot manager keeps in-memory state about whether\n // `_pglite_snapshot` exists. Dropping the schema out from under it would\n // leave `hasSnapshot = true` and the next `resetDb()` call would query a\n // missing schema. `resetSnapshot()` is idempotent — safe to call when no\n // snapshot was ever taken.\n if ('resetSnapshot' in target) {\n await target.resetSnapshot();\n }\n};\n\nexport const pushSchema = async (\n target: SchemaTarget,\n options: PushSchemaOptions,\n): Promise<PushSchemaResult> => {\n const filename = options.filename ?? 'schema.prisma';\n if (options.forceReset) {\n await dropAllUserSchemas(target);\n }\n const { SchemaEngine } = await import('@prisma/schema-engine-wasm');\n const bound = await bindAdapter(target);\n\n const engine = await SchemaEngine.new(\n { datamodels: [[filename, options.schema]] },\n () => {},\n bound,\n );\n try {\n const result = await engine.schemaPush({\n force: options.acceptDataLoss ?? false,\n schema: { files: [{ path: filename, content: options.schema }] },\n filters: emptyFilter(),\n });\n return {\n executedSteps: result.executedSteps,\n warnings: result.warnings,\n unexecutable: result.unexecutable,\n };\n } finally {\n engine.free();\n }\n};\n\nexport const resetSchema = async (target: SchemaTarget): Promise<void> => {\n await dropAllUserSchemas(target);\n};\n","/**\n * prisma-pglite-bridge — in-process PGlite bridge for Prisma.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPGliteBridge, pushMigrations } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const bridge = await createPGliteBridge({ pglite });\n * await pushMigrations(bridge, { migrationsPath: './prisma/migrations' });\n *\n * const prisma = new PrismaClient({ adapter: bridge.adapter });\n * ```\n *\n * @packageDocumentation\n */\n\n// ── High-level API (most users only need this) ──\nexport type {\n CloseFn,\n CreatePGliteBridgeOptions,\n PGliteBridge,\n ResetDbFn,\n ResetSnapshotFn,\n SnapshotDbFn,\n StatsFn,\n} from './create-pglite-bridge.ts';\nexport { createPGliteBridge } from './create-pglite-bridge.ts';\nexport type { PushMigrationsOptions, PushMigrationsResult } from './migrations.ts';\nexport { pushMigrations } from './migrations.ts';\nexport type { PushSchemaOptions, PushSchemaResult, SchemaTarget } from './schema.ts';\nexport { pushSchema, resetSchema } from './schema.ts';\n\n// ── Low-level building blocks ──\nimport {\n type CreatePoolOptions as CreateBasePoolOptions,\n createPool as createBasePool,\n type PoolResult,\n} from './create-pool.ts';\n\nexport type { PoolResult };\n\n/**\n * Options for {@link createPool}. Identical to the internal pool options,\n * minus the library-private `telemetry` sink (consumers subscribe via\n * `node:diagnostics_channel` instead — see {@link QUERY_CHANNEL} and\n * {@link LOCK_WAIT_CHANNEL}).\n */\nexport type CreatePoolOptions = Omit<CreateBasePoolOptions, 'telemetry'>;\nexport type { SyncToFsMode } from './create-pool.ts';\n\n/**\n * Build a `pg.Pool` backed by a caller-supplied PGlite instance. Each pool\n * connection bridges through its own {@link PGliteDuplex} stream while\n * sharing one PGlite WASM runtime and session lock.\n *\n * Use this low-level entry point when you want a raw `pg.Pool` (for example\n * to wire into `@prisma/adapter-pg` yourself). Most users should prefer\n * {@link createPGliteBridge}, which layers schema setup and reset helpers\n * on top.\n */\nexport const createPool = async (options: CreatePoolOptions): Promise<PoolResult> =>\n createBasePool(options);\nexport { PGliteDuplex } from './pglite-duplex.ts';\nexport type { Stats, StatsBasic, StatsFull, StatsLevel } from './utils/bridge-stats.ts';\n// ── Diagnostics channels (public observability surface) ──\nexport {\n LOCK_WAIT_CHANNEL,\n type LockWaitEvent,\n QUERY_CHANNEL,\n type QueryEvent,\n} from './utils/diagnostics.ts';\nexport { SessionLock } from './utils/session-lock.ts';\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1166,7 +1166,7 @@ const createSnapshotManager = (pglite) => {
|
|
|
1166
1166
|
await pglite.exec(`CREATE TABLE ${SNAPSHOT_SCHEMA_IDENT}.${quoteIdent$1(snapName)} AS SELECT * FROM ${qualified}`);
|
|
1167
1167
|
await pglite.exec(`INSERT INTO ${SNAPSHOT_SCHEMA_IDENT}.__tables VALUES (${escapeLiteral(snapName)}, ${escapeLiteral(schemaname)}, ${escapeLiteral(tablename)})`);
|
|
1168
1168
|
}
|
|
1169
|
-
const { rows: seqs } = await pglite.query(`SELECT quote_literal(schemaname || '.' || sequencename) AS name, last_value::text AS value
|
|
1169
|
+
const { rows: seqs } = await pglite.query(`SELECT quote_literal(quote_ident(schemaname) || '.' || quote_ident(sequencename)) AS name, last_value::text AS value
|
|
1170
1170
|
FROM pg_sequences
|
|
1171
1171
|
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
|
|
1172
1172
|
AND schemaname != '${SNAPSHOT_SCHEMA}'
|