prisma-pglite-bridge 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/index.cjs +22 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.mts +8 -5
- package/dist/index.mjs +22 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -107,15 +107,20 @@ Returns:
|
|
|
107
107
|
Note: this clears all data including seed data — re-seed after
|
|
108
108
|
reset if needed.
|
|
109
109
|
- `close()` — shuts down the pool. The caller-supplied PGlite
|
|
110
|
-
instance is not closed — you own its lifecycle.
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
instance is not closed — you own its lifecycle. Recommended in
|
|
111
|
+
explicit test teardown, long-running scripts, and dev servers so
|
|
112
|
+
the pool is released promptly and leak warnings do not fire.
|
|
113
113
|
- `stats()` — returns telemetry when `statsLevel` is `'basic'` or
|
|
114
114
|
`'full'`, else `undefined`. See [Stats collection](#stats-collection).
|
|
115
115
|
- `adapterId` — a unique `symbol` identifying this adapter. Use it
|
|
116
116
|
to filter events from the public
|
|
117
117
|
[diagnostics channels](#diagnostics-channels) when multiple
|
|
118
118
|
adapters share a process.
|
|
119
|
+
- `snapshotDb()` — captures the current DB contents into an internal
|
|
120
|
+
snapshot so later `resetDb()` calls restore to that state instead of
|
|
121
|
+
truncating to empty.
|
|
122
|
+
- `resetSnapshot()` — discards the current snapshot so later
|
|
123
|
+
`resetDb()` calls truncate back to empty again.
|
|
119
124
|
|
|
120
125
|
### `createPool(options)`
|
|
121
126
|
|
|
@@ -137,7 +142,7 @@ Returns `pool` (pg.Pool), `adapterId` (a unique `symbol` for
|
|
|
137
142
|
[diagnostics channel](#diagnostics-channels) filtering), and
|
|
138
143
|
`close()` (which shuts down the pool only — the caller-supplied
|
|
139
144
|
PGlite instance is not closed). Accepts `pglite` (required),
|
|
140
|
-
`max`, and `
|
|
145
|
+
`max`, `adapterId`, and `syncToFs`.
|
|
141
146
|
|
|
142
147
|
### `PGliteBridge`
|
|
143
148
|
|
|
@@ -186,6 +191,7 @@ the in-memory PGlite version:
|
|
|
186
191
|
import { PGlite } from '@electric-sql/pglite';
|
|
187
192
|
import { createPgliteAdapter } from 'prisma-pglite-bridge';
|
|
188
193
|
import { PrismaClient } from '@prisma/client';
|
|
194
|
+
import { beforeEach, vi } from 'vitest';
|
|
189
195
|
|
|
190
196
|
const pglite = new PGlite();
|
|
191
197
|
const { adapter, resetDb } = await createPgliteAdapter({
|
package/dist/index.cjs
CHANGED
|
@@ -585,7 +585,10 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
585
585
|
const message = this.input.consume(len);
|
|
586
586
|
await this.acquireSession();
|
|
587
587
|
await this.pglite.runExclusive(async () => {
|
|
588
|
-
await this.
|
|
588
|
+
await this.streamProtocol(message, {
|
|
589
|
+
detectErrors: false,
|
|
590
|
+
suppressIntermediateRfq: false
|
|
591
|
+
});
|
|
589
592
|
});
|
|
590
593
|
this.phase = "ready";
|
|
591
594
|
}
|
|
@@ -623,7 +626,10 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
623
626
|
await this.flushPipeline();
|
|
624
627
|
continue;
|
|
625
628
|
}
|
|
626
|
-
await this.runWithTiming((detectErrors) => this.
|
|
629
|
+
await this.runWithTiming((detectErrors) => this.streamProtocol(message, {
|
|
630
|
+
detectErrors,
|
|
631
|
+
suppressIntermediateRfq: false
|
|
632
|
+
}));
|
|
627
633
|
}
|
|
628
634
|
}
|
|
629
635
|
/**
|
|
@@ -640,7 +646,10 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
640
646
|
const messages = this.pipeline;
|
|
641
647
|
this.pipeline = [];
|
|
642
648
|
const batch = concat(messages);
|
|
643
|
-
await this.runWithTiming((detectErrors) => this.
|
|
649
|
+
await this.runWithTiming((detectErrors) => this.streamProtocol(batch, {
|
|
650
|
+
detectErrors,
|
|
651
|
+
suppressIntermediateRfq: true
|
|
652
|
+
}));
|
|
644
653
|
}
|
|
645
654
|
/**
|
|
646
655
|
* Acquires the session, runs the op under `pglite.runExclusive`, and
|
|
@@ -694,42 +703,22 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
694
703
|
});
|
|
695
704
|
}
|
|
696
705
|
}
|
|
697
|
-
async runPipelineBatch(batch, detectErrors) {
|
|
698
|
-
let errSeen = false;
|
|
699
|
-
const framer = new BackendMessageFramer({
|
|
700
|
-
suppressIntermediateReadyForQuery: true,
|
|
701
|
-
onChunk: (chunk) => {
|
|
702
|
-
/* c8 ignore next — race-only: tornDown becomes true mid-stream */
|
|
703
|
-
if (!this.tornDown && chunk.length > 0) this.push(chunk);
|
|
704
|
-
},
|
|
705
|
-
onErrorResponse: () => {
|
|
706
|
-
if (detectErrors) errSeen = true;
|
|
707
|
-
},
|
|
708
|
-
onReadyForQuery: (status) => {
|
|
709
|
-
if (this.sessionLock) this.sessionLock.updateStatus(this.bridgeId, status);
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
await this.pglite.execProtocolRawStream(batch, {
|
|
713
|
-
syncToFs: this.syncToFs,
|
|
714
|
-
onRawData: (chunk) => {
|
|
715
|
-
/* c8 ignore next — race-only: tornDown becomes true mid-stream */
|
|
716
|
-
if (!this.tornDown) framer.write(chunk);
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
framer.flush({ dropHeldReadyForQuery: this.tornDown });
|
|
720
|
-
return !errSeen;
|
|
721
|
-
}
|
|
722
706
|
/**
|
|
723
|
-
* Sends a message to PGlite and pushes response
|
|
724
|
-
* stream as they arrive. Avoids collecting and
|
|
725
|
-
* multi-row responses (e.g., findMany 500 rows
|
|
707
|
+
* Sends a message (or pipelined batch) to PGlite and pushes response
|
|
708
|
+
* chunks directly to the stream as they arrive. Avoids collecting and
|
|
709
|
+
* concatenating for large multi-row responses (e.g., findMany 500 rows
|
|
710
|
+
* = ~503 onRawData chunks).
|
|
711
|
+
*
|
|
712
|
+
* For pipelined Extended Query batches, pass `suppressIntermediateRfq`
|
|
713
|
+
* so only the final ReadyForQuery reaches the client.
|
|
726
714
|
*
|
|
727
715
|
* Must be called inside runExclusive.
|
|
728
716
|
*/
|
|
729
|
-
async
|
|
717
|
+
async streamProtocol(message, options) {
|
|
718
|
+
const { detectErrors, suppressIntermediateRfq } = options;
|
|
730
719
|
let errSeen = false;
|
|
731
720
|
const framer = new BackendMessageFramer({
|
|
732
|
-
suppressIntermediateReadyForQuery:
|
|
721
|
+
suppressIntermediateReadyForQuery: suppressIntermediateRfq,
|
|
733
722
|
onChunk: (chunk) => {
|
|
734
723
|
/* c8 ignore next — race-only: tornDown becomes true mid-stream */
|
|
735
724
|
if (!this.tornDown && chunk.length > 0) this.push(chunk);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["diagnostics_channel","Duplex","createPool","createPool","PrismaPg","createBasePool"],"sources":["../src/utils/diagnostics.ts","../src/utils/time.ts","../src/pglite-bridge.ts","../src/bridge-client.ts","../src/utils/session-lock.ts","../src/create-pool.ts","../src/utils/adapter-stats.ts","../src/utils/migrations.ts","../src/utils/snapshot.ts","../src/create-pglite-adapter.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 * adapter 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 `adapterId` to distinguish events from different adapters\n * in the same process — obtain it from the `createPgliteAdapter` 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 /** Adapter identity tag — filter on this to isolate one adapter's events. */\n adapterId: 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 /** Adapter identity tag — filter on this to isolate one adapter's events. */\n adapterId: 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 bridge stream.\n *\n * A Duplex stream that replaces the TCP socket in pg.Client, routing\n * wire protocol messages directly to an in-process PGlite instance.\n *\n * pg.Client writes wire protocol bytes → bridge frames messages →\n * PGlite processes via execProtocolRawStream → bridge pushes responses back.\n *\n * Extended Query Protocol pipelines (Parse→Bind→Describe→Execute→Sync) are\n * concatenated into a single buffer and sent as one atomic execProtocolRawStream\n * call within one runExclusive. This prevents portal interleaving between\n * concurrent bridges AND reduces async overhead (1 WASM call instead of 5).\n *\n * The response from a batched pipeline contains spurious ReadyForQuery messages\n * after each sub-message (PGlite's single-user mode). These are stripped,\n * keeping only the final ReadyForQuery after Sync.\n */\nimport { Duplex } from 'node:stream';\nimport type { PGlite } from '@electric-sql/pglite';\nimport type { TelemetrySink } from './utils/adapter-stats.ts';\nimport { lockWaitChannel, queryChannel } from './utils/diagnostics.ts';\nimport type { BridgeId, 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)\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 * 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\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 {\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 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 this.emitChunkSlice(chunk, offset, offset + bytesToEmit);\n this.payloadBytesRemaining -= bytesToEmit;\n offset += bytesToEmit;\n }\n if (this.payloadBytesRemaining === 0) {\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 }\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 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()` / `createPgliteAdapter()`:\n *\n * ```typescript\n * const client = new pg.Client({\n * stream: () => new PGliteBridge(pglite),\n * });\n * ```\n */\nexport class PGliteBridge extends Duplex {\n private readonly pglite: PGlite;\n private readonly sessionLock?: SessionLock;\n private readonly adapterId?: symbol;\n private readonly telemetry?: TelemetrySink;\n private readonly syncToFs: boolean;\n private readonly bridgeId: BridgeId;\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 bridges. Omit for a standalone bridge.\n * @param adapterId Identity tag published with diagnostics-channel events.\n * Omit to disable channel publication for this bridge.\n * @param telemetry Internal sink used by `createPgliteAdapter` for built-in\n * stats. Not a public extension point — subscribe via\n * `node:diagnostics_channel` instead.\n * @param syncToFs Whether each bridged 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 adapterId?: symbol,\n telemetry?: TelemetrySink,\n syncToFs = true,\n ) {\n super();\n this.pglite = pglite;\n this.sessionLock = sessionLock;\n this.adapterId = adapterId;\n this.telemetry = telemetry;\n this.syncToFs = syncToFs;\n this.bridgeId = Symbol('bridge');\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.bridgeId);\n this.push(null);\n callback();\n }\n\n override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {\n this.tornDown = true;\n this.pipeline.length = 0;\n this.input.clear();\n this.sessionLock?.cancel(this.bridgeId, 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.bridgeId);\n } finally {\n this.draining = false;\n\n // Fire all waiting callbacks\n const callbacks = this.drainQueue;\n this.drainQueue = [];\n for (const cb of callbacks) {\n cb(error);\n }\n }\n }\n\n /**\n * Frames and processes the startup message.\n *\n * Format: [4 bytes: total length] [4 bytes: protocol version] [key\\0value\\0 pairs]\n * No type byte — length includes itself.\n */\n private async processPreStartup(): Promise<void> {\n 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 await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n await this.execAndPush(message, 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.bridgeId);\n this.push(null);\n return;\n }\n\n if (EQP_MESSAGES.has(msgType)) {\n this.pipeline.push(message);\n continue;\n }\n\n if (msgType === SYNC) {\n this.pipeline.push(message);\n await this.flushPipeline();\n continue;\n }\n\n // SimpleQuery or other standalone message\n await this.runWithTiming((detectErrors) => this.execAndPush(message, detectErrors));\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) => this.runPipelineBatch(batch, detectErrors));\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 `AdapterStats` 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.adapterId !== undefined && queryChannel.hasSubscribers;\n const publishLockWait = this.adapterId !== undefined && lockWaitChannel.hasSubscribers;\n const wantTiming = wantTelemetry || publishQuery || publishLockWait;\n const detectErrors = wantTelemetry || publishQuery;\n\n if (!wantTiming) {\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n await op(false);\n });\n return;\n }\n\n const lockStart = process.hrtime.bigint();\n await this.acquireSession();\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 adapterId: this.adapterId,\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 adapterId: this.adapterId,\n durationMs: queryMs,\n succeeded,\n });\n }\n }\n }\n\n private async runPipelineBatch(batch: Uint8Array, detectErrors: boolean): Promise<boolean> {\n let errSeen = false;\n const framer = new BackendMessageFramer({\n suppressIntermediateReadyForQuery: true,\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.bridgeId, status);\n }\n },\n });\n\n await this.pglite.execProtocolRawStream(batch, {\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 /**\n * Sends a message to PGlite and pushes response chunks directly to the\n * stream as they arrive. Avoids collecting and concatenating for large\n * multi-row responses (e.g., findMany 500 rows = ~503 onRawData chunks).\n *\n * Must be called inside runExclusive.\n */\n private async execAndPush(message: Uint8Array, detectErrors: boolean): Promise<boolean> {\n let errSeen = false;\n const framer = new BackendMessageFramer({\n suppressIntermediateReadyForQuery: false,\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.bridgeId, 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 framer.flush({ dropHeldReadyForQuery: this.tornDown });\n return !errSeen;\n }\n\n // ── Session lock helpers ──\n\n private async acquireSession(): Promise<void> {\n await this.sessionLock?.acquire(this.bridgeId);\n }\n}\n","import type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { PGliteBridge } from './pglite-bridge.ts';\nimport type { TelemetrySink } from './utils/adapter-stats.ts';\nimport type { SessionLock } from './utils/session-lock.ts';\n\nexport const bridgeClientOptionsKey: unique symbol = Symbol('bridgeClientOptions');\n\nexport interface BridgeClientOptions {\n pglite: PGlite;\n sessionLock: SessionLock;\n adapterId: symbol;\n telemetry?: TelemetrySink;\n syncToFs: boolean;\n}\n\nexport type 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 PGliteBridge(\n bridge.pglite,\n bridge.sessionLock,\n bridge.adapterId,\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 PGliteBridge}\n * and created automatically by {@link createPool}. Only instantiate directly\n * if building a custom pool setup.\n */\nexport class SessionLock {\n private owner?: BridgeId;\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 PGliteBridge stream, all sharing the\n * same PGlite WASM instance and SessionLock. The session lock ensures\n * transaction isolation: when one bridge starts a transaction (BEGIN),\n * it gets exclusive PGlite access until COMMIT/ROLLBACK. Non-transactional\n * operations from any bridge serialize through PGlite's runExclusive mutex.\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { BridgeClient, type BridgePoolConfig, bridgeClientOptionsKey } from './bridge-client.ts';\nimport type { TelemetrySink } from './utils/adapter-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).\n *\n * PGlite's WASM runtime executes queries serially behind a single mutex,\n * and every bridge connection shares the same {@link SessionLock}. Raising\n * `max` above 1 therefore does not add parallelism — queries still run\n * one at a time — and each extra connection costs a full `PGliteBridge`\n * (its framers and scratch buffers) in memory. Leave this at `1` unless\n * you are deliberately exercising wait-queue 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 adapters in the\n * same process. A fresh `Symbol('adapter')` is generated if omitted.\n */\n adapterId?: 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 `adapterId` option if supplied,\n * otherwise a freshly minted symbol. Filter on it from external\n * subscribers to isolate this pool's events.\n */\n adapterId: 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 createPgliteAdapter}, 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 createPgliteAdapter} 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 adapterId = options.adapterId ?? Symbol('adapter');\n const syncToFs = resolveSyncToFs(pglite, options.syncToFs);\n\n await pglite.waitReady;\n\n const sessionLock = new SessionLock();\n\n const poolConfig: BridgePoolConfig = {\n Client: BridgeClient,\n max,\n [bridgeClientOptionsKey]: {\n pglite,\n sessionLock,\n adapterId,\n telemetry,\n syncToFs,\n },\n };\n const pool = new pg.Pool(poolConfig);\n const close = () => pool.end();\n\n return { pool, adapterId, close };\n};\n","/**\n * Private per-adapter 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 (`markSchemaSetup`, `incrementResetDb`, `freeze`) remain direct\n * method calls invoked by the adapter 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 adapter 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`, `schemaSetupMs`), query\n * percentiles, counters, and `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 schemaSetupMs: 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 * adapters.\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 adapters, 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 AdapterStats 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 schemaSetupMs = 0;\n private schemaSetupSet = false;\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 markSchemaSetup(durationMs: number): void {\n if (this.schemaSetupSet) return;\n this.schemaSetupSet = true;\n this.schemaSetupMs = durationMs;\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 schemaSetupMs: this.schemaSetupMs,\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 { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nexport interface MigrationsOptions {\n /** Path to prisma/migrations/ directory (auto-discovered via prisma.config.ts if omitted) */\n migrationsPath?: string;\n\n /** Pre-generated SQL to apply instead of auto-generating from schema */\n sql?: string;\n\n /** Root directory for prisma.config.ts discovery (default: process.cwd()). Set this in monorepos where tests run from the workspace root. */\n configRoot?: string;\n}\n\n/**\n * Get the migrations directory via Prisma's config API.\n * Uses the same resolution as `prisma migrate dev` — reads prisma.config.ts,\n * resolves paths relative to config file location.\n *\n * Returns undefined if @prisma/config is not available or config 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 // Explicit migrations path from prisma.config.ts\n if (config.migrations?.path) return config.migrations.path;\n\n // Fallback: Prisma convention is {schemaDir}/migrations\n const schemaPath = config.schema;\n if (schemaPath) return join(dirname(schemaPath), 'migrations');\n\n return undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Read migration SQL files from a migrations directory in directory order.\n * Returns undefined if the directory doesn't exist or has no 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 * Get schema SQL. Priority:\n * 1. Explicit `sql` option — use directly\n * 2. Explicit `migrationsPath` — read migration files\n * 3. Auto-discovered migrations (via prisma.config.ts) — read migration files\n * 4. Error — tell the user to generate migration files\n */\nexport const getMigrationSQL = async (options: MigrationsOptions): 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","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 `createPgliteAdapter`'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 Prisma adapter 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 { createPgliteAdapter } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const { adapter, resetDb } = await createPgliteAdapter({\n * pglite,\n * migrationsPath: './prisma/migrations',\n * });\n * const prisma = new PrismaClient({ adapter });\n *\n * beforeEach(() => 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 { AdapterStats, type Stats, type StatsLevel } from './utils/adapter-stats.ts';\nimport { getMigrationSQL, type MigrationsOptions } from './utils/migrations.ts';\nimport { createSnapshotManager } from './utils/snapshot.ts';\nimport { nsToMs } from './utils/time.ts';\n\n/** @internal Exported for testing. */\nexport const emitAdapterLeakWarning = (): void => {\n process.emitWarning(\n 'PGlite adapter was garbage-collected before close() was called. ' +\n 'Call adapter.close() to release the pool and finalize stats().',\n { type: 'PgliteAdapterLeakWarning' },\n );\n};\n\nconst leakRegistry = new FinalizationRegistry<void>(emitAdapterLeakWarning);\n\nexport interface CreatePgliteAdapterOptions extends MigrationsOptions {\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).\n *\n * PGlite serialises queries inside its WASM runtime, so extra pool\n * connections cost memory without adding throughput.\n */\n max?: number;\n\n /**\n * Collect adapter/query telemetry. Default `'off'` (zero overhead).\n *\n * - `'basic'` — timing (`durationMs`, `schemaSetupMs`, query percentiles)\n * and counters (`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 adapter.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 adapter/query telemetry. See {@link CreatePgliteAdapterOptions.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 interface PgliteAdapter {\n /** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */\n adapter: PrismaPg;\n\n /**\n * Identity tag published on every `QUERY_CHANNEL` / `LOCK_WAIT_CHANNEL`\n * diagnostics event produced by this adapter's bridges. External\n * subscribers filter on it to isolate events from this adapter in\n * multi-adapter processes.\n */\n adapterId: 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: () => Promise<void>;\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 Prisma adapter backed by a caller-supplied PGlite instance.\n *\n * When migration config is provided (`sql`, `migrationsPath`, or\n * `configRoot`), the resolved SQL is applied once on construction.\n * Otherwise, the PGlite instance is assumed to already hold the schema —\n * useful for reopening a persistent `dataDir`.\n */\nexport const createPgliteAdapter = async (\n options: CreatePgliteAdapterOptions,\n): Promise<PgliteAdapter> => {\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 adapterId = Symbol('adapter');\n const adapterStats = statsLevel === 'off' ? undefined : new AdapterStats(statsLevel);\n\n const { pglite } = options;\n\n const { pool } = await createPool({\n pglite,\n max: options.max,\n adapterId,\n syncToFs: options.syncToFs,\n telemetry: adapterStats,\n });\n\n const hasMigrationConfig =\n options.sql !== undefined ||\n options.migrationsPath !== undefined ||\n options.configRoot !== undefined;\n\n const schemaStart = adapterStats ? process.hrtime.bigint() : undefined;\n if (hasMigrationConfig) {\n const sql = await getMigrationSQL(options);\n try {\n await pglite.exec(sql);\n } catch (err) {\n const target = pglite.dataDir ? `PGlite(dataDir=${pglite.dataDir})` : 'in-memory PGlite';\n throw new Error(\n `Failed to apply schema SQL to ${target}. Check your schema or migration files.`,\n { cause: err },\n );\n }\n }\n if (adapterStats && schemaStart !== undefined) {\n adapterStats.markSchemaSetup(nsToMs(process.hrtime.bigint() - schemaStart));\n }\n\n const adapter = new PrismaPg(pool);\n const snapshotManager = createSnapshotManager(pglite);\n\n const resetDb: ResetDbFn = async () => {\n adapterStats?.incrementResetDb();\n await snapshotManager.resetDb();\n };\n\n const leakToken: object = {};\n\n let closing: Promise<void> | undefined;\n const close = async () => {\n if (!closing) {\n closing = (async () => {\n const closeEntry = adapterStats ? process.hrtime.bigint() : undefined;\n await pool.end();\n if (adapterStats && closeEntry !== undefined) {\n await adapterStats.freeze(pglite, closeEntry);\n }\n leakRegistry.unregister(leakToken);\n })();\n }\n return closing;\n };\n\n const result: PgliteAdapter = {\n adapter,\n adapterId,\n close,\n resetDb,\n resetSnapshot: snapshotManager.resetSnapshot,\n snapshotDb: snapshotManager.snapshotDb,\n stats: async () => (adapterStats ? adapterStats.snapshot(pglite) : undefined),\n };\n\n // Track the lifetime of the Prisma adapter instance users actually retain.\n // The wrapper object returned by createPgliteAdapter() is often ephemeral\n // (`const adapter = (await createPgliteAdapter(...)).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 * prisma-pglite-bridge — in-process PGlite bridge for Prisma.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPgliteAdapter } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const { adapter, resetDb } = await createPgliteAdapter({\n * pglite,\n * migrationsPath: './prisma/migrations',\n * });\n * const prisma = new PrismaClient({ adapter });\n * ```\n *\n * @packageDocumentation\n */\n\n// ── High-level API (most users only need this) ──\nexport type {\n CreatePgliteAdapterOptions,\n PgliteAdapter,\n ResetDbFn,\n ResetSnapshotFn,\n SnapshotDbFn,\n StatsFn,\n} from './create-pglite-adapter.ts';\nexport { createPgliteAdapter } from './create-pglite-adapter.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 PGliteBridge} 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 createPgliteAdapter}, which layers schema setup and reset helpers\n * on top.\n */\nexport const createPool = async (options: CreatePoolOptions): Promise<PoolResult> =>\n createBasePool(options);\nexport { PGliteBridge } from './pglite-bridge.ts';\nexport type { Stats, StatsBasic, StatsFull, StatsLevel } from './utils/adapter-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","createPool","PrismaPg","createBasePool"],"sources":["../src/utils/diagnostics.ts","../src/utils/time.ts","../src/pglite-bridge.ts","../src/bridge-client.ts","../src/utils/session-lock.ts","../src/create-pool.ts","../src/utils/adapter-stats.ts","../src/utils/migrations.ts","../src/utils/snapshot.ts","../src/create-pglite-adapter.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 * adapter 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 `adapterId` to distinguish events from different adapters\n * in the same process — obtain it from the `createPgliteAdapter` 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 /** Adapter identity tag — filter on this to isolate one adapter's events. */\n adapterId: 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 /** Adapter identity tag — filter on this to isolate one adapter's events. */\n adapterId: 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 bridge stream.\n *\n * A Duplex stream that replaces the TCP socket in pg.Client, routing\n * wire protocol messages directly to an in-process PGlite instance.\n *\n * pg.Client writes wire protocol bytes → bridge frames messages →\n * PGlite processes via execProtocolRawStream → bridge pushes responses back.\n *\n * Extended Query Protocol pipelines (Parse→Bind→Describe→Execute→Sync) are\n * concatenated into a single buffer and sent as one atomic execProtocolRawStream\n * call within one runExclusive. This prevents portal interleaving between\n * concurrent bridges AND reduces async overhead (1 WASM call instead of 5).\n *\n * The response from a batched pipeline contains spurious ReadyForQuery messages\n * after each sub-message (PGlite's single-user mode). These are stripped,\n * keeping only the final ReadyForQuery after Sync.\n */\nimport { Duplex } from 'node:stream';\nimport type { PGlite } from '@electric-sql/pglite';\nimport type { TelemetrySink } from './utils/adapter-stats.ts';\nimport { lockWaitChannel, queryChannel } from './utils/diagnostics.ts';\nimport type { BridgeId, 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)\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 * 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\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 {\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 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 this.emitChunkSlice(chunk, offset, offset + bytesToEmit);\n this.payloadBytesRemaining -= bytesToEmit;\n offset += bytesToEmit;\n }\n if (this.payloadBytesRemaining === 0) {\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 }\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 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()` / `createPgliteAdapter()`:\n *\n * ```typescript\n * const client = new pg.Client({\n * stream: () => new PGliteBridge(pglite),\n * });\n * ```\n */\nexport class PGliteBridge extends Duplex {\n private readonly pglite: PGlite;\n private readonly sessionLock?: SessionLock;\n private readonly adapterId?: symbol;\n private readonly telemetry?: TelemetrySink;\n private readonly syncToFs: boolean;\n private readonly bridgeId: BridgeId;\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 bridges. Omit for a standalone bridge.\n * @param adapterId Identity tag published with diagnostics-channel events.\n * Omit to disable channel publication for this bridge.\n * @param telemetry Internal sink used by `createPgliteAdapter` for built-in\n * stats. Not a public extension point — subscribe via\n * `node:diagnostics_channel` instead.\n * @param syncToFs Whether each bridged 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 adapterId?: symbol,\n telemetry?: TelemetrySink,\n syncToFs = true,\n ) {\n super();\n this.pglite = pglite;\n this.sessionLock = sessionLock;\n this.adapterId = adapterId;\n this.telemetry = telemetry;\n this.syncToFs = syncToFs;\n this.bridgeId = Symbol('bridge');\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.bridgeId);\n this.push(null);\n callback();\n }\n\n override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {\n this.tornDown = true;\n this.pipeline.length = 0;\n this.input.clear();\n this.sessionLock?.cancel(this.bridgeId, 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.bridgeId);\n } finally {\n this.draining = false;\n\n // Fire all waiting callbacks\n const callbacks = this.drainQueue;\n this.drainQueue = [];\n for (const cb of callbacks) {\n cb(error);\n }\n }\n }\n\n /**\n * Frames and processes the startup message.\n *\n * Format: [4 bytes: total length] [4 bytes: protocol version] [key\\0value\\0 pairs]\n * No type byte — length includes itself.\n */\n private async processPreStartup(): Promise<void> {\n 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 await this.acquireSession();\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.bridgeId);\n this.push(null);\n return;\n }\n\n if (EQP_MESSAGES.has(msgType)) {\n this.pipeline.push(message);\n continue;\n }\n\n if (msgType === SYNC) {\n this.pipeline.push(message);\n await this.flushPipeline();\n continue;\n }\n\n // SimpleQuery or other standalone message\n await this.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 `AdapterStats` 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.adapterId !== undefined && queryChannel.hasSubscribers;\n const publishLockWait = this.adapterId !== undefined && lockWaitChannel.hasSubscribers;\n const wantTiming = wantTelemetry || publishQuery || publishLockWait;\n const detectErrors = wantTelemetry || publishQuery;\n\n if (!wantTiming) {\n await this.acquireSession();\n await this.pglite.runExclusive(async () => {\n await op(false);\n });\n return;\n }\n\n const lockStart = process.hrtime.bigint();\n await this.acquireSession();\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 adapterId: this.adapterId,\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 adapterId: this.adapterId,\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.bridgeId, 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 async acquireSession(): Promise<void> {\n await this.sessionLock?.acquire(this.bridgeId);\n }\n}\n","import type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { PGliteBridge } from './pglite-bridge.ts';\nimport type { TelemetrySink } from './utils/adapter-stats.ts';\nimport type { SessionLock } from './utils/session-lock.ts';\n\nexport const bridgeClientOptionsKey: unique symbol = Symbol('bridgeClientOptions');\n\nexport interface BridgeClientOptions {\n pglite: PGlite;\n sessionLock: SessionLock;\n adapterId: 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 PGliteBridge(\n bridge.pglite,\n bridge.sessionLock,\n bridge.adapterId,\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 PGliteBridge}\n * and created automatically by {@link createPool}. Only instantiate directly\n * if building a custom pool setup.\n */\nexport class SessionLock {\n private owner?: BridgeId;\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 PGliteBridge stream, all sharing the\n * same PGlite WASM instance and SessionLock. The session lock ensures\n * transaction isolation: when one bridge starts a transaction (BEGIN),\n * it gets exclusive PGlite access until COMMIT/ROLLBACK. Non-transactional\n * operations from any bridge serialize through PGlite's runExclusive mutex.\n */\nimport type { PGlite } from '@electric-sql/pglite';\nimport pg from 'pg';\nimport { BridgeClient, type BridgePoolConfig, bridgeClientOptionsKey } from './bridge-client.ts';\nimport type { TelemetrySink } from './utils/adapter-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).\n *\n * PGlite's WASM runtime executes queries serially behind a single mutex,\n * and every bridge connection shares the same {@link SessionLock}. Raising\n * `max` above 1 therefore does not add parallelism — queries still run\n * one at a time — and each extra connection costs a full `PGliteBridge`\n * (its framers and scratch buffers) in memory. Leave this at `1` unless\n * you are deliberately exercising wait-queue 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 adapters in the\n * same process. A fresh `Symbol('adapter')` is generated if omitted.\n */\n adapterId?: 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 `adapterId` option if supplied,\n * otherwise a freshly minted symbol. Filter on it from external\n * subscribers to isolate this pool's events.\n */\n adapterId: 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 createPgliteAdapter}, 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 createPgliteAdapter} 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 adapterId = options.adapterId ?? Symbol('adapter');\n const syncToFs = resolveSyncToFs(pglite, options.syncToFs);\n\n await pglite.waitReady;\n\n const sessionLock = new SessionLock();\n\n const poolConfig: BridgePoolConfig = {\n Client: BridgeClient,\n max,\n [bridgeClientOptionsKey]: {\n pglite,\n sessionLock,\n adapterId,\n telemetry,\n syncToFs,\n },\n };\n const pool = new pg.Pool(poolConfig);\n const close = () => pool.end();\n\n return { pool, adapterId, close };\n};\n","/**\n * Private per-adapter 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 (`markSchemaSetup`, `incrementResetDb`, `freeze`) remain direct\n * method calls invoked by the adapter 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 adapter 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`, `schemaSetupMs`), query\n * percentiles, counters, and `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 schemaSetupMs: 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 * adapters.\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 adapters, 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 AdapterStats 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 schemaSetupMs = 0;\n private schemaSetupSet = false;\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 markSchemaSetup(durationMs: number): void {\n if (this.schemaSetupSet) return;\n this.schemaSetupSet = true;\n this.schemaSetupMs = durationMs;\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 schemaSetupMs: this.schemaSetupMs,\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 { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nexport interface MigrationsOptions {\n /** Path to prisma/migrations/ directory (auto-discovered via prisma.config.ts if omitted) */\n migrationsPath?: string;\n\n /** Pre-generated SQL to apply instead of auto-generating from schema */\n sql?: string;\n\n /** Root directory for prisma.config.ts discovery (default: process.cwd()). Set this in monorepos where tests run from the workspace root. */\n configRoot?: string;\n}\n\n/**\n * Get the migrations directory via Prisma's config API.\n * Uses the same resolution as `prisma migrate dev` — reads prisma.config.ts,\n * resolves paths relative to config file location.\n *\n * Returns undefined if @prisma/config is not available or config 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 // Explicit migrations path from prisma.config.ts\n if (config.migrations?.path) return config.migrations.path;\n\n // Fallback: Prisma convention is {schemaDir}/migrations\n const schemaPath = config.schema;\n if (schemaPath) return join(dirname(schemaPath), 'migrations');\n\n return undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Read migration SQL files from a migrations directory in directory order.\n * Returns undefined if the directory doesn't exist or has no 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 * Get schema SQL. Priority:\n * 1. Explicit `sql` option — use directly\n * 2. Explicit `migrationsPath` — read migration files\n * 3. Auto-discovered migrations (via prisma.config.ts) — read migration files\n * 4. Error — tell the user to generate migration files\n */\nexport const getMigrationSQL = async (options: MigrationsOptions): 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","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 `createPgliteAdapter`'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 Prisma adapter 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 { createPgliteAdapter } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const { adapter, resetDb } = await createPgliteAdapter({\n * pglite,\n * migrationsPath: './prisma/migrations',\n * });\n * const prisma = new PrismaClient({ adapter });\n *\n * beforeEach(() => 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 { AdapterStats, type Stats, type StatsLevel } from './utils/adapter-stats.ts';\nimport { getMigrationSQL, type MigrationsOptions } from './utils/migrations.ts';\nimport { createSnapshotManager } from './utils/snapshot.ts';\nimport { nsToMs } from './utils/time.ts';\n\n/** @internal Exported for testing. */\nexport const emitAdapterLeakWarning = (): void => {\n process.emitWarning(\n 'PGlite adapter was garbage-collected before close() was called. ' +\n 'Call adapter.close() to release the pool and finalize stats().',\n { type: 'PgliteAdapterLeakWarning' },\n );\n};\n\nconst leakRegistry = new FinalizationRegistry<void>(emitAdapterLeakWarning);\n\nexport interface CreatePgliteAdapterOptions extends MigrationsOptions {\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).\n *\n * PGlite serialises queries inside its WASM runtime, so extra pool\n * connections cost memory without adding throughput.\n */\n max?: number;\n\n /**\n * Collect adapter/query telemetry. Default `'off'` (zero overhead).\n *\n * - `'basic'` — timing (`durationMs`, `schemaSetupMs`, query percentiles)\n * and counters (`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 adapter.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 adapter/query telemetry. See {@link CreatePgliteAdapterOptions.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 interface PgliteAdapter {\n /** Prisma adapter — pass directly to `new PrismaClient({ adapter })` */\n adapter: PrismaPg;\n\n /**\n * Identity tag published on every `QUERY_CHANNEL` / `LOCK_WAIT_CHANNEL`\n * diagnostics event produced by this adapter's bridges. External\n * subscribers filter on it to isolate events from this adapter in\n * multi-adapter processes.\n */\n adapterId: 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: () => Promise<void>;\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 Prisma adapter backed by a caller-supplied PGlite instance.\n *\n * When migration config is provided (`sql`, `migrationsPath`, or\n * `configRoot`), the resolved SQL is applied once on construction.\n * Otherwise, the PGlite instance is assumed to already hold the schema —\n * useful for reopening a persistent `dataDir`.\n */\nexport const createPgliteAdapter = async (\n options: CreatePgliteAdapterOptions,\n): Promise<PgliteAdapter> => {\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 adapterId = Symbol('adapter');\n const adapterStats = statsLevel === 'off' ? undefined : new AdapterStats(statsLevel);\n\n const { pglite } = options;\n\n const { pool } = await createPool({\n pglite,\n max: options.max,\n adapterId,\n syncToFs: options.syncToFs,\n telemetry: adapterStats,\n });\n\n const hasMigrationConfig =\n options.sql !== undefined ||\n options.migrationsPath !== undefined ||\n options.configRoot !== undefined;\n\n const schemaStart = adapterStats ? process.hrtime.bigint() : undefined;\n if (hasMigrationConfig) {\n const sql = await getMigrationSQL(options);\n try {\n await pglite.exec(sql);\n } catch (err) {\n const target = pglite.dataDir ? `PGlite(dataDir=${pglite.dataDir})` : 'in-memory PGlite';\n throw new Error(\n `Failed to apply schema SQL to ${target}. Check your schema or migration files.`,\n { cause: err },\n );\n }\n }\n if (adapterStats && schemaStart !== undefined) {\n adapterStats.markSchemaSetup(nsToMs(process.hrtime.bigint() - schemaStart));\n }\n\n const adapter = new PrismaPg(pool);\n const snapshotManager = createSnapshotManager(pglite);\n\n const resetDb: ResetDbFn = async () => {\n adapterStats?.incrementResetDb();\n await snapshotManager.resetDb();\n };\n\n const leakToken: object = {};\n\n let closing: Promise<void> | undefined;\n const close = async () => {\n if (!closing) {\n closing = (async () => {\n const closeEntry = adapterStats ? process.hrtime.bigint() : undefined;\n await pool.end();\n if (adapterStats && closeEntry !== undefined) {\n await adapterStats.freeze(pglite, closeEntry);\n }\n leakRegistry.unregister(leakToken);\n })();\n }\n return closing;\n };\n\n const result: PgliteAdapter = {\n adapter,\n adapterId,\n close,\n resetDb,\n resetSnapshot: snapshotManager.resetSnapshot,\n snapshotDb: snapshotManager.snapshotDb,\n stats: async () => (adapterStats ? adapterStats.snapshot(pglite) : undefined),\n };\n\n // Track the lifetime of the Prisma adapter instance users actually retain.\n // The wrapper object returned by createPgliteAdapter() is often ephemeral\n // (`const adapter = (await createPgliteAdapter(...)).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 * prisma-pglite-bridge — in-process PGlite bridge for Prisma.\n *\n * @example\n * ```typescript\n * import { PGlite } from '@electric-sql/pglite';\n * import { createPgliteAdapter } from 'prisma-pglite-bridge';\n * import { PrismaClient } from '@prisma/client';\n *\n * const pglite = new PGlite();\n * const { adapter, resetDb } = await createPgliteAdapter({\n * pglite,\n * migrationsPath: './prisma/migrations',\n * });\n * const prisma = new PrismaClient({ adapter });\n * ```\n *\n * @packageDocumentation\n */\n\n// ── High-level API (most users only need this) ──\nexport type {\n CreatePgliteAdapterOptions,\n PgliteAdapter,\n ResetDbFn,\n ResetSnapshotFn,\n SnapshotDbFn,\n StatsFn,\n} from './create-pglite-adapter.ts';\nexport { createPgliteAdapter } from './create-pglite-adapter.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 PGliteBridge} 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 createPgliteAdapter}, which layers schema setup and reset helpers\n * on top.\n */\nexport const createPool = async (options: CreatePoolOptions): Promise<PoolResult> =>\n createBasePool(options);\nexport { PGliteBridge } from './pglite-bridge.ts';\nexport type { Stats, StatsBasic, StatsFull, StatsLevel } from './utils/adapter-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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"}
|