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
This commit is contained in:
serhii12 2026-03-11 12:55:20 -03:00
parent ae3604cb88
commit 52ebafce11
2 changed files with 48 additions and 5 deletions

View File

@ -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);
}

View File

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