"use strict"; const { Database: NativeDB } = require("./index.js"); const SqliteError = require("./sqlite-error.js"); const convertibleErrorTypes = { TypeError }; const CONVERTIBLE_ERROR_PREFIX = '[TURSO_CONVERT_TYPE]'; function convertError(err) { if ((err.code ?? '').startsWith(CONVERTIBLE_ERROR_PREFIX)) { return createErrorByName(err.code.substring(CONVERTIBLE_ERROR_PREFIX.length), err.message); } return new SqliteError(err.message, err.code, err.rawCode); } function createErrorByName(name, message) { const ErrorConstructor = convertibleErrorTypes[name]; if (!ErrorConstructor) { throw new Error(`unknown error type ${name} from Turso`); } return new ErrorConstructor(message); } /** * Database represents a connection that can prepare and execute SQL statements. */ class Database { /** * Creates a new database connection. If the database file pointed to by `path` does not exists, it will be created. * * @constructor * @param {string} path - Path to the database file. * @param {Object} opts - Options for database behavior. * @param {boolean} [opts.readonly=false] - Open the database in read-only mode. * @param {boolean} [opts.fileMustExist=false] - If true, throws if database file does not exist. * @param {number} [opts.timeout=0] - Timeout duration in milliseconds for database operations. Defaults to 0 (no timeout). */ constructor(path, opts = {}) { opts.readonly = opts.readonly === undefined ? false : opts.readonly; opts.fileMustExist = opts.fileMustExist === undefined ? false : opts.fileMustExist; opts.timeout = opts.timeout === undefined ? 0 : opts.timeout; this.db = new NativeDB(path, opts); this.memory = this.db.memory; const db = this.db; Object.defineProperties(this, { inTransaction: { get() { return db.inTransaction(); }, }, name: { get() { return path; }, }, readonly: { get() { return opts.readonly; }, }, }); } /** * Prepares a SQL statement for execution. * * @param {string} sql - The SQL statement string to prepare. */ prepare(sql) { try { return new Statement(this.db.prepare(sql), this); } catch (err) { throw convertError(err); } } /** * Returns a function that executes the given function in a transaction. * * @param {function} fn - The function to wrap in a transaction. */ transaction(fn) { if (typeof fn !== "function") throw new TypeError("Expected first argument to be a function"); const db = this; const wrapTxn = (mode) => { return (...bindParameters) => { db.exec("BEGIN " + mode); try { const result = fn(...bindParameters); db.exec("COMMIT"); return result; } catch (err) { db.exec("ROLLBACK"); throw err; } }; }; const properties = { default: { value: wrapTxn("") }, deferred: { value: wrapTxn("DEFERRED") }, immediate: { value: wrapTxn("IMMEDIATE") }, exclusive: { value: wrapTxn("EXCLUSIVE") }, database: { value: this, enumerable: true }, }; Object.defineProperties(properties.default.value, properties); Object.defineProperties(properties.deferred.value, properties); Object.defineProperties(properties.immediate.value, properties); Object.defineProperties(properties.exclusive.value, properties); return properties.default.value; } pragma(source, options) { if (options == null) options = {}; if (typeof source !== "string") throw new TypeError("Expected first argument to be a string"); if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object"); const simple = options["simple"]; const pragma = `PRAGMA ${source}`; return simple ? this.db.pragma(source, { simple: true }) : this.db.pragma(source); } backup(filename, options) { throw new Error("not implemented"); } serialize(options) { throw new Error("not implemented"); } function(name, options, fn) { throw new Error("not implemented"); } aggregate(name, options) { throw new Error("not implemented"); } table(name, factory) { throw new Error("not implemented"); } loadExtension(path) { this.db.loadExtension(path); } maxWriteReplicationIndex() { throw new Error("not implemented"); } /** * Executes a SQL statement. * * @param {string} sql - The SQL statement string to execute. */ exec(sql) { try { this.db.exec(sql); } catch (err) { throw convertError(err); } } /** * Interrupts the database connection. */ interrupt() { this.db.interrupt(); } /** * Closes the database connection. */ close() { this.db.close(); } } /** * Statement represents a prepared SQL statement that can be executed. */ class Statement { constructor(stmt, database) { this.stmt = stmt; this.db = database; } /** * Toggle raw mode. * * @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled. */ raw(raw) { this.stmt.raw(raw); return this; } /** * Toggle pluck mode. * * @param pluckMode Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled. */ pluck(pluckMode) { this.stmt.pluck(pluckMode); return this; } get source() { return this.stmt.source; } get reader() { throw new Error("not implemented"); } get source() { return this.stmt.source; } get database() { return this.db; } /** * Executes the SQL statement and returns an info object. */ run(...bindParameters) { return this.stmt.run(bindParameters.flat()); } /** * Executes the SQL statement and returns the first row. * * @param bindParameters - The bind parameters for executing the statement. */ get(...bindParameters) { return this.stmt.get(bindParameters.flat()); } /** * Executes the SQL statement and returns an iterator to the resulting rows. * * @param bindParameters - The bind parameters for executing the statement. */ iterate(...bindParameters) { return this.stmt.iterate(bindParameters.flat()); } /** * Executes the SQL statement and returns an array of the resulting rows. * * @param bindParameters - The bind parameters for executing the statement. */ all(...bindParameters) { return this.stmt.all(bindParameters.flat()); } /** * Interrupts the statement. */ interrupt() { this.stmt.interrupt(); return this; } /** * Returns the columns in the result set returned by this prepared statement. */ columns() { return this.stmt.columns(); } /** * Binds the given parameters to the statement _permanently_ * * @param bindParameters - The bind parameters for binding the statement. * @returns this - Statement with binded parameters */ bind(...bindParameters) { try { return new Statement(this.stmt.bind(bindParameters.flat()), this.db); } catch (err) { throw convertError(err); } } } module.exports = Database; module.exports.SqliteError = SqliteError;