Browse Source

Merge pull request #377 from serhii12/fix/sqlite-vec-macos-bun-error-handling

fix(db): add macOS Homebrew SQLite support for Bun and restore actionable errors
Tobias Lütke 2 tháng trước cách đây
mục cha
commit
95dc295433
2 tập tin đã thay đổi với 48 bổ sung5 xóa
  1. 46 4
      src/db.ts
  2. 2 1
      src/store.ts

+ 46 - 4
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);
 }

+ 2 - 1
src/store.ts

@@ -632,9 +632,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");