From 9ad90fe7a361eb13e9c46aee855693ef917c2851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BA=BFt=20Tu=E1=BA=A5n?= Date: Wed, 26 Aug 2026 06:31:34 +0700 Subject: [PATCH] fix(bun): use native bun:sqlite during startup to avoid N-API crash (#11468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated in a combined 4-PR batch worktree off release/v3.8.51 tip (Bun-native SQLite infrastructure cluster from the same contributor). - Focused test: bunSqliteAdapter.test.ts — part of batch's 5/5 node:test run - typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK - Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff Thanks for tracing this to the pre-boot better-sqlite3 require — a native NAPI abort that JS try/catch cannot recover from is exactly the kind of failure mode that needs the guard moved earlier, and aligning bootstrap/sync-env with the already-preferred bun:sqlite driver is the right fix. --- bin/cli/runtime/sqliteRuntime.mjs | 11 ++++++++ changelog.d/fixes/11468-bun-sqlite-startup.md | 1 + scripts/build/bootstrap-env.mjs | 26 +++++++++++++++++ scripts/dev/sync-env.mjs | 28 ++++++++++++++++++- .../unit/db-adapters/bunSqliteAdapter.test.ts | 12 ++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/11468-bun-sqlite-startup.md diff --git a/bin/cli/runtime/sqliteRuntime.mjs b/bin/cli/runtime/sqliteRuntime.mjs index e8ca615bbf..195ce06349 100644 --- a/bin/cli/runtime/sqliteRuntime.mjs +++ b/bin/cli/runtime/sqliteRuntime.mjs @@ -26,6 +26,17 @@ let resolvedCached = null; export async function loadSqliteRuntime() { if (resolvedCached) return resolvedCached; + if (process.versions.bun) { + try { + const bunSqlite = await import("bun:sqlite"); + resolvedCached = { + driver: { kind: "bun-sqlite", Database: bunSqlite.Database }, + source: "bun-sqlite", + }; + return resolvedCached; + } catch {} + } + const bundled = await tryLoadBundled(); if (bundled) { resolvedCached = { driver: bundled, source: "bundled" }; diff --git a/changelog.d/fixes/11468-bun-sqlite-startup.md b/changelog.d/fixes/11468-bun-sqlite-startup.md new file mode 100644 index 0000000000..0adcceea49 --- /dev/null +++ b/changelog.d/fixes/11468-bun-sqlite-startup.md @@ -0,0 +1 @@ +- **fix(bun):** use native `bun:sqlite` in `bootstrap-env` and `sync-env` to avoid loading `better-sqlite3` N-API addon during Bun startup ([#11468](https://github.com/diegosouzapw/OmniRoute/pull/11468)) — thanks @TheDemonTuan diff --git a/scripts/build/bootstrap-env.mjs b/scripts/build/bootstrap-env.mjs index e3bdd673ec..5e02d283ae 100644 --- a/scripts/build/bootstrap-env.mjs +++ b/scripts/build/bootstrap-env.mjs @@ -82,6 +82,32 @@ function hasEncryptedCredentials(dataDir) { const dbPath = join(dataDir, "storage.sqlite"); if (!existsSync(dbPath)) return false; + if (process.versions.bun) { + try { + const { Database } = require("bun:sqlite"); + const db = new Database(dbPath, { readonly: true, create: false }); + try { + const row = db + .query( + `SELECT 1 + FROM provider_connections + WHERE access_token LIKE 'enc:v1:%' + OR refresh_token LIKE 'enc:v1:%' + OR api_key LIKE 'enc:v1:%' + OR id_token LIKE 'enc:v1:%' + LIMIT 1` + ) + .get(); + return !!row; + } finally { + db.close(); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Unable to inspect existing database at ${dbPath}: ${message}`); + } + } + try { const Database = require("better-sqlite3"); const db = new Database(dbPath, { readonly: true, fileMustExist: true }); diff --git a/scripts/dev/sync-env.mjs b/scripts/dev/sync-env.mjs index 35bced0537..f0c58460d3 100644 --- a/scripts/dev/sync-env.mjs +++ b/scripts/dev/sync-env.mjs @@ -93,12 +93,38 @@ function hasEncryptedCredentials(dataDir) { const dbPath = join(dataDir, "storage.sqlite"); if (!existsSync(dbPath)) return false; + const require = createRequire(import.meta.url); + + if (process.versions.bun) { + try { + const { Database } = require("bun:sqlite"); + const db = new Database(dbPath, { readonly: true, create: false }); + try { + const row = db + .query( + `SELECT 1 + FROM provider_connections + WHERE access_token LIKE 'enc:v1:%' + OR refresh_token LIKE 'enc:v1:%' + OR api_key LIKE 'enc:v1:%' + OR id_token LIKE 'enc:v1:%' + LIMIT 1` + ) + .get(); + return !!row; + } finally { + db.close(); + } + } catch { + return false; + } + } + try { // Resolve `require` lazily here (not at module top-level): when this file is // bundled into a standalone route, a top-level `createRequire(import.meta.url)` // throws during module evaluation and 500s the whole route (#5006). Inside this // guarded block, any failure simply returns false (the safe default below). - const require = createRequire(import.meta.url); const Database = require("better-sqlite3"); const db = new Database(dbPath, { readonly: true, fileMustExist: true }); try { diff --git a/tests/unit/db-adapters/bunSqliteAdapter.test.ts b/tests/unit/db-adapters/bunSqliteAdapter.test.ts index d7080a4020..e85be86065 100644 --- a/tests/unit/db-adapters/bunSqliteAdapter.test.ts +++ b/tests/unit/db-adapters/bunSqliteAdapter.test.ts @@ -81,3 +81,15 @@ test("bun:sqlite adapter backs up on-disk databases without serializing them", a assert.deepEqual(execCalls, ["PRAGMA wal_checkpoint(TRUNCATE)"]); assert.equal(fs.readFileSync(destinationPath, "utf8"), sourceContents); }); + +test("loadSqliteRuntime prioritizes bun:sqlite under Bun without loading better-sqlite3", async (t) => { + if (!process.versions.bun) { + t.skip("bun:sqlite is only available under Bun"); + return; + } + + const { loadSqliteRuntime } = await import("../../../bin/cli/runtime/sqliteRuntime.mjs"); + const runtime = await loadSqliteRuntime(); + assert.equal(runtime.driver.kind, "bun-sqlite"); + assert.equal(runtime.source, "bun-sqlite"); +});