pglite-prisma-adapter
Version:
Prisma's driver adapter for "@electric-sql/pglite"
492 lines (483 loc) • 13.3 kB
JavaScript
'use strict';
var pglite = require('@electric-sql/pglite');
var driverAdapterUtils = require('@prisma/driver-adapter-utils');
var postgresArray = require('postgres-array');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var pglite__namespace = /*#__PURE__*/_interopNamespaceDefault(pglite);
var name = "pglite-prisma-adapter";
const ScalarColumnType = {
...pglite.types,
NAME: 19
};
const ArrayColumnType = {
BYTEA: 1001,
CHAR: 1002,
INT8: 1016,
INT2: 1005,
INT4: 1007,
TEXT: 1009,
OID: 1028,
JSON: 199,
FLOAT4: 1021,
FLOAT8: 1022,
VARCHAR: 1015,
JSONB: 3807,
DATE: 1182,
TIMESTAMP: 1115,
TIMESTAMPTZ: 1116
};
class UnsupportedNativeDataType extends Error {
// map of type codes to type names
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}`;
}
}
function fieldToColumnType(fieldTypeId) {
switch (fieldTypeId) {
case ScalarColumnType.INT2:
case ScalarColumnType.INT4:
return driverAdapterUtils.ColumnTypeEnum.Int32;
case ScalarColumnType.INT8:
return driverAdapterUtils.ColumnTypeEnum.Int64;
case ScalarColumnType.FLOAT4:
return driverAdapterUtils.ColumnTypeEnum.Float;
case ScalarColumnType.FLOAT8:
return driverAdapterUtils.ColumnTypeEnum.Double;
case ScalarColumnType.BOOL:
return driverAdapterUtils.ColumnTypeEnum.Boolean;
case ScalarColumnType.DATE:
return driverAdapterUtils.ColumnTypeEnum.Date;
case ScalarColumnType.TIME:
case ScalarColumnType.TIMETZ:
return driverAdapterUtils.ColumnTypeEnum.Time;
case ScalarColumnType.TIMESTAMP:
case ScalarColumnType.TIMESTAMPTZ:
return driverAdapterUtils.ColumnTypeEnum.DateTime;
case ScalarColumnType.NUMERIC:
case ScalarColumnType.MONEY:
return driverAdapterUtils.ColumnTypeEnum.Numeric;
case ScalarColumnType.JSON:
case ScalarColumnType.JSONB:
return driverAdapterUtils.ColumnTypeEnum.Json;
case ScalarColumnType.UUID:
return driverAdapterUtils.ColumnTypeEnum.Uuid;
case ScalarColumnType.OID:
return driverAdapterUtils.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 ScalarColumnType.NAME:
return driverAdapterUtils.ColumnTypeEnum.Text;
case ScalarColumnType.CHAR:
return driverAdapterUtils.ColumnTypeEnum.Character;
case ScalarColumnType.BYTEA:
return driverAdapterUtils.ColumnTypeEnum.Bytes;
case ArrayColumnType.INT2:
case ArrayColumnType.INT4:
return driverAdapterUtils.ColumnTypeEnum.Int32Array;
case ArrayColumnType.FLOAT4:
return driverAdapterUtils.ColumnTypeEnum.FloatArray;
case ArrayColumnType.FLOAT8:
return driverAdapterUtils.ColumnTypeEnum.DoubleArray;
case ArrayColumnType.CHAR:
return driverAdapterUtils.ColumnTypeEnum.CharacterArray;
case ArrayColumnType.TEXT:
case ArrayColumnType.VARCHAR:
return driverAdapterUtils.ColumnTypeEnum.TextArray;
case ArrayColumnType.DATE:
return driverAdapterUtils.ColumnTypeEnum.DateArray;
case ArrayColumnType.TIMESTAMP:
case ArrayColumnType.TIMESTAMPTZ:
return driverAdapterUtils.ColumnTypeEnum.DateTimeArray;
case ArrayColumnType.JSON:
case ArrayColumnType.JSONB:
return driverAdapterUtils.ColumnTypeEnum.JsonArray;
case ArrayColumnType.BYTEA:
return driverAdapterUtils.ColumnTypeEnum.BytesArray;
case ArrayColumnType.OID:
case ArrayColumnType.INT8:
return driverAdapterUtils.ColumnTypeEnum.Int64Array;
default:
if (fieldTypeId >= 1e4) {
return driverAdapterUtils.ColumnTypeEnum.Text;
}
throw new UnsupportedNativeDataType(fieldTypeId);
}
}
function normalize_array(element_normalizer) {
return (str) => postgresArray.parse(str, element_normalizer);
}
function normalize_numeric(numeric) {
return numeric;
}
function normalize_date(date) {
return date;
}
function normalize_timestamp(time) {
return time;
}
function normalize_timestampz(time) {
return time.split("+")[0];
}
function normalize_time(time) {
return time;
}
function normalize_timez(time) {
return time.split("+")[0];
}
function normalize_money(money) {
return money.slice(1);
}
function toJson(json) {
return json;
}
function encodeBuffer(buffer) {
return Array.from(new Uint8Array(buffer));
}
const parsePgBytes = (x) => Buffer.from(x.slice(2), "hex");
function convertBytes(serializedBytes) {
const buffer = parsePgBytes(serializedBytes);
return encodeBuffer(buffer);
}
const customParsers = {
[ScalarColumnType.INT8]: normalize_numeric,
[ScalarColumnType.NUMERIC]: normalize_numeric,
[ScalarColumnType.TIME]: 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,
[ScalarColumnType.JSON]: toJson,
[ScalarColumnType.JSONB]: toJson,
[ScalarColumnType.BYTEA]: convertBytes,
[ArrayColumnType.BYTEA]: normalize_array(convertBytes)
};
function fixArrayBufferValues(values) {
for (let i = 0; i < values.length; i++) {
const list = values[i];
if (!Array.isArray(list)) {
continue;
}
for (let j = 0; j < list.length; j++) {
const listItem = list[j];
if (ArrayBuffer.isView(listItem)) {
list[j] = Buffer.from(
listItem.buffer,
listItem.byteOffset,
listItem.byteLength
);
}
}
}
return values;
}
function createDeferred() {
const deferred = {};
return [
deferred,
new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
})
];
}
const debug = driverAdapterUtils.Debug("prisma:driver-adapter:pglite");
class PGliteQueryable {
constructor(client) {
this.client = client;
}
provider = "postgres";
adapterName = name;
async queryRaw(query) {
const tag = "[js::query_raw]";
debug(`${tag} %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 driverAdapterUtils.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) {
const tag = "[js::execute_raw]";
debug(`${tag} %O`, query);
return (await this.performIO(query)).affectedRows ?? 0;
}
async performIO(query) {
const { sql, args: values } = query;
try {
const result = await this.client.query(
sql,
fixArrayBufferValues(values),
{
rowMode: "array",
parsers: customParsers
}
);
return result;
} catch (e) {
this.onError(e);
}
}
onError(error) {
debug("Error in performIO: %O", error);
if (error instanceof pglite__namespace.messages.DatabaseError) {
throw new driverAdapterUtils.DriverAdapterError({
kind: "postgres",
code: error.code ?? "UNKNOWN",
severity: error.severity ?? "UNKNOWN",
message: error.message,
detail: error.detail,
column: error.column,
hint: error.hint
});
}
throw error;
}
}
class PGliteTransaction 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;
}
}
class PrismaPGliteAdapter 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
};
const tag = "[js::startTransaction]";
debug("%s options: %O", tag, 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();
const txWrapper = new PGliteTransaction(
tx,
options,
txDeferred,
txResultPromise
);
resolve(txWrapper);
return deferredPromise;
}).catch((error) => {
return reject(error);
});
});
}
async dispose() {
return Promise.resolve();
}
}
class PrismaPGliteAdapterFactory {
constructor(client) {
this.client = client;
}
provider = "postgres";
adapterName = name;
connect() {
return Promise.resolve(new PrismaPGliteAdapter(this.client));
}
connectToShadowDb() {
return Promise.resolve(
new PrismaPGliteAdapter(
new pglite__namespace.PGlite({ dataDir: "memory://shadow" })
)
);
}
}
exports.PrismaPGlite = PrismaPGliteAdapterFactory;