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");