UNPKG

@prisma/ppg

Version:

Lightweight client for Prisma Postgres

1,229 lines (1,215 loc) 38.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name); var __typeError = (msg) => { throw TypeError(msg); }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __using = (stack, value, async) => { if (value != null) { if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected"); var dispose, inner; if (async) dispose = value[__knownSymbol("asyncDispose")]; if (dispose === void 0) { dispose = value[__knownSymbol("dispose")]; if (async) inner = dispose; } if (typeof dispose !== "function") __typeError("Object not disposable"); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; stack.push([async, dispose, value]); } else if (async) { stack.push([async]); } return value; }; var __callDispose = (stack, error, hasError) => { var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) { return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _; }; var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e); var next = (it) => { while (it = stack.pop()) { try { var result = it[1] && it[1].call(it[2]); if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next())); } catch (e) { fail(e); } } if (hasError) throw error; }; return next(); }; // src/index.ts var index_exports = {}; __export(index_exports, { BINARY: () => BINARY, DatabaseError: () => DatabaseError, GenericError: () => GenericError, HttpResponseError: () => HttpResponseError, TEXT: () => TEXT, ValidationError: () => ValidationError, WebSocketError: () => WebSocketError, boundedByteStreamParameter: () => boundedByteStreamParameter, byteArrayParameter: () => byteArrayParameter, client: () => client, defaultClientConfig: () => defaultClientConfig, prismaPostgres: () => prismaPostgres }); module.exports = __toCommonJS(index_exports); // src/common/types.ts var BINARY = "binary"; var TEXT = "text"; function boundedByteStreamParameter(readableStream, format, byteLength) { return Object.assign(readableStream, { byteLength, format }); } function isBoundedByteStreamParameter(x) { return x instanceof ReadableStream && "byteLength" in x && typeof x.byteLength === "number" && hasFormat(x); } function hasFormat(x) { return "format" in x && (x.format === TEXT || x.format === BINARY); } function byteArrayParameter(array, format) { return Object.assign(array, { format }); } function isByteArrayParameter(x) { return x instanceof Uint8Array && hasFormat(x); } function toCollectableIterator(iterator, transform) { let collected = false; const transformFn = transform ?? ((item) => item); const collectableIterator = { async next() { if (collected) { return { value: void 0, done: true }; } const result = await iterator.next(); if (result.done) { return { value: void 0, done: true }; } return { value: transformFn(result.value), done: false }; }, async collect() { if (collected) { return []; } collected = true; const results = []; for await (const item of iterator) { results.push(transformFn(item)); } return results; }, [Symbol.asyncIterator]() { return collectableIterator; }, async return(value) { collected = true; await iterator.return?.(value); return { value: void 0, done: true }; }, async throw(error) { collected = true; await iterator.throw?.(error); return Promise.reject(error); } }; return collectableIterator; } var GenericError = class extends Error { constructor(msg, opts) { super(msg, opts); this.name = new.target.name; } }; var ValidationError = class extends GenericError { constructor(msg, opts) { super(msg, opts); this.name = new.target.name; } }; var HttpResponseError = class extends GenericError { /** HTTP status code from the failed request */ status; constructor({ message, statusCode }, opts) { super(message, opts); this.name = new.target.name; this.status = statusCode; } }; var WebSocketError = class extends GenericError { /** WebSocket closure code (e.g., 1000 for normal closure) */ closureCode; /** Human-readable closure reason */ closureReason; constructor({ message, closureCode, closureReason }, opts) { super(`${message}${closureStr(closureCode, closureReason)}`, opts); this.name = new.target.name; this.closureCode = closureCode; this.closureReason = closureReason; } }; function closureStr(closureCode, closureReason) { return !closureCode && !closureReason ? "" : ` (${closureCode} : ${closureReason})`; } var DatabaseError = class extends GenericError { /** PostgreSQL error code (SQLSTATE) */ code; /** Additional error details from PostgreSQL (severity, hint, etc.) */ details; constructor(details, opts) { super(details.message, opts); this.code = details.code; this.details = { ...details }; delete this.details.code; delete this.details.message; this.name = new.target.name; } }; // src/transport/shims.ts async function* emptyIterableIterator() { } function createDeferred() { if ("withResolvers" in Promise && typeof Promise.withResolvers === "function") { return Promise.withResolvers(); } let resolve; let reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; } async function createWebSocket(url, protocols) { if (typeof WebSocket !== "undefined") { return new WebSocket(url, protocols); } try { const WS = await import("ws"); const WebSocketImpl = WS.WebSocket || WS.default; return new WebSocketImpl(url, protocols); } catch (error) { throw new GenericError( 'WebSocket is not available. For Node.js < 21, please install the "ws" package: npm install ws', { cause: error } ); } } var MAX_BUFFERED_AMOUNT = 1024 * 1024; function wsBusyCheck(ws) { return "bufferedAmount" in ws && typeof ws.bufferedAmount === "number" ? () => ws.bufferedAmount > MAX_BUFFERED_AMOUNT : () => false; } var hasBufferByteLength = typeof Buffer !== "undefined" && typeof Buffer.byteLength === "function"; function utf8ByteLength(str) { if (hasBufferByteLength) { return Buffer.byteLength(str, "utf8"); } let bytes = 0; for (let i = 0; i < str.length; i++) { let codePoint = str.charCodeAt(i); if (codePoint >= 55296 && codePoint <= 56319) { const next = str.charCodeAt(i + 1); if (next >= 56320 && next <= 57343) { codePoint = (codePoint - 55296 << 10) + (next - 56320) + 65536; i++; } } if (codePoint <= 127) { bytes += 1; } else if (codePoint <= 2047) { bytes += 2; } else if (codePoint <= 65535) { bytes += 3; } else { bytes += 4; } } return bytes; } // src/transport/frames.ts function isNonNull(x) { return !!x && typeof x === "object"; } function isQueryDescriptorFrame(frame) { return isNonNull(frame) && ("query" in frame && typeof frame.query === "string" || "exec" in frame && typeof frame.exec === "string"); } function isExtendedParamFrame(frame) { return isNonNull(frame) && "type" in frame && "data" in frame && (frame.type === "text" || frame.type === "binary"); } function isDataRowDescription(frame) { return isNonNull(frame) && "columns" in frame && Array.isArray(frame.columns); } function isDataRow(frame) { return isNonNull(frame) && "values" in frame && Array.isArray(frame.values); } function isCommandComplete(frame) { return isNonNull(frame) && "complete" in frame && frame.complete === true; } function isErrorFrame(frame) { return isNonNull(frame) && "error" in frame && typeof frame.error === "object" && frame.error !== null && "message" in frame.error && typeof frame.error.message === "string"; } var EXENDED_PARAM_SIZE_THRESHOLD = 1 << 10; async function streamToBase64(stream) { const reader = stream.getReader(); const chunks = []; let totalLength = 0; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); totalLength += value.length; } const combined = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.length; } const binaryString = Array.from(combined, (byte) => String.fromCharCode(byte)).join(""); return btoa(binaryString); } async function requestFrames(kind, sql, rawParams) { const queryParams = []; const extendedFrames = []; for (const param of rawParams) { if (typeof param === "string") { const byteLength = utf8ByteLength(param); if (byteLength <= EXENDED_PARAM_SIZE_THRESHOLD) { queryParams.push({ type: TEXT, value: param }); } else { queryParams.push({ type: TEXT, byteSize: byteLength }); const stringStream = new ReadableStream({ start(controller) { controller.enqueue(param); controller.close(); } }); const encoderStream = new TextEncoderStream(); const encodedStream = stringStream.pipeThrough(encoderStream); const boundedStream = boundedByteStreamParameter(encodedStream, TEXT, byteLength); extendedFrames.push({ type: TEXT, data: boundedStream }); } } else if (isByteArrayParameter(param)) { const byteLength = param.byteLength; const format = param.format; if (byteLength <= EXENDED_PARAM_SIZE_THRESHOLD) { if (format === TEXT) { const textValue = new TextDecoder().decode(param); queryParams.push({ type: TEXT, value: textValue }); } else { const binaryString = Array.from(param, (byte) => String.fromCharCode(byte)).join(""); const base64 = btoa(binaryString); queryParams.push({ type: BINARY, value: base64 }); } } else { queryParams.push({ type: format, byteSize: byteLength }); extendedFrames.push({ type: format, data: param }); } } else if (isBoundedByteStreamParameter(param)) { const format = param.format; if (param.byteLength <= EXENDED_PARAM_SIZE_THRESHOLD) { if (format === TEXT) { const chunks = []; const reader = param.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); } const combined = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0)); let offset = 0; for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.length; } const textValue = new TextDecoder().decode(combined); queryParams.push({ type: TEXT, value: textValue }); } else { const base64 = await streamToBase64(param); queryParams.push({ type: BINARY, value: base64 }); } } else { queryParams.push({ type: format, byteSize: param.byteLength }); extendedFrames.push({ type: format, data: param }); } } else if (param === null) { queryParams.push({ type: TEXT, value: null }); } else { throw new ValidationError(`unsupported raw parameter type: ${param}`); } } const parameters = queryParams.length > 0 ? queryParams : void 0; const queryDescriptor = kind === "query" ? { query: sql, parameters } : { exec: sql, parameters }; return [queryDescriptor, ...extendedFrames]; } // src/transport/shared.ts var FRAME_URNS = { queryUrn: "urn:prisma:query", queryDescriptorUrn: "urn:prisma:query:descriptor", binaryParamUrn: "urn:prisma:query:param:binary", textParamUrn: "urn:prisma:query:param:text", dataRowDescriptionUrn: "urn:prisma:query:result:description", dataRowUrn: "urn:prisma:query:result:datarow", commandCompleteUrn: "urn:prisma:query:result:complete", errorUrn: "urn:prisma:query:result:error" }; var MIME_TYPES = { applicationJson: "application/json", applicationNdJson: "application/x-ndjson", applicationOctetStream: "application/octet-stream", multipartFormData: "multipart/form-data", multipartMixed: "multipart/mixed", textPlain: "text/plain" }; // src/transport/multipart.ts function createMultipartStream(frames, boundary) { const textEncoder = new TextEncoder(); async function* multipartGenerator() { for (const frame of frames) { yield textEncoder.encode(`--${boundary}\r `); if (isQueryDescriptorFrame(frame)) { const descriptor = frame; yield textEncoder.encode(`Content-Disposition: form-data; name="${FRAME_URNS.queryDescriptorUrn}"\r `); yield textEncoder.encode( `Content-Type: ${MIME_TYPES.applicationJson}; profile="${FRAME_URNS.queryDescriptorUrn}"\r \r ` ); yield textEncoder.encode(`${JSON.stringify(descriptor)}\r `); } else if (isExtendedParamFrame(frame)) { const extendedParam = frame; if (extendedParam.type === "text") { yield textEncoder.encode(`Content-Disposition: form-data; name="${FRAME_URNS.textParamUrn}"\r `); yield textEncoder.encode( `Content-Type: ${MIME_TYPES.textPlain}; charset=utf-8; profile="${FRAME_URNS.textParamUrn}"\r \r ` ); const param = extendedParam.data; if (typeof param === "string") { yield textEncoder.encode(param); yield textEncoder.encode("\r\n"); } else if (isByteArrayParameter(param)) { yield param; yield textEncoder.encode("\r\n"); } else if (isBoundedByteStreamParameter(param)) { const reader = param.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; yield value; } } finally { reader.releaseLock(); } yield textEncoder.encode("\r\n"); } else { throw new ValidationError( `Unsupported text extended parameter data type. Expected string, ByteArrayParameter, or BoundedByteStream, got: ${typeof param}` ); } } else { yield textEncoder.encode(`Content-Disposition: form-data; name="${FRAME_URNS.binaryParamUrn}"\r `); yield textEncoder.encode( `Content-Type: ${MIME_TYPES.applicationOctetStream}; profile="${FRAME_URNS.binaryParamUrn}"\r \r ` ); if (typeof extendedParam.data === "string") { yield textEncoder.encode(extendedParam.data); yield textEncoder.encode("\r\n"); } else if (isByteArrayParameter(extendedParam.data)) { yield extendedParam.data; yield textEncoder.encode("\r\n"); } else if (isBoundedByteStreamParameter(extendedParam.data)) { const reader = extendedParam.data.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; yield value; } } finally { reader.releaseLock(); } yield textEncoder.encode("\r\n"); } else { throw new ValidationError( `Unsupported binary extended parameter data type. Expected string, ByteArrayParameter, or BoundedByteStream, got: ${typeof extendedParam.data}` ); } } } else { throw new ValidationError( `Unsupported frame type. Expected QueryDescriptorFrame or ExtendedParamFrame, got: ${JSON.stringify(frame)}` ); } } yield textEncoder.encode(`--${boundary}--\r `); } const iterator = multipartGenerator(); return new ReadableStream({ async pull(controller) { try { const { done, value } = await iterator.next(); if (done) { controller.close(); } else { controller.enqueue(value); } } catch (error) { controller.error(error); } }, async cancel(reason) { await iterator.return?.(reason); } }); } // src/transport/ndjson.ts async function parseNDJSONResponse(response) { if (!response.body) { throw new Error("Response body is null"); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let columns = []; async function* rowGenerator() { try { while (true) { const { done, value } = await reader.read(); if (value) { buffer += decoder.decode(value, { stream: !done }); } const lines = buffer.split("\n"); if (!done) { buffer = lines.pop() || ""; } else { buffer = ""; } for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; const frame = JSON.parse(trimmed); if (isDataRowDescription(frame)) { columns = frame.columns.map((col) => ({ name: col.name, oid: col.typeOid })); } else if (isDataRow(frame)) { yield frame.values; } else if (isCommandComplete(frame)) { return; } else if (isErrorFrame(frame)) { throw new DatabaseError(frame.error); } } if (done) { break; } } } finally { reader.releaseLock(); } } const generator = rowGenerator(); const firstResult = await generator.next(); async function* rowsWithFirst() { if (!firstResult.done && firstResult.value !== void 0) { yield firstResult.value; } yield* generator; } const rows = toCollectableIterator(rowsWithFirst()); return { columns, rows }; } // src/transport/http.ts var HTTP_REQUEST_PATH = "/v0/statement"; function httpTransport(config) { async function statement(kind, sql, parameters) { const frames = await requestFrames(kind, sql, parameters); const url = new URL(HTTP_REQUEST_PATH, config.endpoint); if (config.database) { url.searchParams.set("db", config.database); } const authHeader = `Basic ${btoa(`${config.username}:${config.password}`)}`; const headers = { Authorization: authHeader }; return request({ url: url.toString(), headers, frames, keepalive: config.keepalive ?? false }); } return { statement }; } async function request({ headers, keepalive, frames, url }) { const boundary = `----PPGBoundary${Date.now()}${Math.random().toString(36).substring(2)}`; const bodyStream = createMultipartStream(frames, boundary); const requestInit = { method: "POST", headers: { ...headers, "Content-Type": `${MIME_TYPES.multipartFormData}; profile="${FRAME_URNS.queryUrn}"; boundary=${boundary}` }, body: bodyStream, duplex: "half", keepalive // node doesn't seem to support it, throws a TypeError when this is true }; const response = await fetch(url, requestInit); if (!response.ok) { const responseText = await response.text(); const message = responseText || `HTTP error ${response.status}: ${response.statusText}`; throw new HttpResponseError({ message, statusCode: response.status }); } return parseNDJSONResponse(response); } // src/transport/query-queue.ts function newRunningQuery() { const statementDeferred = createDeferred(); const rowBuffer = []; let completed = false; let rowDeferred; let columns = []; function tryResolveRow(value) { if (rowDeferred) { rowDeferred.resolve(value); rowDeferred = null; return true; } return false; } function tryRejectRow(error2) { if (rowDeferred) { rowDeferred.reject(error2); rowDeferred = null; return true; } return false; } async function* rowGenerator() { while (true) { if (rowBuffer.length > 0) { yield rowBuffer.shift(); continue; } if (completed) { break; } rowDeferred = createDeferred(); const result = await rowDeferred.promise; if (result.done) break; yield result.value; } } function rowDescription(descr) { columns = descr.columns.map((col) => ({ name: col.name, oid: col.typeOid })); statementDeferred.resolve({ columns, rows: toCollectableIterator(rowGenerator()) }); } function dataRow(row) { if (!tryResolveRow({ value: row.values, done: false })) { rowBuffer.push(row.values); } } function error({ error: error2 }) { const err = new DatabaseError(error2); abort(err); } function complete() { completed = true; tryResolveRow({ value: void 0, done: true }); statementDeferred.resolve({ columns, rows: toCollectableIterator(emptyIterableIterator()) }); } function abort(err) { if (!tryRejectRow(err)) { statementDeferred.reject(err); } } return { rowDescription, dataRow, error, complete, abort, statementPromise: statementDeferred.promise }; } function newQueryQueue() { const queryQueue = []; function enqueueNew() { const query = newRunningQuery(); queryQueue.push(query); return { promise: query.statementPromise, abort: query.abort }; } function processFrame(urn, payload) { if (queryQueue.length === 0) return; const currentQuery = queryQueue[0]; if (urn === FRAME_URNS.dataRowDescriptionUrn && isDataRowDescription(payload)) { currentQuery.rowDescription(payload); } else if (urn === FRAME_URNS.dataRowUrn && isDataRow(payload)) { currentQuery.dataRow(payload); } else if (urn === FRAME_URNS.commandCompleteUrn && isCommandComplete(payload)) { currentQuery.complete(); queryQueue.shift(); } else if (urn === FRAME_URNS.errorUrn && isErrorFrame(payload)) { currentQuery.error(payload); queryQueue.shift(); } else { const protocolError = new Error(`Protocol error: unexpected frame URN '${urn}' for current query state`); abortAll(protocolError); } } function abortAll(error) { for (const q of queryQueue) { q.abort(error); } queryQueue.length = 0; } function isEmpty() { return queryQueue.length === 0; } return { isEmpty, enqueueNew, processFrame, abortAll }; } // src/transport/websocket-conn.ts var WS_REQUEST_PATH = "/v0/session"; var WS_SUBPROTOCOL = "prisma-postgres-1.0"; var MAX_BACKOFF_MS = 100; async function wsTransportConnection(config) { const url = new URL(WS_REQUEST_PATH, config.endpoint); url.protocol = url.protocol.replace("http", "ws"); if (config.database) { url.searchParams.set("db", config.database); } const ws = await createWebSocket(url.toString(), WS_SUBPROTOCOL); ws.binaryType = "arraybuffer"; const authDeferred = createDeferred(); const queryQueue = newQueryQueue(); let expectingUrn = true; let currentUrn = null; const authFrame = { username: config.username, password: config.password }; ws.onopen = () => { ws.send(JSON.stringify(authFrame)); authDeferred.resolve(conn); }; ws.onmessage = (event) => { if (typeof event.data !== "string") { const error = new Error(`Protocol error: expected text message, received ${typeof event.data}`); queryQueue.abortAll(error); ws.close(1002, "Protocol error: binary messages not supported"); return; } if (expectingUrn) { currentUrn = event.data; expectingUrn = false; return; } const payload = JSON.parse(event.data); queryQueue.processFrame(currentUrn, payload); currentUrn = null; expectingUrn = true; }; ws.onerror = (event) => { const errorMsg = "message" in event ? String(event.message) : "WebSocket error"; const error = new WebSocketError({ message: errorMsg }); authDeferred.reject(error); queryQueue.abortAll(error); }; ws.onclose = (event) => { authDeferred.resolve(conn); const err = new WebSocketError({ message: "WebSocket connection closed", closureCode: event.code, closureReason: event.reason }); authDeferred.reject(err); queryQueue.abortAll(err); }; const isBusy = wsBusyCheck(ws); const conn = { isReady() { return ws.readyState === WebSocket.OPEN; }, enqueueNewQuery() { return queryQueue.enqueueNew(); }, async send(data) { let backoffMs = 5; while (isBusy()) { await new Promise((resolve) => setTimeout(resolve, backoffMs)); backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS); } ws.send(data); }, close() { ws.close(1e3, "Normal closure"); } }; return authDeferred.promise; } // src/transport/websocket.ts function webSocketTransport(config) { let conn; let sendQueue = Promise.resolve(); const transport = { async connect() { conn ??= await wsTransportConnection(config); }, async statement(kind, sql, parameters) { if (!conn?.isReady()) { throw new WebSocketError({ message: "WebSocket is not connected" }); } const frames = await requestFrames(kind, sql, parameters); const enqueuedQuery = conn.enqueueNewQuery(); const activeConn = conn; sendQueue = sendQueue.then(() => sendFrames(activeConn, frames)).catch(enqueuedQuery.abort); await sendQueue; return enqueuedQuery.promise; }, close() { conn?.close(); }, isConnected() { return !!conn?.isReady(); }, [Symbol.dispose]() { this.close(); } }; return transport; } async function sendFrames(conn, frames) { for (const frame of frames) { if ("query" in frame || "exec" in frame) { await conn.send(FRAME_URNS.queryDescriptorUrn); await conn.send(JSON.stringify(frame)); } else { const isTextParam = frame.type === "text"; await conn.send(isTextParam ? FRAME_URNS.textParamUrn : FRAME_URNS.binaryParamUrn); if (typeof frame.data === "string") { await conn.send(frame.data); } else if (frame.data instanceof Uint8Array) { await conn.send(frame.data); } else if (frame.data instanceof ReadableStream) { if (isTextParam) { const text = await consumeStreamToString(frame.data); await conn.send(text); } else { const bytes = await consumeStreamToUint8Array(frame.data); await conn.send(bytes); } } else { throw new ValidationError(`Unsupported extended parameter data type: ${typeof frame.data}`); } } } } async function consumeStreamToUint8Array(stream) { const chunks = []; let totalLength = 0; const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); totalLength += value.length; } } finally { reader.releaseLock(); } const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; } async function consumeStreamToString(stream) { const decoder = new TextDecoder(); let result = ""; const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; result += decoder.decode(value, { stream: true }); } result += decoder.decode(); } finally { reader.releaseLock(); } return result; } // src/api/client.ts function parseConnectionString(connectionString) { const url = new URL(connectionString); if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") { throw new Error(`Invalid connection string protocol: ${url.protocol}. Expected "postgres:" or "postgresql:"`); } const username = url.username; const password = url.password; const hostname = url.hostname; const database = url.pathname.slice(1) || void 0; if (!username || !password) { throw new Error("Connection string must include username and password"); } const endpoint = `https://${hostname}`; return { endpoint, username, password, database }; } function defaultClientConfig(connectionString) { return { connectionString, parsers: DEFAULT_PARSERS.map((p) => ({ ...p })), serializers: DEFAULT_SERIALIZERS.map((s) => ({ ...s })) }; } var passThrough = (v) => v; var nullPassThrough = (fn) => (v) => v === null ? null : fn(v); var DEFAULT_PARSERS = [ // Boolean { oid: 16, parse: (value) => value === "t" }, // Int2 (smallint) { oid: 21, parse: nullPassThrough((value) => Number.parseInt(value, 10)) }, // Int4 (integer) { oid: 23, parse: nullPassThrough((value) => Number.parseInt(value, 10)) }, // Int8 (bigint) - parse as BigInt to preserve precision { oid: 20, parse: nullPassThrough(BigInt) }, // Float4 (real) { oid: 700, parse: nullPassThrough((value) => Number.parseFloat(value)) }, // Float8 (double precision) { oid: 701, parse: nullPassThrough((value) => Number.parseFloat(value)) }, // Text { oid: 25, parse: passThrough }, // Varchar { oid: 1043, parse: passThrough }, // JSON { oid: 114, parse: nullPassThrough((value) => JSON.parse(value)) }, // JSONB { oid: 3802, parse: nullPassThrough((value) => JSON.parse(value)) } ]; var serializeToString = (x) => x.toString(); var DEFAULT_SERIALIZERS = [ // Date serializer { supports: (value) => value instanceof Date, serialize: (value) => value.toISOString() }, // BigInt serializer { supports: (value) => typeof value === "bigint", serialize: serializeToString }, // Boolean serializer { supports: (value) => typeof value === "boolean", serialize: (value) => value ? "t" : "f" }, // Number serializer { supports: (value) => typeof value === "number", serialize: serializeToString } ]; function client(config) { const transportConfig = "transportConfig" in config ? config.transportConfig : ( // override the config for testing parseConnectionString(config.connectionString.toString()) ); const parsersMap = (config.parsers || []).reduce( (map, parser) => map.set(parser.oid, parser), /* @__PURE__ */ new Map() ); const parsers = [...parsersMap.values()]; const serializers = config.serializers || []; const transport = httpTransport(transportConfig); const statements = createStatements(transport, serializers, parsers); async function newSession(sessionConfig) { const sessionTransport = webSocketTransport(transportConfig); await sessionTransport.connect(); const sessionParsersMap = sessionConfig?.parsers?.reduce((map, parser) => map.set(parser.oid, parser), new Map(parsersMap)) ?? new Map(parsersMap); const sessionParsers = [...sessionParsersMap.values()]; const sessionSerializers = [...sessionConfig?.serializers || [], ...config.serializers || []]; const sessionStatements = createStatements(sessionTransport, sessionSerializers, sessionParsers); const session = { ...sessionStatements, close() { sessionTransport.close(); }, get active() { return sessionTransport.isConnected(); }, [Symbol.dispose]() { this.close(); } }; return session; } return { ...statements, newSession }; } function createStatements(transport, serializers, parsers) { function transformResponse(response) { const columns = response.columns.map((col) => ({ name: col.name, oid: col.oid })); const rows = toCollectableIterator(response.rows, (rawRow) => parseSessionRow(parsers, columns, rawRow)); return { columns, rows }; } return { async query(sql, ...params) { const rawParams = serializeSessionParams(serializers, params); const response = await transport.statement("query", sql, rawParams); return transformResponse(response); }, async exec(sql, ...params) { const rawParams = serializeSessionParams(serializers, params); const response = await transport.statement("exec", sql, rawParams); const firstRow = await response.rows.next(); assertRowAffectedResult(firstRow); return Number.parseInt(firstRow.value[0], 10); } }; } function serializeSessionParams(serializers, params) { return params.map((param) => { if (param === null || param === void 0) { return null; } const serializer = serializers.find((s) => s.supports(param)); return serializer ? serializer.serialize(param) : typeof param === "string" ? param : String(param); }); } function parseSessionRow(parsers, columns, rawValues) { const values = rawValues.map((value, index) => { const columnOid = columns[index].oid; const parser = parsers.find((p) => p.oid === columnOid); return parser ? parser.parse(value) : value; }); return { values }; } function assertRowAffectedResult(x) { if (!x || x.done || x.value?.length !== 1 || !x.value[0] || !/^\d+$/.test(x.value[0])) { throw new Error(`Protocol error: missing rowsAffected value in exec response: ${JSON.stringify(x)}`); } } // src/api/ppg.ts function prismaPostgres(config) { const lowLevelClient = client(config); const prismaStatements = createPrismaStatements(lowLevelClient); async function transaction(callback) { var _stack = []; try { const session = __using(_stack, await lowLevelClient.newSession()); const sessionStatements = createPrismaStatements(session); try { const beginPromise = session.exec("BEGIN"); const result = await Promise.all([beginPromise, callback(sessionStatements)]); await session.exec("COMMIT"); return result[1]; } catch (error) { await session.exec("ROLLBACK"); throw error; } } catch (_) { var _error = _, _hasError = true; } finally { __callDispose(_stack, _error, _hasError); } } function batchWithQueries(...statements) { return transaction(async (stmt) => { const results = []; for (const statement of statements) { if ("query" in statement) { const rows = await stmt.query(statement.query, ...statement.parameters || []).collect(); results.push(rows); } else { const affected = await stmt.exec(statement.exec, ...statement.parameters || []); results.push(affected); } } return results; }); } function batch(...queries) { if (queries.length === 0) { return createBatchQueryBuilder([]); } return batchWithQueries(...queries); } function createBatchQueryBuilder(statements) { return { query(sql, ...params) { return createBatchQueryBuilder([...statements, { query: sql, parameters: params }]); }, exec(sql, ...params) { return createBatchQueryBuilder([...statements, { exec: sql, parameters: params }]); }, run() { return batchWithQueries(...statements); } }; } return { ...prismaStatements, transaction, batch }; } function templateToSqlParams(strings, values) { let sql = strings[0]; for (let i = 0; i < values.length; i++) { sql += `$${i + 1}${strings[i + 1]}`; } return [sql, values]; } function rowToObject(columns, values) { const obj = {}; for (let i = 0; i < columns.length; i++) { obj[columns[i].name] = values[i]; } return obj; } function createPrismaStatements(statements) { async function* rowObjectsGenerator(sql, params) { const result = await statements.query(sql, ...params); for await (const row of result.rows) { yield rowToObject(result.columns, row.values); } } function sqlTag(strings, ...values) { const [sql, params] = templateToSqlParams(strings, values); return toCollectableIterator(rowObjectsGenerator(sql, params)); } sqlTag.exec = async (strings, ...values) => { const [sql, params] = templateToSqlParams(strings, values); return statements.exec(sql, ...params); }; return { sql: sqlTag, query(sql, ...params) { return toCollectableIterator(rowObjectsGenerator(sql, params)); }, async exec(sql, ...params) { return statements.exec(sql, ...params); } }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BINARY, DatabaseError, GenericError, HttpResponseError, TEXT, ValidationError, WebSocketError, boundedByteStreamParameter, byteArrayParameter, client, defaultClientConfig, prismaPostgres });