"use strict"; const { load, currentTarget } = require("@neon-rs/load"); const { familySync, GLIBC } = require("detect-libc"); function requireNative() { if (process.env.LIBSQL_JS_DEV) { return load(__dirname) } let target = currentTarget(); // Workaround for Bun, which reports a musl target, but really wants glibc... if (familySync() == GLIBC) { switch (target) { case "linux-x64-musl": target = "linux-x64-gnu"; break; case "linux-arm64-musl": target = "linux-arm64-gnu"; break; } } return require(`@libsql/${target}`); } const { databaseOpen, databaseOpenWithRpcSync, databaseInTransaction, databaseClose, databaseSyncSync, databaseSyncUntilSync, databaseExecSync, databasePrepareSync, databaseDefaultSafeIntegers, databaseLoadExtension, databaseMaxWriteReplicationIndex, statementRaw, statementIsReader, statementGet, statementRun, statementRowsSync, statementColumns, statementSafeIntegers, rowsNext, } = requireNative(); const SqliteError = require("./sqlite-error"); function convertError(err) { if (err.libsqlError) { return new SqliteError(err.message, err.code, err.rawCode); } return err; } /** * 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. */ constructor(path, opts) { const encryptionCipher = opts?.encryptionCipher ?? "aes256cbc"; if (opts && opts.syncUrl) { var authToken = ""; if (opts.syncAuth) { console.warn("Warning: The `syncAuth` option is deprecated, please use `authToken` option instead."); authToken = opts.syncAuth; } else if (opts.authToken) { authToken = opts.authToken; } const encryptionKey = opts?.encryptionKey ?? ""; const syncPeriod = opts?.syncPeriod ?? 0.0; const readYourWrites = opts?.readYourWrites ?? true; this.db = databaseOpenWithRpcSync(path, opts.syncUrl, authToken, encryptionCipher, encryptionKey, syncPeriod, readYourWrites); } else { const authToken = opts?.authToken ?? ""; const encryptionKey = opts?.encryptionKey ?? ""; this.db = databaseOpen(path, authToken, encryptionCipher, encryptionKey); } // TODO: Use a libSQL API for this? this.memory = path === ":memory:"; this.readonly = false; this.name = ""; this.open = true; const db = this.db; Object.defineProperties(this, { inTransaction: { get() { return databaseInTransaction(db); } }, }); } sync() { return databaseSyncSync.call(this.db); } syncUntil(replicationIndex) { return databaseSyncUntilSync.call(this.db, replicationIndex); } /** * Prepares a SQL statement for execution. * * @param {string} sql - The SQL statement string to prepare. */ prepare(sql) { try { const stmt = databasePrepareSync.call(this.db, sql); return new Statement(stmt); } 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 stmt = this.prepare(`PRAGMA ${source}`, this, true); return simple ? stmt.pluck().get() : stmt.all(); } backup(filename, options) { throw new Error("not implemented"); } serialize(options) { throw new Error("not implemented"); } function(name, options, fn) { // Apply defaults if (options == null) options = {}; if (typeof options === "function") { fn = options; options = {}; } // Validate arguments if (typeof name !== "string") throw new TypeError("Expected first argument to be a string"); if (typeof fn !== "function") throw new TypeError("Expected last argument to be a function"); if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object"); if (!name) throw new TypeError( "User-defined function name cannot be an empty string" ); throw new Error("not implemented"); } aggregate(name, options) { // Validate arguments if (typeof name !== "string") throw new TypeError("Expected first argument to be a string"); if (typeof options !== "object" || options === null) throw new TypeError("Expected second argument to be an options object"); if (!name) throw new TypeError( "User-defined function name cannot be an empty string" ); throw new Error("not implemented"); } table(name, factory) { // Validate arguments if (typeof name !== "string") throw new TypeError("Expected first argument to be a string"); if (!name) throw new TypeError( "Virtual table module name cannot be an empty string" ); throw new Error("not implemented"); } loadExtension(...args) { databaseLoadExtension.call(this.db, ...args); } maxWriteReplicationIndex() { return databaseMaxWriteReplicationIndex.call(this.db) } /** * Executes a SQL statement. * * @param {string} sql - The SQL statement string to execute. */ exec(sql) { try { databaseExecSync.call(this.db, sql); } catch (err) { throw convertError(err); } } /** * Closes the database connection. */ close() { databaseClose.call(this.db); this.open = false; } /** * Toggle 64-bit integer support. */ defaultSafeIntegers(toggle) { databaseDefaultSafeIntegers.call(this.db, toggle ?? true); return this; } unsafeMode(...args) { throw new Error("not implemented"); } } /** * Statement represents a prepared SQL statement that can be executed. */ class Statement { constructor(stmt) { this.stmt = stmt; } /** * Toggle raw mode. * * @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled. */ raw(raw) { statementRaw.call(this.stmt, raw ?? true); return this; } get reader() { return statementIsReader.call(this.stmt); } /** * Executes the SQL statement and returns an info object. */ run(...bindParameters) { try { if (bindParameters.length == 1 && typeof bindParameters[0] === "object") { return statementRun.call(this.stmt, bindParameters[0]); } else { return statementRun.call(this.stmt, bindParameters.flat()); } } catch (err) { throw convertError(err); } } /** * Executes the SQL statement and returns the first row. * * @param bindParameters - The bind parameters for executing the statement. */ get(...bindParameters) { if (bindParameters.length == 1 && typeof bindParameters[0] === "object") { return statementGet.call(this.stmt, bindParameters[0]); } else { return statementGet.call(this.stmt, 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) { var rows = undefined; if (bindParameters.length == 1 && typeof bindParameters[0] === "object") { rows = statementRowsSync.call(this.stmt, bindParameters[0]); } else { rows = statementRowsSync.call(this.stmt, bindParameters.flat()); } const iter = { nextRows: Array(100), nextRowIndex: 100, next() { if (this.nextRowIndex === 100) { rowsNext.call(rows, this.nextRows); this.nextRowIndex = 0; } const row = this.nextRows[this.nextRowIndex]; this.nextRows[this.nextRowIndex] = undefined; if (!row) { return { done: true }; } this.nextRowIndex++; return { value: row, done: false }; }, [Symbol.iterator]() { return this; }, }; return iter; } /** * Executes the SQL statement and returns an array of the resulting rows. * * @param bindParameters - The bind parameters for executing the statement. */ all(...bindParameters) { const result = []; for (const row of this.iterate(...bindParameters)) { result.push(row); } return result; } /** * Returns the columns in the result set returned by this prepared statement. */ columns() { return statementColumns.call(this.stmt); } /** * Toggle 64-bit integer support. */ safeIntegers(toggle) { statementSafeIntegers.call(this.stmt, toggle ?? true); return this; } } module.exports = Database; module.exports.SqliteError = SqliteError;