diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d4c590..8bf578e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,20 +7,51 @@ on: branches: [main] jobs: - test: + test-node: + name: Node ${{ matrix.node-version }} (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + node-version: ["22", "23"] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install SQLite (Ubuntu) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y libsqlite3-dev + + - name: Install SQLite (macOS) + if: runner.os == 'macOS' + run: brew install sqlite + + - run: npm install + + - name: Tests + run: npx vitest run --reporter=verbose test/ + env: + CI: true + + test-bun: + name: Bun (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] - bun-version: ["latest", "1.1.0"] steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ matrix.bun-version }} + bun-version: latest - name: Install SQLite (Ubuntu) if: runner.os == 'Linux' @@ -32,4 +63,9 @@ jobs: - run: bun install - - run: bun test + - name: Tests + run: bun test --timeout 30000 --preload ./src/test-preload.ts test/ + env: + CI: true + DYLD_LIBRARY_PATH: /opt/homebrew/opt/sqlite/lib + LD_LIBRARY_PATH: /usr/lib/x86_64-linux-gnu diff --git a/CHANGELOG.md b/CHANGELOG.md index 148860d..43b0fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ All notable changes to QMD will be documented in this file. +## [1.0.0] - 2026-02-15 + +### Node.js Compatibility + +QMD now runs on both **Node.js (>=22)** and **Bun**. Install with `npm install -g @tobilu/qmd` or `bun install -g @tobilu/qmd` — your choice. The `qmd` wrapper auto-detects Node.js via `tsx` and works out of the box with mise, asdf, nvm, and Homebrew installs. + +### Performance + +- **Parallel embedding & reranking** — multiple contexts split work across CPU cores (or VRAM on GPU), delivering up to **2.7x faster reranking** and significantly faster embedding on multi-core machines +- **Flash attention** — ~20% less VRAM per reranking context, enabling more parallel contexts on GPU +- **Right-sized contexts** — reranker context dropped from 40960 to 2048 tokens (17x less memory), since chunks are capped at ~900 tokens +- **Adaptive parallelism** — automatically scales context count based on available VRAM (GPU) or CPU math cores +- **CPU thread splitting** — each context runs on its own cores for true parallelism instead of contending on a single context + +### GPU Auto-Detection + +- Probes for CUDA, Metal, and Vulkan at startup — uses the best available backend +- Falls back gracefully to CPU with a warning if GPU init fails +- `qmd status` now shows device info (GPU type, VRAM usage) + +### Test Suite + +- Tests split into `src/*.test.ts` (unit), `src/models/*.test.ts` (model), and `src/integration/*.test.ts` (CLI/integration) +- Vitest config for Node.js; bun test still works for Bun +- New `eval-bm25` and `store.helpers.unit` test suites + +### Fixes + +- Prevent VRAM waste from duplicate context creation during concurrent loads +- Collection-aware FTS filtering for scoped keyword search + +--- + ## [0.9.0] - 2026-02-15 Initial public release. @@ -30,5 +63,6 @@ Initial public release. - BM25 score normalization with Math.abs - Bun UTF-8 path corruption workaround +[1.0.0]: https://github.com/tobi/qmd/releases/tag/v1.0.0 [0.9.0]: https://github.com/tobi/qmd/releases/tag/v0.9.0 diff --git a/CLAUDE.md b/CLAUDE.md index 0c80b75..8c5e3b9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,23 +122,15 @@ bun src/qmd.ts # Run from source bun link # Install globally as 'qmd' ``` -## Test Layout +## Tests -- `src/*.test.ts` = unit tests -- `src/models/*.test.ts` = tests that require model/runtime setup -- `src/integration/*.test.ts` = integration tests (CLI subprocesses, daemon/server behavior) - -### Run Order +All tests live in `test/`. Run everything: ```sh -npx vitest run --reporter=verbose src/*.test.ts -npx vitest run --reporter=verbose src/models/*.test.ts -npx vitest run --reporter=verbose src/integration/*.test.ts +npx vitest run --reporter=verbose test/ +bun test --preload ./src/test-preload.ts test/ ``` -Use this order for faster feedback: -`unit -> models -> integration`. - ## Architecture - SQLite FTS5 for full-text search (BM25) diff --git a/package.json b/package.json index f82c0c6..b0e066c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tobilu/qmd", - "version": "0.9.9", + "version": "1.0.0", "description": "Query Markup Documents - On-device hybrid search for markdown files with BM25, vector search, and LLM reranking", "type": "module", "bin": { @@ -15,19 +15,7 @@ "CHANGELOG.md" ], "scripts": { - "test": "vitest run", - "test:unit": "vitest run --reporter=verbose src/*.test.ts", - "test:models": "vitest run --reporter=verbose src/models/*.test.ts", - "test:integration": "vitest run --reporter=verbose src/integration/*.test.ts", - "test:unit:bun": "bun run vitest run --reporter=verbose --testTimeout=120000 src/*.test.ts", - "test:models:bun": "bun run vitest run --reporter=verbose --testTimeout=120000 src/models/*.test.ts", - "test:integration:bun": "bun run vitest run --reporter=verbose --testTimeout=120000 src/integration/*.test.ts", - "test:unit:node": "npx vitest run --reporter=verbose --testTimeout=120000 src/*.test.ts", - "test:models:node": "npx vitest run --reporter=verbose --testTimeout=120000 src/models/*.test.ts", - "test:integration:node": "npx vitest run --reporter=verbose --testTimeout=120000 src/integration/*.test.ts", - "test:ci:bun": "npm run test:unit:bun && npm run test:models:bun && npm run test:integration:bun", - "test:ci:node": "npm run test:unit:node && npm run test:models:node && npm run test:integration:node", - "test:ci": "npm run test:unit && npm run test:models && npm run test:integration", + "test": "vitest run --reporter=verbose test/", "qmd": "tsx src/qmd.ts", "index": "tsx src/qmd.ts index", "vector": "tsx src/qmd.ts vector", diff --git a/src/db.ts b/src/db.ts new file mode 100644 index 0000000..c5d0c2a --- /dev/null +++ b/src/db.ts @@ -0,0 +1,52 @@ +/** + * db.ts - Cross-runtime SQLite compatibility layer + * + * 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. + */ + +export const isBun = typeof globalThis.Bun !== "undefined"; + +let _Database: any; +let _sqliteVecLoad: (db: any) => void; + +if (isBun) { + _Database = (await import("bun:sqlite")).Database; + const { getLoadablePath } = await import("sqlite-vec"); + _sqliteVecLoad = (db: any) => db.loadExtension(getLoadablePath()); +} else { + _Database = (await import("better-sqlite3")).default; + const sqliteVec = await import("sqlite-vec"); + _sqliteVecLoad = (db: any) => sqliteVec.load(db); +} + +/** + * Open a SQLite database. Works with both bun:sqlite and better-sqlite3. + */ +export function openDatabase(path: string): Database { + return new _Database(path) as Database; +} + +/** + * Common subset of the Database interface used throughout QMD. + */ +export interface Database { + exec(sql: string): void; + prepare(sql: string): Statement; + loadExtension(path: string): void; + close(): void; +} + +export interface Statement { + run(...params: any[]): { changes: number; lastInsertRowid: number | bigint }; + get(...params: any[]): any; + all(...params: any[]): any[]; +} + +/** + * Load the sqlite-vec extension into a database. + */ +export function loadSqliteVec(db: Database): void { + _sqliteVecLoad(db); +} diff --git a/src/mcp.ts b/src/mcp.ts index fa9ddfc..8d53ec1 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -8,6 +8,7 @@ */ import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import { randomUUID } from "node:crypto"; import { fileURLToPath } from "url"; import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; @@ -553,6 +554,7 @@ export async function startMcpHttpServer(port: number, options?: { quiet?: boole const store = createStore(); const mcpServer = createMcpServer(store); const transport = new WebStandardStreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), enableJsonResponse: true, }); await mcpServer.connect(transport); diff --git a/src/qmd.ts b/src/qmd.ts index 6c200dd..6c8c8f8 100755 --- a/src/qmd.ts +++ b/src/qmd.ts @@ -1,4 +1,5 @@ -import Database from "better-sqlite3"; +import { openDatabase } from "./db.js"; +import type { Database } from "./db.js"; import fastGlob from "fast-glob"; import { execSync, spawn as nodeSpawn } from "child_process"; import { fileURLToPath } from "url"; diff --git a/src/store.ts b/src/store.ts index b1bb4c7..de3acb3 100644 --- a/src/store.ts +++ b/src/store.ts @@ -11,11 +11,11 @@ * const store = createStore(); */ -import Database from "better-sqlite3"; +import { openDatabase, loadSqliteVec } from "./db.js"; +import type { Database } from "./db.js"; import picomatch from "picomatch"; import { createHash } from "crypto"; import { realpathSync, statSync, mkdirSync } from "node:fs"; -import * as sqliteVec from "sqlite-vec"; import { LlamaCpp, getDefaultLlamaCpp, @@ -618,22 +618,16 @@ export function verifySqliteVecLoaded(db: Database): void { } } +let _sqliteVecAvailable: boolean | null = null; + function initializeDatabase(db: Database): void { try { - sqliteVec.load(db); + loadSqliteVec(db); verifySqliteVecLoaded(db); - } catch (err) { - 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; + _sqliteVecAvailable = true; + } catch { + // sqlite-vec is optional — vector search won't work but FTS is fine + _sqliteVecAvailable = false; } db.exec("PRAGMA journal_mode = WAL"); db.exec("PRAGMA foreign_keys = ON"); @@ -747,7 +741,14 @@ function initializeDatabase(db: Database): void { } +export function isSqliteVecAvailable(): boolean { + return _sqliteVecAvailable === true; +} + function ensureVecTableInternal(db: Database, dimensions: number): void { + if (!_sqliteVecAvailable) { + throw new Error("sqlite-vec is not available. Vector operations require a SQLite build with extension loading support."); + } const tableInfo = db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='vectors_vec'`).get() as { sql: string } | null; if (tableInfo) { const match = tableInfo.sql.match(/float\[(\d+)\]/); @@ -845,7 +846,7 @@ export type Store = { */ export function createStore(dbPath?: string): Store { const resolvedPath = dbPath || getDefaultDbPath(); - const db = new Database(resolvedPath); + const db = openDatabase(resolvedPath); initializeDatabase(db); return { diff --git a/src/integration/cli.test.ts b/test/cli.test.ts similarity index 99% rename from src/integration/cli.test.ts rename to test/cli.test.ts index 5ce9cb2..b723e7d 100644 --- a/src/integration/cli.test.ts +++ b/test/cli.test.ts @@ -21,11 +21,10 @@ let testConfigDir: string; let fixturesDir: string; let testCounter = 0; // Unique counter for each test run -// Get the directory where this test file lives (same as qmd.ts) -const qmdDir = dirname(fileURLToPath(import.meta.url)); -const qmdSrcDir = join(qmdDir, ".."); -const projectRoot = join(qmdSrcDir, ".."); -const qmdScript = join(qmdSrcDir, "qmd.ts"); +// Get the directory where this test file lives +const thisDir = dirname(fileURLToPath(import.meta.url)); +const projectRoot = join(thisDir, ".."); +const qmdScript = join(projectRoot, "src", "qmd.ts"); // Resolve tsx binary from project's node_modules (not cwd-dependent) const tsxBin = (() => { const candidate = join(projectRoot, "node_modules", ".bin", "tsx"); diff --git a/src/eval-bm25.test.ts b/test/eval-bm25.test.ts similarity index 98% rename from src/eval-bm25.test.ts rename to test/eval-bm25.test.ts index 333ba33..aaa9fe8 100644 --- a/src/eval-bm25.test.ts +++ b/test/eval-bm25.test.ts @@ -8,7 +8,7 @@ import { describe, test, expect, beforeAll, afterAll } from "vitest"; import { mkdtempSync, rmSync, readFileSync, readdirSync } from "fs"; import { join, dirname } from "path"; import { tmpdir } from "os"; -import Database from "better-sqlite3"; +import type { Database } from "../src/db.js"; import { createHash } from "crypto"; import { fileURLToPath } from "url"; @@ -17,7 +17,7 @@ import { searchFTS, insertDocument, insertContent, -} from "./store"; +} from "../src/store"; // Set INDEX_PATH before importing store to prevent using global index const tempDir = mkdtempSync(join(tmpdir(), "qmd-eval-unit-")); @@ -92,7 +92,7 @@ describe("BM25 Search (FTS)", () => { db = store.db; // Load and index eval documents - const evalDocsDir = join(dirname(fileURLToPath(import.meta.url)), "../test/eval-docs"); + const evalDocsDir = join(dirname(fileURLToPath(import.meta.url)), "eval-docs"); const files = readdirSync(evalDocsDir).filter(f => f.endsWith(".md")); for (const file of files) { diff --git a/src/models/eval.test.ts b/test/eval.test.ts similarity index 98% rename from src/models/eval.test.ts rename to test/eval.test.ts index 88cd1e0..d575ff8 100644 --- a/src/models/eval.test.ts +++ b/test/eval.test.ts @@ -14,7 +14,8 @@ import { describe, test, expect, beforeAll, afterAll } from "vitest"; import { mkdtempSync, rmSync, readFileSync, readdirSync } from "fs"; import { join } from "path"; import { tmpdir } from "os"; -import Database from "better-sqlite3"; +import { openDatabase } from "../src/db.js"; +import type { Database } from "../src/db.js"; import { createHash } from "crypto"; import { fileURLToPath } from "url"; import { dirname } from "path"; @@ -34,8 +35,8 @@ import { reciprocalRankFusion, DEFAULT_EMBED_MODEL, type RankedResult, -} from "../store"; -import { getDefaultLlamaCpp, formatDocForEmbedding, disposeDefaultLlamaCpp } from "../llm"; +} from "../src/store"; +import { getDefaultLlamaCpp, formatDocForEmbedding, disposeDefaultLlamaCpp } from "../src/llm"; // Eval queries with expected documents const evalQueries: { @@ -109,7 +110,7 @@ describe("BM25 Search (FTS)", () => { db = store.db; // Load and index eval documents - const evalDocsDir = join(dirname(fileURLToPath(import.meta.url)), "../../test/eval-docs"); + const evalDocsDir = join(dirname(fileURLToPath(import.meta.url)), "eval-docs"); const files = readdirSync(evalDocsDir).filter(f => f.endsWith(".md")); for (const file of files) { @@ -181,7 +182,7 @@ describe.skipIf(!!process.env.CI)("Vector Search", () => { const llm = getDefaultLlamaCpp(); store.ensureVecTable(768); // embeddinggemma uses 768 dimensions - const evalDocsDir = join(dirname(fileURLToPath(import.meta.url)), "../../test/eval-docs"); + const evalDocsDir = join(dirname(fileURLToPath(import.meta.url)), "eval-docs"); const files = readdirSync(evalDocsDir).filter(f => f.endsWith(".md")); for (const file of files) { diff --git a/src/formatter.test.ts b/test/formatter.test.ts similarity index 98% rename from src/formatter.test.ts rename to test/formatter.test.ts index 1367f2e..9fee06e 100644 --- a/src/formatter.test.ts +++ b/test/formatter.test.ts @@ -27,8 +27,8 @@ import { documentToXml, formatDocument, type MultiGetFile, -} from "./formatter.js"; -import type { SearchResult, DocumentResult } from "./store.js"; +} from "../src/formatter.js"; +import type { SearchResult, DocumentResult } from "../src/store.js"; // ============================================================================= // Test Fixtures diff --git a/src/models/llm.test.ts b/test/llm.test.ts similarity index 99% rename from src/models/llm.test.ts rename to test/llm.test.ts index 8899771..662d11c 100644 --- a/src/models/llm.test.ts +++ b/test/llm.test.ts @@ -17,7 +17,7 @@ import { SessionReleasedError, type RerankDocument, type ILLMSession, -} from "../llm.js"; +} from "../src/llm.js"; // ============================================================================= // Singleton Tests (no model loading required) diff --git a/src/models/mcp.test.ts b/test/mcp.test.ts similarity index 97% rename from src/models/mcp.test.ts rename to test/mcp.test.ts index 2c0520d..881874d 100644 --- a/src/models/mcp.test.ts +++ b/test/mcp.test.ts @@ -6,16 +6,16 @@ */ import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from "vitest"; -import Database from "better-sqlite3"; -import * as sqliteVec from "sqlite-vec"; +import { openDatabase, loadSqliteVec } from "../src/db.js"; +import type { Database } from "../src/db.js"; import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import { getDefaultLlamaCpp, disposeDefaultLlamaCpp } from "../llm"; +import { getDefaultLlamaCpp, disposeDefaultLlamaCpp } from "../src/llm"; import { mkdtemp, writeFile, readdir, unlink, rmdir } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; import YAML from "yaml"; -import type { CollectionConfig } from "../collections"; +import type { CollectionConfig } from "../src/collections"; // ============================================================================= // Test Database Setup @@ -31,7 +31,7 @@ afterAll(async () => { }); function initTestDatabase(db: Database): void { - sqliteVec.load(db); + loadSqliteVec(db); db.exec("PRAGMA journal_mode = WAL"); // Content-addressable storage - the source of truth for document content @@ -192,8 +192,8 @@ import { DEFAULT_RERANK_MODEL, DEFAULT_MULTI_GET_MAX_BYTES, createStore, -} from "../store"; -import type { RankedResult } from "../store"; +} from "../src/store"; +import type { RankedResult } from "../src/store"; // Note: searchResultsToMcpCsv no longer used in MCP - using structuredContent instead // ============================================================================= @@ -226,7 +226,7 @@ describe("MCP Server", () => { await writeFile(join(testConfigDir, "index.yml"), YAML.stringify(testConfig)); testDbPath = `/tmp/qmd-mcp-test-${Date.now()}.sqlite`; - testDb = new Database(testDbPath); + testDb = openDatabase(testDbPath); initTestDatabase(testDb); seedTestData(testDb); }); @@ -306,7 +306,7 @@ describe("MCP Server", () => { }); test("returns empty when no vector table exists", async () => { - const emptyDb = new Database(":memory:"); + const emptyDb = openDatabase(":memory:"); initTestDatabase(emptyDb); emptyDb.exec("DROP TABLE IF EXISTS vectors_vec"); @@ -649,7 +649,7 @@ describe("MCP Server", () => { WHERE d.path = ? AND d.active = 1 `).get(path) as { filepath: string; display_path: string; body: string } | null; - expect(doc).toBeUndefined(); + expect(doc == null).toBe(true); // bun:sqlite returns null, better-sqlite3 returns undefined }); test("includes context in document body", () => { @@ -865,8 +865,8 @@ describe("MCP Server", () => { // HTTP Transport Tests // ============================================================================= -import { startMcpHttpServer, type HttpServerHandle } from "../mcp"; -import { enableProductionMode } from "../store"; +import { startMcpHttpServer, type HttpServerHandle } from "../src/mcp"; +import { enableProductionMode } from "../src/store"; describe("MCP HTTP Transport", () => { let handle: HttpServerHandle; @@ -880,7 +880,7 @@ describe("MCP HTTP Transport", () => { beforeAll(async () => { // Create isolated test database with seeded data httpTestDbPath = `/tmp/qmd-mcp-http-test-${Date.now()}.sqlite`; - const db = new Database(httpTestDbPath); + const db = openDatabase(httpTestDbPath); initTestDatabase(db); seedTestData(db); db.close(); @@ -946,17 +946,28 @@ describe("MCP HTTP Transport", () => { // MCP protocol over HTTP // --------------------------------------------------------------------------- + /** Track session ID returned by initialize (MCP Streamable HTTP spec) */ + let sessionId: string | null = null; + /** Send a JSON-RPC message to /mcp and return the parsed response. * MCP Streamable HTTP requires Accept header with both JSON and SSE. */ async function mcpRequest(body: object): Promise<{ status: number; json: any; contentType: string | null }> { + const headers: Record = { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + }; + if (sessionId) headers["mcp-session-id"] = sessionId; + const res = await fetch(`${baseUrl}/mcp`, { method: "POST", - headers: { - "Content-Type": "application/json", - "Accept": "application/json, text/event-stream", - }, + headers, body: JSON.stringify(body), }); + + // Capture session ID from initialize responses + const sid = res.headers.get("mcp-session-id"); + if (sid) sessionId = sid; + const json = await res.json(); return { status: res.status, json, contentType: res.headers.get("content-type") }; } diff --git a/src/store-paths.test.ts b/test/store-paths.test.ts similarity index 99% rename from src/store-paths.test.ts rename to test/store-paths.test.ts index ca8726e..52d658b 100644 --- a/src/store-paths.test.ts +++ b/test/store-paths.test.ts @@ -16,7 +16,7 @@ import { normalizePathSeparators, getRelativePathFromPrefix, resolve, -} from "./store.js"; +} from "../src/store.js"; // ============================================================================= // Test Utilities diff --git a/src/store.helpers.unit.test.ts b/test/store.helpers.unit.test.ts similarity index 99% rename from src/store.helpers.unit.test.ts rename to test/store.helpers.unit.test.ts index bd30e9d..3303187 100644 --- a/src/store.helpers.unit.test.ts +++ b/test/store.helpers.unit.test.ts @@ -15,7 +15,7 @@ import { normalizeDocid, isDocid, handelize, -} from "./store"; +} from "../src/store"; // ============================================================================= // Path Utilities diff --git a/src/models/store.test.ts b/test/store.test.ts similarity index 99% rename from src/models/store.test.ts rename to test/store.test.ts index b838087..9c38477 100644 --- a/src/models/store.test.ts +++ b/test/store.test.ts @@ -7,13 +7,13 @@ */ import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from "vitest"; -import Database from "better-sqlite3"; -import * as sqliteVec from "sqlite-vec"; +import { openDatabase, loadSqliteVec } from "../src/db.js"; +import type { Database } from "../src/db.js"; import { unlink, mkdtemp, rmdir, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import YAML from "yaml"; -import { disposeDefaultLlamaCpp } from "../llm.js"; +import { disposeDefaultLlamaCpp } from "../src/llm.js"; import { createStore, verifySqliteVecLoaded, @@ -49,8 +49,8 @@ import { type DocumentResult, type SearchResult, type RankedResult, -} from "../store.js"; -import type { CollectionConfig } from "../collections.js"; +} from "../src/store.js"; +import type { CollectionConfig } from "../src/collections.js"; // ============================================================================= // LlamaCpp Setup @@ -431,7 +431,8 @@ describe("Store Creation", () => { test("createStore creates a new store with custom path", async () => { const store = await createTestStore(); expect(store.dbPath).toBe(testDbPath); - expect(store.db).toBeInstanceOf(Database); + expect(store.db).toBeDefined(); + expect(typeof store.db.exec).toBe("function"); await cleanupTestDb(store); }); @@ -461,7 +462,7 @@ describe("Store Creation", () => { }); test("verifySqliteVecLoaded throws when sqlite-vec is not loaded", () => { - const db = new Database(":memory:"); + const db = openDatabase(":memory:"); try { expect(() => verifySqliteVecLoaded(db)).toThrow("sqlite-vec extension is unavailable"); } finally { @@ -470,9 +471,9 @@ describe("Store Creation", () => { }); test("verifySqliteVecLoaded succeeds when sqlite-vec is loaded", () => { - const db = new Database(":memory:"); + const db = openDatabase(":memory:"); try { - sqliteVec.load(db); + loadSqliteVec(db); expect(() => verifySqliteVecLoaded(db)).not.toThrow(); } finally { db.close(); diff --git a/vitest.config.ts b/vitest.config.ts index 4870abb..fb6d61e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,5 +3,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { testTimeout: 30000, + include: ["test/**/*.test.ts"], }, });