From 0081f010a7611e2009b3ac6a1a93d255f0b4daaa Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 4 Aug 2026 04:08:19 -0300 Subject: [PATCH] fix(cli): fall back to node:sqlite when better-sqlite3 constructor throws at runtime (#8826) --- bin/cli/sqlite.mjs | 2 +- changelog.d/fixes/8826-fix.plan.md | 1 + ...-sqlite-construction-fallback-8826.test.ts | 65 +++++++++++++++++++ .../fixtures/8826-mock-better-sqlite3.mjs | 21 ++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/8826-fix.plan.md create mode 100644 tests/unit/cli-sqlite-construction-fallback-8826.test.ts create mode 100644 tests/unit/fixtures/8826-mock-better-sqlite3.mjs diff --git a/bin/cli/sqlite.mjs b/bin/cli/sqlite.mjs index 2bdb7bd544..ce14541480 100644 --- a/bin/cli/sqlite.mjs +++ b/bin/cli/sqlite.mjs @@ -130,7 +130,7 @@ async function openSqliteDatabase(dbPath, options = {}) { try { return new loaded.Database(dbPath, options); } catch (error) { - throw createSqliteNativeError(error); + return openWithSyncDriverFallback(dbPath, options, error); } } diff --git a/changelog.d/fixes/8826-fix.plan.md b/changelog.d/fixes/8826-fix.plan.md new file mode 100644 index 0000000000..9549452e3c --- /dev/null +++ b/changelog.d/fixes/8826-fix.plan.md @@ -0,0 +1 @@ +- fix(cli): fall back to node:sqlite when better-sqlite3 constructor throws at runtime (#8826) \ No newline at end of file diff --git a/tests/unit/cli-sqlite-construction-fallback-8826.test.ts b/tests/unit/cli-sqlite-construction-fallback-8826.test.ts new file mode 100644 index 0000000000..fc9783d85b --- /dev/null +++ b/tests/unit/cli-sqlite-construction-fallback-8826.test.ts @@ -0,0 +1,65 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { register } from "node:module"; +import Module from "node:module"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// #8826: better-sqlite3 v12 loads its native addon lazily -- import("better-sqlite3") +// SUCCEEDS and only new Database() throws "Could not locate the bindings file" when +// there is no .node binding for the runtime ABI (e.g. CachyOS + Node v26 via AUR). +// openSqliteDatabase() only fell back when the *import* failed; the construction-time +// failure was translated into "Run: omniroute runtime repair" guidance and aborted. + +const FIXTURE_DIR = new URL("fixtures/", import.meta.url).pathname; +const hookPath = path.join(FIXTURE_DIR, "8826-mock-better-sqlite3.mjs"); + +// Register the ESM hook to return a module whose Database constructor throws +register(hookPath, import.meta.url); + +// Patch Module._load so CJS createRequire("better-sqlite3") in driverFactory.ts +// also gets a constructor that throws the bindings error. +const originalLoad = Module._load; +Module._load = function patchedLoad(request, parent, isMain) { + if (request === "better-sqlite3") { + function FakeBetterSqlite() { + throw new Error( + "Could not locate the bindings file. Tried:\n" + + " -> /fake/path/better_sqlite3.node" + ); + } + return FakeBetterSqlite; + } + // @ts-expect-error Module._load is a CJS internal + return originalLoad.call(this, request, parent, isMain); +}; + +const { openOmniRouteDb } = await import("../../bin/cli/sqlite.mjs"); + +test("#8826: openOmniRouteDb() falls back to node:sqlite when better-sqlite3 native binding is missing (construction-time failure)", async (t) => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-8826-")); + t.after(() => { + try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {} + Module._load = originalLoad; + }); + + const origDataDir = process.env.DATA_DIR; + process.env.DATA_DIR = tmpDir; + t.after(() => { + if (origDataDir) { + process.env.DATA_DIR = origDataDir; + } else { + delete process.env.DATA_DIR; + } + }); + + const result = await openOmniRouteDb(); + + assert.ok(result.db, "openOmniRouteDb() should return a working db adapter"); + assert.equal( + result.db.driver, + "node:sqlite", + "should fall back to node:sqlite when better-sqlite3 constructor throws (#8826)" + ); +}); diff --git a/tests/unit/fixtures/8826-mock-better-sqlite3.mjs b/tests/unit/fixtures/8826-mock-better-sqlite3.mjs new file mode 100644 index 0000000000..12ebe2c6ea --- /dev/null +++ b/tests/unit/fixtures/8826-mock-better-sqlite3.mjs @@ -0,0 +1,21 @@ +export async function resolve(specifier, context, nextResolve) { + if (specifier === "better-sqlite3") { + const moduleSource = [ + "class Database {", + " constructor(dbPath, options) {", + ' throw new Error("Could not locate the bindings file. Tried: /fake/path/better_sqlite3.node");', + " }", + "}", + "export default Database;", + ].join("\n"); + + return { + url: + "data:text/javascript," + + encodeURIComponent(moduleSource) + + "#mock-better-sqlite3-8826", + shortCircuit: true, + }; + } + return nextResolve(specifier, context); +} \ No newline at end of file