From 52ebafce11cedf3220ff16d3ee4007f93f6e2f47 Mon Sep 17 00:00:00 2001 From: serhii12 Date: Wed, 11 Mar 2026 12:55:20 -0300 Subject: [PATCH] fix(db): add macOS Homebrew SQLite support for Bun and restore actionable errors On macOS, bun:sqlite uses Apple's system SQLite which is compiled with SQLITE_OMIT_LOAD_EXTENSION, preventing sqlite-vec from loading. The v2.0 refactor also silently swallowed extension loading failures, losing the actionable error messages that existed pre-2.0. - Call Database.setCustomSQLite() on macOS to use Homebrew's SQLite - Eagerly validate extension loading at init, not at first query - Throw with platform-specific fix instructions in loadSqliteVec() - Log warning in store.ts instead of silently catching Fixes #363 --- src/db.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- src/store.ts | 3 ++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/db.ts b/src/db.ts index 1e5e570..5fe7ab4 100644 --- a/src/db.ts +++ b/src/db.ts @@ -4,19 +4,51 @@ * Provides a unified Database export that works under both Bun (bun:sqlite) * and Node.js (better-sqlite3). The APIs are nearly identical — the main * difference is the import path. + * + * On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION, + * which prevents loading native extensions like sqlite-vec. When running under + * Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured + * SQLite build before creating any database instances. */ export const isBun = typeof globalThis.Bun !== "undefined"; let _Database: any; -let _sqliteVecLoad: (db: any) => void; +let _sqliteVecLoad: ((db: any) => void) | null; if (isBun) { // Dynamic string prevents tsc from resolving bun:sqlite on Node.js builds const bunSqlite = "bun:" + "sqlite"; - _Database = (await import(/* @vite-ignore */ bunSqlite)).Database; - const { getLoadablePath } = await import("sqlite-vec"); - _sqliteVecLoad = (db: any) => db.loadExtension(getLoadablePath()); + const BunDatabase = (await import(/* @vite-ignore */ bunSqlite)).Database; + + // See: https://bun.com/docs/runtime/sqlite#setcustomsqlite + if (process.platform === "darwin") { + const homebrewPaths = [ + "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib", // Apple Silicon + "/usr/local/opt/sqlite/lib/libsqlite3.dylib", // Intel + ]; + for (const p of homebrewPaths) { + try { + BunDatabase.setCustomSQLite(p); + break; + } catch {} + } + } + + _Database = BunDatabase; + + // setCustomSQLite may have silently failed — test that extensions actually work. + try { + const { getLoadablePath } = await import("sqlite-vec"); + const vecPath = getLoadablePath(); + const testDb = new BunDatabase(":memory:"); + testDb.loadExtension(vecPath); + testDb.close(); + _sqliteVecLoad = (db: any) => db.loadExtension(vecPath); + } catch { + // Vector search won't work, but BM25 and other operations are unaffected. + _sqliteVecLoad = null; + } } else { _Database = (await import("better-sqlite3")).default; const sqliteVec = await import("sqlite-vec"); @@ -48,7 +80,17 @@ export interface Statement { /** * Load the sqlite-vec extension into a database. + * + * Throws with platform-specific fix instructions when the extension is + * unavailable. */ export function loadSqliteVec(db: Database): void { + if (!_sqliteVecLoad) { + const hint = isBun && process.platform === "darwin" + ? "On macOS with Bun, install Homebrew SQLite: brew install sqlite\n" + + "Or install qmd with npm instead: npm install -g @tobilu/qmd" + : "Ensure the sqlite-vec native module is installed correctly."; + throw new Error(`sqlite-vec extension is unavailable. ${hint}`); + } _sqliteVecLoad(db); } diff --git a/src/store.ts b/src/store.ts index aa5fae4..e5fdffd 100644 --- a/src/store.ts +++ b/src/store.ts @@ -631,9 +631,10 @@ function initializeDatabase(db: Database): void { loadSqliteVec(db); verifySqliteVecLoaded(db); _sqliteVecAvailable = true; - } catch { + } catch (err) { // sqlite-vec is optional — vector search won't work but FTS is fine _sqliteVecAvailable = false; + console.warn(getErrorMessage(err)); } db.exec("PRAGMA journal_mode = WAL"); db.exec("PRAGMA foreign_keys = ON");