436 lines
13 KiB
JavaScript
436 lines
13 KiB
JavaScript
"use strict";
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name2 in all)
|
|
__defProp(target, name2, { get: all[name2], 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// src/index-node.ts
|
|
var index_node_exports = {};
|
|
__export(index_node_exports, {
|
|
PrismaLibSql: () => PrismaLibSqlAdapterFactory
|
|
});
|
|
module.exports = __toCommonJS(index_node_exports);
|
|
|
|
// src/libsql-node.ts
|
|
var import_client = require("@libsql/client");
|
|
|
|
// src/libsql.ts
|
|
var import_driver_adapter_utils2 = require("@prisma/driver-adapter-utils");
|
|
var import_async_mutex = require("async-mutex");
|
|
|
|
// package.json
|
|
var name = "@prisma/adapter-libsql";
|
|
|
|
// src/conversion.ts
|
|
var import_driver_adapter_utils = require("@prisma/driver-adapter-utils");
|
|
var debug = (0, import_driver_adapter_utils.Debug)("prisma:driver-adapter:libsql:conversion");
|
|
function mapDeclType(declType) {
|
|
switch (declType.toUpperCase()) {
|
|
case "":
|
|
return null;
|
|
case "DECIMAL":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Numeric;
|
|
case "FLOAT":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Float;
|
|
case "DOUBLE":
|
|
case "DOUBLE PRECISION":
|
|
case "NUMERIC":
|
|
case "REAL":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Double;
|
|
case "TINYINT":
|
|
case "SMALLINT":
|
|
case "MEDIUMINT":
|
|
case "INT":
|
|
case "INTEGER":
|
|
case "SERIAL":
|
|
case "INT2":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Int32;
|
|
case "BIGINT":
|
|
case "UNSIGNED BIG INT":
|
|
case "INT8":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Int64;
|
|
case "DATETIME":
|
|
case "TIMESTAMP":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.DateTime;
|
|
case "TIME":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Time;
|
|
case "DATE":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Date;
|
|
case "TEXT":
|
|
case "CLOB":
|
|
case "CHARACTER":
|
|
case "VARCHAR":
|
|
case "VARYING CHARACTER":
|
|
case "NCHAR":
|
|
case "NATIVE CHARACTER":
|
|
case "NVARCHAR":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Text;
|
|
case "BLOB":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Bytes;
|
|
case "BOOLEAN":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Boolean;
|
|
case "JSONB":
|
|
return import_driver_adapter_utils.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] = import_driver_adapter_utils.ColumnTypeEnum.Int32;
|
|
}
|
|
return columnTypes;
|
|
}
|
|
function inferColumnType(value) {
|
|
switch (typeof value) {
|
|
case "string":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Text;
|
|
case "bigint":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Int64;
|
|
case "boolean":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.Boolean;
|
|
case "number":
|
|
return import_driver_adapter_utils.ColumnTypeEnum.UnknownNumber;
|
|
case "object":
|
|
return inferObjectType(value);
|
|
default:
|
|
throw new UnexpectedTypeError(value);
|
|
}
|
|
}
|
|
function inferObjectType(value) {
|
|
if (value instanceof ArrayBuffer) {
|
|
return import_driver_adapter_utils.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] === import_driver_adapter_utils.ColumnTypeEnum.Int32 || columnTypes[i] === import_driver_adapter_utils.ColumnTypeEnum.Int64) && !Number.isInteger(value)) {
|
|
result[i] = Math.trunc(value);
|
|
continue;
|
|
}
|
|
if (["number", "bigint"].includes(typeof value) && columnTypes[i] === import_driver_adapter_utils.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 = (0, import_driver_adapter_utils2.Debug)("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 import_async_mutex.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 import_driver_adapter_utils2.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 import_driver_adapter_utils2.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 (0, import_client.createClient)(config);
|
|
}
|
|
};
|
|
// Annotate the CommonJS export names for ESM import in node:
|
|
0 && (module.exports = {
|
|
PrismaLibSql
|
|
});
|