UNPKG

pglite-prisma-adapter

Version:

Prisma's driver adapter for "@electric-sql/pglite"

608 lines (601 loc) 19.4 kB
//#region rolldown:runtime 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 __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) { __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } } } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let _electric_sql_pglite = require("@electric-sql/pglite"); _electric_sql_pglite = __toESM(_electric_sql_pglite); let _prisma_driver_adapter_utils = require("@prisma/driver-adapter-utils"); let postgres_array = require("postgres-array"); //#region package.json var name = "pglite-prisma-adapter"; //#endregion //#region src/conversion.ts /** * Additional scalar column types not defined in `pg` types builtins. */ const AdditionalScalarColumnType = { NAME: 19 }; const ScalarColumnType = _electric_sql_pglite.types; /** * PostgreSQL array column types (not defined in ScalarColumnType). * * See the semantics of each of this code in: * https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat */ const ArrayColumnType = { BIT: 1561, BOOL: 1e3, BYTEA: 1001, BPCHAR: 1014, CHAR: 1002, CIDR: 651, DATE: 1182, FLOAT4: 1021, FLOAT8: 1022, INET: 1041, INT2: 1005, INT4: 1007, INT8: 1016, JSONB: 3807, JSON: 199, MONEY: 791, NUMERIC: 1231, OID: 1028, TEXT: 1009, TIMESTAMP: 1115, TIMESTAMPTZ: 1185, TIME: 1183, UUID: 2951, VARBIT: 1563, VARCHAR: 1015, XML: 143 }; var UnsupportedNativeDataType = class UnsupportedNativeDataType extends Error { static typeNames = { 16: "bool", 17: "bytea", 18: "char", 19: "name", 20: "int8", 21: "int2", 22: "int2vector", 23: "int4", 24: "regproc", 25: "text", 26: "oid", 27: "tid", 28: "xid", 29: "cid", 30: "oidvector", 32: "pg_ddl_command", 71: "pg_type", 75: "pg_attribute", 81: "pg_proc", 83: "pg_class", 114: "json", 142: "xml", 194: "pg_node_tree", 269: "table_am_handler", 325: "index_am_handler", 600: "point", 601: "lseg", 602: "path", 603: "box", 604: "polygon", 628: "line", 650: "cidr", 700: "float4", 701: "float8", 705: "unknown", 718: "circle", 774: "macaddr8", 790: "money", 829: "macaddr", 869: "inet", 1033: "aclitem", 1042: "bpchar", 1043: "varchar", 1082: "date", 1083: "time", 1114: "timestamp", 1184: "timestamptz", 1186: "interval", 1266: "timetz", 1560: "bit", 1562: "varbit", 1700: "numeric", 1790: "refcursor", 2202: "regprocedure", 2203: "regoper", 2204: "regoperator", 2205: "regclass", 2206: "regtype", 2249: "record", 2275: "cstring", 2276: "any", 2277: "anyarray", 2278: "void", 2279: "trigger", 2280: "language_handler", 2281: "internal", 2283: "anyelement", 2287: "_record", 2776: "anynonarray", 2950: "uuid", 2970: "txid_snapshot", 3115: "fdw_handler", 3220: "pg_lsn", 3310: "tsm_handler", 3361: "pg_ndistinct", 3402: "pg_dependencies", 3500: "anyenum", 3614: "tsvector", 3615: "tsquery", 3642: "gtsvector", 3734: "regconfig", 3769: "regdictionary", 3802: "jsonb", 3831: "anyrange", 3838: "event_trigger", 3904: "int4range", 3906: "numrange", 3908: "tsrange", 3910: "tstzrange", 3912: "daterange", 3926: "int8range", 4072: "jsonpath", 4089: "regnamespace", 4096: "regrole", 4191: "regcollation", 4451: "int4multirange", 4532: "nummultirange", 4533: "tsmultirange", 4534: "tstzmultirange", 4535: "datemultirange", 4536: "int8multirange", 4537: "anymultirange", 4538: "anycompatiblemultirange", 4600: "pg_brin_bloom_summary", 4601: "pg_brin_minmax_multi_summary", 5017: "pg_mcv_list", 5038: "pg_snapshot", 5069: "xid8", 5077: "anycompatible", 5078: "anycompatiblearray", 5079: "anycompatiblenonarray", 5080: "anycompatiblerange" }; type; constructor(code) { super(); this.type = UnsupportedNativeDataType.typeNames[code] || "Unknown"; this.message = `Unsupported column type ${this.type}`; } }; /** * This is a simplification of quaint's value inference logic. Take a look at quaint's conversion.rs * module to see how other attributes of the field packet such as the field length are used to infer * the correct quaint::Value variant. */ function fieldToColumnType(fieldTypeId) { switch (fieldTypeId) { case ScalarColumnType.INT2: case ScalarColumnType.INT4: return _prisma_driver_adapter_utils.ColumnTypeEnum.Int32; case ScalarColumnType.INT8: return _prisma_driver_adapter_utils.ColumnTypeEnum.Int64; case ScalarColumnType.FLOAT4: return _prisma_driver_adapter_utils.ColumnTypeEnum.Float; case ScalarColumnType.FLOAT8: return _prisma_driver_adapter_utils.ColumnTypeEnum.Double; case ScalarColumnType.BOOL: return _prisma_driver_adapter_utils.ColumnTypeEnum.Boolean; case ScalarColumnType.DATE: return _prisma_driver_adapter_utils.ColumnTypeEnum.Date; case ScalarColumnType.TIME: case ScalarColumnType.TIMETZ: return _prisma_driver_adapter_utils.ColumnTypeEnum.Time; case ScalarColumnType.TIMESTAMP: case ScalarColumnType.TIMESTAMPTZ: return _prisma_driver_adapter_utils.ColumnTypeEnum.DateTime; case ScalarColumnType.NUMERIC: case ScalarColumnType.MONEY: return _prisma_driver_adapter_utils.ColumnTypeEnum.Numeric; case ScalarColumnType.JSON: case ScalarColumnType.JSONB: return _prisma_driver_adapter_utils.ColumnTypeEnum.Json; case ScalarColumnType.UUID: return _prisma_driver_adapter_utils.ColumnTypeEnum.Uuid; case ScalarColumnType.OID: return _prisma_driver_adapter_utils.ColumnTypeEnum.Int64; case ScalarColumnType.BPCHAR: case ScalarColumnType.TEXT: case ScalarColumnType.VARCHAR: case ScalarColumnType.BIT: case ScalarColumnType.VARBIT: case ScalarColumnType.INET: case ScalarColumnType.CIDR: case ScalarColumnType.XML: case AdditionalScalarColumnType.NAME: return _prisma_driver_adapter_utils.ColumnTypeEnum.Text; case ScalarColumnType.CHAR: return _prisma_driver_adapter_utils.ColumnTypeEnum.Character; case ScalarColumnType.BYTEA: return _prisma_driver_adapter_utils.ColumnTypeEnum.Bytes; case ArrayColumnType.INT2: case ArrayColumnType.INT4: return _prisma_driver_adapter_utils.ColumnTypeEnum.Int32Array; case ArrayColumnType.FLOAT4: return _prisma_driver_adapter_utils.ColumnTypeEnum.FloatArray; case ArrayColumnType.FLOAT8: return _prisma_driver_adapter_utils.ColumnTypeEnum.DoubleArray; case ArrayColumnType.NUMERIC: case ArrayColumnType.MONEY: return _prisma_driver_adapter_utils.ColumnTypeEnum.NumericArray; case ArrayColumnType.BOOL: return _prisma_driver_adapter_utils.ColumnTypeEnum.BooleanArray; case ArrayColumnType.CHAR: return _prisma_driver_adapter_utils.ColumnTypeEnum.CharacterArray; case ArrayColumnType.BPCHAR: case ArrayColumnType.TEXT: case ArrayColumnType.VARCHAR: case ArrayColumnType.VARBIT: case ArrayColumnType.BIT: case ArrayColumnType.INET: case ArrayColumnType.CIDR: case ArrayColumnType.XML: return _prisma_driver_adapter_utils.ColumnTypeEnum.TextArray; case ArrayColumnType.DATE: return _prisma_driver_adapter_utils.ColumnTypeEnum.DateArray; case ArrayColumnType.TIMESTAMP: case ArrayColumnType.TIMESTAMPTZ: return _prisma_driver_adapter_utils.ColumnTypeEnum.DateTimeArray; case ArrayColumnType.JSON: case ArrayColumnType.JSONB: return _prisma_driver_adapter_utils.ColumnTypeEnum.JsonArray; case ArrayColumnType.BYTEA: return _prisma_driver_adapter_utils.ColumnTypeEnum.BytesArray; case ArrayColumnType.UUID: return _prisma_driver_adapter_utils.ColumnTypeEnum.UuidArray; case ArrayColumnType.INT8: case ArrayColumnType.OID: return _prisma_driver_adapter_utils.ColumnTypeEnum.Int64Array; default: if (fieldTypeId >= 1e4) return _prisma_driver_adapter_utils.ColumnTypeEnum.Text; throw new UnsupportedNativeDataType(fieldTypeId); } } function normalize_array(element_normalizer) { return (str) => (0, postgres_array.parse)(str, element_normalizer); } function normalize_numeric(numeric) { return numeric; } function normalize_date(date) { return date; } function normalize_timestamp(time) { return `${time.replace(" ", "T")}+00:00`; } function normalize_timestampz(time) { return time.replace(" ", "T").replace(/[+-]\d{2}(:\d{2})?$/, "+00:00"); } function normalize_time(time) { return time; } function normalize_timez(time) { return time.replace(/[+-]\d{2}(:\d{2})?$/, ""); } function normalize_money(money) { return money.slice(1); } function normalize_xml(xml) { return xml; } /** * We hand off JSON handling entirely to engines, so we keep it * stringified here. This function needs to exist as otherwise * the default type parser attempts to deserialise it. */ function toJson(json) { return json; } const parsePgBytes = (x) => { const hexString = x.slice(2); return Uint8Array.from({ length: hexString.length / 2 }, (_, idx) => Number.parseInt(hexString.substring(idx * 2, (idx + 1) * 2), 16)); }; function normalizeByteaArray(x) { return (0, postgres_array.parse)(x).map((x$1) => { const hexString = x$1.slice(2); return Uint8Array.from({ length: hexString.length / 2 }, (_, idx) => Number.parseInt(hexString.substring(idx * 2, (idx + 1) * 2), 16)); }); } /** * Convert bytes to a JSON-encodable representation since we can't * currently send a parsed Buffer or ArrayBuffer across JS to Rust * boundary. */ function convertBytes(serializedBytes) { return parsePgBytes(serializedBytes); } function normalizeBit(bit) { return bit; } function normalizeBigInt(bigint) { return bigint; } const customParsers = { [ScalarColumnType.NUMERIC]: normalize_numeric, [ArrayColumnType.NUMERIC]: normalize_array(normalize_numeric), [ScalarColumnType.TIME]: normalize_time, [ArrayColumnType.TIME]: normalize_array(normalize_time), [ScalarColumnType.TIMETZ]: normalize_timez, [ScalarColumnType.DATE]: normalize_date, [ArrayColumnType.DATE]: normalize_array(normalize_date), [ScalarColumnType.TIMESTAMP]: normalize_timestamp, [ArrayColumnType.TIMESTAMP]: normalize_array(normalize_timestamp), [ScalarColumnType.TIMESTAMPTZ]: normalize_timestampz, [ArrayColumnType.TIMESTAMPTZ]: normalize_array(normalize_timestampz), [ScalarColumnType.MONEY]: normalize_money, [ArrayColumnType.MONEY]: normalize_array(normalize_money), [ScalarColumnType.JSON]: toJson, [ArrayColumnType.JSON]: normalize_array(toJson), [ScalarColumnType.JSONB]: toJson, [ArrayColumnType.JSONB]: normalize_array(toJson), [ScalarColumnType.BYTEA]: convertBytes, [ArrayColumnType.BYTEA]: normalizeByteaArray, [ArrayColumnType.BIT]: normalize_array(normalizeBit), [ArrayColumnType.VARBIT]: normalize_array(normalizeBit), [ArrayColumnType.XML]: normalize_array(normalize_xml), [ScalarColumnType.INT8]: normalizeBigInt, [ArrayColumnType.INT8]: normalize_array(normalizeBigInt) }; function mapArg(arg, argType) { if (arg === null) return null; if (Array.isArray(arg) && argType.arity === "list") return arg.map((value) => mapArg(value, argType)); if (typeof arg === "string" && argType.scalarType === "datetime") arg = new Date(arg); if (arg instanceof Date) switch (argType.dbType) { case "TIME": case "TIMETZ": return formatTime(arg); case "DATE": return formatDate(arg); default: return formatDateTime(arg); } if (typeof arg === "string" && argType.scalarType === "bytes") return Buffer.from(arg, "base64"); if (ArrayBuffer.isView(arg)) return new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength); return arg; } function formatDateTime(date) { const pad = (n, z = 2) => String(n).padStart(z, "0"); const ms = date.getUTCMilliseconds(); return `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}${ms ? `.${String(ms).padStart(3, "0")}` : ""}`; } function formatDate(date) { const pad = (n, z = 2) => String(n).padStart(z, "0"); return `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`; } function formatTime(date) { const pad = (n, z = 2) => String(n).padStart(z, "0"); const ms = date.getUTCMilliseconds(); return `${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}${ms ? `.${String(ms).padStart(3, "0")}` : ""}`; } //#endregion //#region src/deferred.ts function createDeferred() { const deferred = {}; return [deferred, new Promise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; })]; } //#endregion //#region src/errors.ts function convertDriverError(error) { if (isDriverError(error)) return { originalCode: error.code, originalMessage: error.message, ...mapDriverError(error) }; throw error; } function mapDriverError(error) { switch (error.code) { case "22001": return { kind: "LengthMismatch", column: error.column }; case "22003": return { kind: "ValueOutOfRange", cause: error.message }; case "22P02": return { kind: "InvalidInputValue", message: error.message }; case "23505": { const fields = error.detail?.match(/Key \(([^)]+)\)/)?.at(1)?.split(", "); return { kind: "UniqueConstraintViolation", constraint: fields !== void 0 ? { fields } : void 0 }; } case "23502": { const fields = error.detail?.match(/Key \(([^)]+)\)/)?.at(1)?.split(", "); return { kind: "NullConstraintViolation", constraint: fields !== void 0 ? { fields } : void 0 }; } case "23503": { let constraint; if (error.column) constraint = { fields: [error.column] }; else if (error.constraint) constraint = { index: error.constraint }; return { kind: "ForeignKeyConstraintViolation", constraint }; } case "3D000": return { kind: "DatabaseDoesNotExist", db: error.message.split(" ").at(1)?.split("\"").at(1) }; case "28000": return { kind: "DatabaseAccessDenied", db: error.message.split(",").find((s) => s.startsWith(" database"))?.split("\"").at(1) }; case "28P01": return { kind: "AuthenticationFailed", user: error.message.split(" ").pop()?.split("\"").at(1) }; case "40001": return { kind: "TransactionWriteConflict" }; case "42P01": return { kind: "TableDoesNotExist", table: error.message.split(" ").at(1)?.split("\"").at(1) }; case "42703": return { kind: "ColumnNotFound", column: error.message.split(" ").at(1)?.split("\"").at(1) }; case "42P04": return { kind: "DatabaseAlreadyExists", db: error.message.split(" ").at(1)?.split("\"").at(1) }; case "53300": return { kind: "TooManyConnections", cause: error.message }; default: return { kind: "postgres", code: error.code ?? "N/A", severity: error.severity ?? "N/A", message: error.message, detail: error.detail, column: error.column, hint: error.hint }; } } function isDriverError(error) { return typeof error.code === "string" && typeof error.message === "string" && typeof error.severity === "string" && (typeof error.detail === "string" || error.detail === void 0) && (typeof error.column === "string" || error.column === void 0) && (typeof error.hint === "string" || error.hint === void 0); } //#endregion //#region src/pglite.ts const debug = (0, _prisma_driver_adapter_utils.Debug)("prisma:driver-adapter:pglite"); var PGliteQueryable = class { provider = "postgres"; adapterName = name; constructor(client) { this.client = client; } async queryRaw(query) { debug(`[js::query_raw] %O`, query); const { fields, rows } = await this.performIO(query); const columnNames = fields.map((field) => field.name); let columnTypes = []; try { columnTypes = fields.map((field) => fieldToColumnType(field.dataTypeID)); } catch (e) { if (e instanceof UnsupportedNativeDataType) throw new _prisma_driver_adapter_utils.DriverAdapterError({ kind: "UnsupportedNativeDataType", type: e.type }); throw e; } return { columnNames, columnTypes, rows }; } /** * Execute a query given as SQL, interpolating the given parameters and * returning the number of affected rows. */ async executeRaw(query) { debug(`[js::execute_raw] %O`, query); return (await this.performIO(query)).affectedRows ?? 0; } async performIO(query) { const { sql, args } = query; const values = args.map((arg, i) => mapArg(arg, query.argTypes[i])); try { return await this.client.query(sql, values, { rowMode: "array", parsers: customParsers }); } catch (e) { this.onError(e); } } onError(error) { debug("Error in performIO: %O", error); if (error instanceof _electric_sql_pglite.messages.DatabaseError) throw new _prisma_driver_adapter_utils.DriverAdapterError(convertDriverError(error)); throw error; } }; var PGliteTransaction = class extends PGliteQueryable { constructor(client, options, txDeferred, txResultPromise) { super(client); this.options = options; this.txDeferred = txDeferred; this.txResultPromise = txResultPromise; } async commit() { debug("[js::commit]"); this.txDeferred.resolve(); return await this.txResultPromise; } async rollback() { debug("[js::rollback]"); this.client.rollback(); this.txDeferred.resolve(); return await this.txResultPromise; } }; var PrismaPGliteAdapter = class extends PGliteQueryable { constructor(client, options) { super(client); this.options = options; } executeScript(script) { try { this.client.exec(script); } catch (e) { this.onError(e); } return Promise.resolve(); } getConnectionInfo() { return { schemaName: this.options?.schema, supportsRelationJoins: true }; } async startTransaction(isolationLevel) { const options = { usePhantomQuery: true }; debug("%s options: %O", "[js::startTransaction]", options); if (isolationLevel) await this.client.exec(`SET TRANSACTION ISOLATION LEVEL ${isolationLevel}`).catch((error) => this.onError(error)); return this.startTransactionInner(this.client, options); } async startTransactionInner(conn, options) { return new Promise((resolve, reject) => { const txResultPromise = conn.transaction(async (tx) => { const [txDeferred, deferredPromise] = createDeferred(); resolve(new PGliteTransaction(tx, options, txDeferred, txResultPromise)); return deferredPromise; }).catch((error) => { return reject(error); }); }); } async dispose() { return Promise.resolve(); } }; var PrismaPGliteAdapterFactory = class { provider = "postgres"; adapterName = name; constructor(client) { this.client = client; } connect() { return Promise.resolve(new PrismaPGliteAdapter(this.client)); } connectToShadowDb() { return Promise.resolve(new PrismaPGliteAdapter(new _electric_sql_pglite.PGlite({ dataDir: "memory://shadow" }))); } }; //#endregion exports.PrismaPGlite = PrismaPGliteAdapterFactory;