/** * Compatibility wrapper around Node.js built-in node:sqlite (DatabaseSync). * Provides a better-sqlite3-compatible API so the rest of the codebase * works without the native module. * * Available on Node.js >= 22.5.0 (node:sqlite is experimental). * On older Node versions, require() will throw and the caller should * handle the error (e.g. show an informative message). * * @file This module exports a Database class that wraps node:sqlite's DatabaseSync to provide a better-sqlite3-like API: `pragma`, `transaction`, `function`, `stmt.pluck()` and the `readonly`/`fileMustExist` options. A plugin install has no better-sqlite3 build, so there this IS the driver. * @author Nguyễn Ngọc Trí Vĩ */ const fs = require("fs"); const { DatabaseSync } = require("node:sqlite"); class Database { /** better-sqlite3's `readonly` and `fileMustExist` options, mapped. */ constructor(filePath, { readonly = false, fileMustExist = false } = {}) { if (fileMustExist && !fs.existsSync(filePath)) { throw new Error(`unable to open database file: ${filePath} does not exist`); } this._db = new DatabaseSync(filePath, { readOnly: readonly }); } exec(sql) { this._db.exec(sql); return this; } pragma(str, options) { if (str.includes("=")) { this._db.exec(`PRAGMA ${str}`); return undefined; } const row = this._db.prepare(`PRAGMA ${str}`).get(); if (!row) return undefined; const keys = Object.keys(row); if (options?.simple || keys.length === 1) return row[keys[0]]; return row; } prepare(sql) { const stmt = this._db.prepare(sql); // better-sqlite3's pluck(): get/all/iterate yield the first column only. stmt.pluck = () => { const first = (row) => (row === undefined ? undefined : Object.values(row)[0]); const { get, all, iterate } = stmt; stmt.get = (...a) => first(get.apply(stmt, a)); stmt.all = (...a) => all.apply(stmt, a).map(first); stmt.iterate = function* (...a) { for (const row of iterate.apply(stmt, a)) yield first(row); }; return stmt; }; return stmt; } /** better-sqlite3's db.function(name, [options], fn): a SQL function. */ function(name, options, fn) { if (typeof options === "function") [fn, options] = [options, {}]; this._db.function(name, options, fn); return this; } transaction(fn) { const db = this._db; const wrapper = (...args) => { db.exec("BEGIN"); try { const result = fn(...args); db.exec("COMMIT"); return result; } catch (err) { db.exec("ROLLBACK"); throw err; } }; return wrapper; } close() { this._db.close(); } } module.exports = Database;