// src/libsql-node.ts import { createClient } from "@libsql/client"; // src/libsql.ts import { Debug as Debug2, DriverAdapterError } from "@prisma/driver-adapter-utils"; import { Mutex } from "async-mutex"; // package.json var name = "@prisma/adapter-libsql"; // src/conversion.ts import { ColumnTypeEnum, Debug } from "@prisma/driver-adapter-utils"; var debug = Debug("prisma:driver-adapter:libsql:conversion"); function mapDeclType(declType) { switch (declType.toUpperCase()) { case "": return null; case "DECIMAL": return ColumnTypeEnum.Numeric; case "FLOAT": return ColumnTypeEnum.Float; case "DOUBLE": case "DOUBLE PRECISION": case "NUMERIC": case "REAL": return ColumnTypeEnum.Double; case "TINYINT": case "SMALLINT": case "MEDIUMINT": case "INT": case "INTEGER": case "SERIAL": case "INT2": return ColumnTypeEnum.Int32; case "BIGINT": case "UNSIGNED BIG INT": case "INT8": return ColumnTypeEnum.Int64; case "DATETIME": case "TIMESTAMP": return ColumnTypeEnum.DateTime; case "TIME": return ColumnTypeEnum.Time; case "DATE": return ColumnTypeEnum.Date; case "TEXT": case "CLOB": case "CHARACTER": case "VARCHAR": case "VARYING CHARACTER": case "NCHAR": case "NATIVE CHARACTER": case "NVARCHAR": return ColumnTypeEnum.Text; case "BLOB": return ColumnTypeEnum.Bytes; case "BOOLEAN": return ColumnTypeEnum.Boolean; case "JSONB": return ColumnTypeEnum.Json; default: debug("unknown decltype:", declType); return null; } } function mapDeclaredColumnTypes(columnTypes) { const emptyIndices = /* @__PURE__ */ new Set(); const result = columnTypes.map((typeName, index) => { const mappedType = mapDeclType(typeName); if (mappedType === null) { emptyIndices.add(index); } return mappedType; }); return [result, emptyIndices]; } function getColumnTypes(declaredTypes, rows) { const [columnTypes, emptyIndices] = mapDeclaredColumnTypes(declaredTypes); if (emptyIndices.size === 0) { return columnTypes; } columnLoop: for (const columnIndex of emptyIndices) { for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { const candidateValue = rows[rowIndex][columnIndex]; if (candidateValue !== null) { columnTypes[columnIndex] = inferColumnType(candidateValue); continue columnLoop; } } columnTypes[columnIndex] = ColumnTypeEnum.Int32; } return columnTypes; } function inferColumnType(value) { switch (typeof value) { case "string": return ColumnTypeEnum.Text; case "bigint": return ColumnTypeEnum.Int64; case "boolean": return ColumnTypeEnum.Boolean; case "number": return ColumnTypeEnum.UnknownNumber; case "object": return inferObjectType(value); default: throw new UnexpectedTypeError(value); } } function inferObjectType(value) { if (value instanceof ArrayBuffer) { return ColumnTypeEnum.Bytes; } throw new UnexpectedTypeError(value); } var UnexpectedTypeError = class extends Error { name = "UnexpectedTypeError"; constructor(value) { const type = typeof value; const repr = type === "object" ? JSON.stringify(value) : String(value); super(`unexpected value of type ${type}: ${repr}`); } }; function mapRow(row, columnTypes) { const result = []; for (let i = 0; i < row.length; i++) { const value = row[i]; if (value instanceof ArrayBuffer) { result[i] = new Uint8Array(value); continue; } if (typeof value === "number" && (columnTypes[i] === ColumnTypeEnum.Int32 || columnTypes[i] === ColumnTypeEnum.Int64) && !Number.isInteger(value)) { result[i] = Math.trunc(value); continue; } if (["number", "bigint"].includes(typeof value) && columnTypes[i] === ColumnTypeEnum.DateTime) { result[i] = new Date(Number(value)).toISOString(); continue; } if (typeof value === "bigint") { result[i] = value.toString(); continue; } result[i] = value; } return result; } function mapArg(arg, argType, options) { if (arg === null) { return null; } if (typeof arg === "string" && argType.scalarType === "bigint") { return BigInt(arg); } if (typeof arg === "string" && argType.scalarType === "decimal") { return Number.parseFloat(arg); } if (typeof arg === "string" && argType.scalarType === "datetime") { arg = new Date(arg); } if (arg instanceof Date) { const format = options?.timestampFormat ?? "iso8601"; switch (format) { case "unixepoch-ms": return arg.getTime(); case "iso8601": return arg.toISOString().replace("Z", "+00:00"); default: throw new Error(`Unknown timestamp format: ${format}`); } } if (typeof arg === "string" && argType.scalarType === "bytes") { return Buffer.from(arg, "base64"); } return arg; } // src/errors.ts var SQLITE_BUSY = 5; var PRIMARY_ERROR_CODE_MASK = 255; function convertDriverError(error) { if (isDriverError(error)) { return { originalCode: error.rawCode?.toString(), originalMessage: error.message, ...mapDriverError(error) }; } throw error; } function mapDriverError(error) { const rawCode = error.rawCode ?? error.cause?.["rawCode"] ?? 1; switch (rawCode) { case 2067: case 1555: { const fields = error.message.split("constraint failed: ").at(1)?.split(", ").map((field) => field.split(".").pop()); return { kind: "UniqueConstraintViolation", constraint: fields !== void 0 ? { fields } : void 0 }; } case 1299: { const fields = error.message.split("constraint failed: ").at(1)?.split(", ").map((field) => field.split(".").pop()); return { kind: "NullConstraintViolation", constraint: fields !== void 0 ? { fields } : void 0 }; } case 787: case 1811: return { kind: "ForeignKeyConstraintViolation", constraint: { foreignKey: {} } }; default: if (rawCode && (rawCode & PRIMARY_ERROR_CODE_MASK) === SQLITE_BUSY) { return { kind: "SocketTimeout" }; } else if (error.message.startsWith("no such table")) { return { kind: "TableDoesNotExist", table: error.message.split(": ").at(1) }; } else if (error.message.startsWith("no such column")) { return { kind: "ColumnNotFound", column: error.message.split(": ").at(1) }; } else if (error.message.includes("has no column named ")) { return { kind: "ColumnNotFound", column: error.message.split("has no column named ").at(1) }; } return { kind: "sqlite", extendedCode: rawCode, message: error.message }; } } function isDriverError(error) { return typeof error.code === "string" && typeof error.message === "string" && (typeof error.rawCode === "number" || error.rawCode === void 0); } // src/libsql.ts var debug2 = Debug2("prisma:driver-adapter:libsql"); var LOCK_TAG = Symbol(); var LibSqlQueryable = class { constructor(client, adapterOptions) { this.client = client; this.adapterOptions = adapterOptions; } provider = "sqlite"; adapterName = name; [LOCK_TAG] = new Mutex(); /** * Execute a query given as SQL, interpolating the given parameters. */ async queryRaw(query) { const tag = "[js::query_raw]"; debug2(`${tag} %O`, query); const { columns, rows, columnTypes: declaredColumnTypes } = await this.performIO(query); const columnTypes = getColumnTypes(declaredColumnTypes, rows); return { columnNames: columns, columnTypes, rows: rows.map((row) => mapRow(row, columnTypes)) }; } /** * Execute a query given as SQL, interpolating the given parameters and * returning the number of affected rows. * Note: Queryable expects a u64, but napi.rs only supports u32. */ async executeRaw(query) { const tag = "[js::execute_raw]"; debug2(`${tag} %O`, query); return (await this.performIO(query)).rowsAffected ?? 0; } /** * Run a query against the database, returning the result set. * Should the query fail due to a connection error, the connection is * marked as unhealthy. */ async performIO(query) { const release = await this[LOCK_TAG].acquire(); try { const result = await this.client.execute({ sql: query.sql, args: query.args.map((arg, i) => mapArg(arg, query.argTypes[i], this.adapterOptions)) }); return result; } catch (e) { this.onError(e); } finally { release(); } } onError(error) { debug2("Error in performIO: %O", error); throw new DriverAdapterError(convertDriverError(error)); } }; var LibSqlTransaction = class extends LibSqlQueryable { constructor(client, options, adapterOptions, unlockParent) { super(client, adapterOptions); this.options = options; this.#unlockParent = unlockParent; } #unlockParent; async commit() { debug2(`[js::commit]`); try { await this.client.commit(); } finally { this.#unlockParent(); } } async rollback() { debug2(`[js::rollback]`); try { await this.client.rollback(); } catch (error) { debug2("error in rollback:", error); } finally { this.#unlockParent(); } } async createSavepoint(name2) { await this.executeRaw({ sql: `SAVEPOINT ${name2}`, args: [], argTypes: [] }); } async rollbackToSavepoint(name2) { await this.executeRaw({ sql: `ROLLBACK TO ${name2}`, args: [], argTypes: [] }); } async releaseSavepoint(name2) { await this.executeRaw({ sql: `RELEASE SAVEPOINT ${name2}`, args: [], argTypes: [] }); } }; var PrismaLibSqlAdapter = class extends LibSqlQueryable { constructor(client, adapterOptions) { super(client, adapterOptions); } async executeScript(script) { const release = await this[LOCK_TAG].acquire(); try { await this.client.executeMultiple(script); } catch (e) { this.onError(e); } finally { release(); } } async startTransaction(isolationLevel) { if (isolationLevel && isolationLevel !== "SERIALIZABLE") { throw new DriverAdapterError({ kind: "InvalidIsolationLevel", level: isolationLevel }); } const options = { usePhantomQuery: true }; const tag = "[js::startTransaction]"; debug2("%s options: %O", tag, options); const release = await this[LOCK_TAG].acquire(); try { const tx = await this.client.transaction("deferred"); return new LibSqlTransaction(tx, options, this.adapterOptions, release); } catch (e) { release(); this.onError(e); } } dispose() { this.client.close(); return Promise.resolve(); } }; var PrismaLibSqlAdapterFactoryBase = class { provider = "sqlite"; adapterName = name; #config; #options; constructor(config, options) { this.#config = config; this.#options = options; } connect() { return Promise.resolve(new PrismaLibSqlAdapter(this.createClient(this.#config), this.#options)); } connectToShadowDb() { return Promise.resolve( new PrismaLibSqlAdapter(this.createClient({ ...this.#config, url: ":memory:" }), this.#options) ); } }; // src/libsql-node.ts var PrismaLibSqlAdapterFactory = class extends PrismaLibSqlAdapterFactoryBase { createClient(config) { return createClient(config); } }; export { PrismaLibSqlAdapterFactory as PrismaLibSql };