From 73136e4f59ba7e2e127cb0dc7dfd0575ac70b2ba Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 19:20:55 -0500 Subject: [PATCH] fix: verify sqlite-vec readiness after extension load. Closes #169 --- src/store.test.ts | 21 +++++++++++++++++++++ src/store.ts | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/store.test.ts b/src/store.test.ts index e3f8955..50df2bf 100644 --- a/src/store.test.ts +++ b/src/store.test.ts @@ -8,6 +8,7 @@ import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach, mock, spyOn } from "bun:test"; import { Database } from "bun:sqlite"; +import * as sqliteVec from "sqlite-vec"; import { unlink, mkdtemp, rmdir, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -15,6 +16,7 @@ import YAML from "yaml"; import { disposeDefaultLlamaCpp } from "./llm.js"; import { createStore, + verifySqliteVecLoaded, getDefaultDbPath, homedir, resolve, @@ -452,6 +454,25 @@ describe("Store Creation", () => { await cleanupTestDb(store); }); + test("verifySqliteVecLoaded throws when sqlite-vec is not loaded", () => { + const db = new Database(":memory:"); + try { + expect(() => verifySqliteVecLoaded(db)).toThrow("sqlite-vec extension is unavailable"); + } finally { + db.close(); + } + }); + + test("verifySqliteVecLoaded succeeds when sqlite-vec is loaded", () => { + const db = new Database(":memory:"); + try { + sqliteVec.load(db); + expect(() => verifySqliteVecLoaded(db)).not.toThrow(); + } finally { + db.close(); + } + }); + test("store.close closes the database connection", async () => { const store = await createTestStore(); store.close(); diff --git a/src/store.ts b/src/store.ts index 3bb5e7c..844f280 100644 --- a/src/store.ts +++ b/src/store.ts @@ -458,17 +458,46 @@ function setSQLiteFromBrewPrefixEnv(): void { setSQLiteFromBrewPrefixEnv(); +function createSqliteVecUnavailableError(reason: string): Error { + return new Error( + "sqlite-vec extension is unavailable. " + + `${reason}. ` + + "Install Homebrew SQLite so the sqlite-vec extension can be loaded, " + + "and set BREW_PREFIX if Homebrew is installed in a non-standard location." + ); +} + +function getErrorMessage(err: unknown): string { + return err instanceof Error ? err.message : String(err); +} + +export function verifySqliteVecLoaded(db: Database): void { + try { + const row = db.prepare(`SELECT vec_version() AS version`).get() as { version?: string } | null; + if (!row?.version || typeof row.version !== "string") { + throw new Error("vec_version() returned no version"); + } + } catch (err) { + const message = getErrorMessage(err); + throw createSqliteVecUnavailableError(`sqlite-vec probe failed (${message})`); + } +} + function initializeDatabase(db: Database): void { try { sqliteVec.load(db); + verifySqliteVecLoaded(db); } catch (err) { - if (err instanceof Error && err.message.includes("does not support dynamic extension loading")) { - throw new Error( - "SQLite build does not support dynamic extension loading. " + - "Install Homebrew SQLite so the sqlite-vec extension can be loaded, " + - "and set BREW_PREFIX if Homebrew is installed in a non-standard location." - ); + const message = getErrorMessage(err); + + if (message.includes("does not support dynamic extension loading")) { + throw createSqliteVecUnavailableError("SQLite build does not support dynamic extension loading"); } + + if (message.includes("sqlite-vec extension is unavailable")) { + throw err; + } + throw err; } db.exec("PRAGMA journal_mode = WAL");