Procházet zdrojové kódy

Merge pull request #178 from tobi/release/v1.0.0

Release v1.0.0
Tobias Lütke před 3 měsíci
rodič
revize
7acba1c451

+ 40 - 4
.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

+ 34 - 0
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
 

+ 4 - 12
CLAUDE.md

@@ -122,23 +122,15 @@ bun src/qmd.ts <command>   # 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)

+ 2 - 14
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",

+ 52 - 0
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);
+}

+ 2 - 0
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);

+ 2 - 1
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";

+ 17 - 16
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 {

+ 4 - 5
src/integration/cli.test.ts → 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");

+ 3 - 3
src/eval-bm25.test.ts → 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) {

+ 6 - 5
src/models/eval.test.ts → 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) {

+ 2 - 2
src/formatter.test.ts → 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

+ 1 - 1
src/models/llm.test.ts → 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)

+ 28 - 17
src/models/mcp.test.ts → 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<string, string> = {
+      "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") };
   }

+ 1 - 1
src/store-paths.test.ts → test/store-paths.test.ts

@@ -16,7 +16,7 @@ import {
   normalizePathSeparators,
   getRelativePathFromPrefix,
   resolve,
-} from "./store.js";
+} from "../src/store.js";
 
 // =============================================================================
 // Test Utilities

+ 1 - 1
src/store.helpers.unit.test.ts → test/store.helpers.unit.test.ts

@@ -15,7 +15,7 @@ import {
   normalizeDocid,
   isDocid,
   handelize,
-} from "./store";
+} from "../src/store";
 
 // =============================================================================
 // Path Utilities

+ 10 - 9
src/models/store.test.ts → 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();

+ 1 - 0
vitest.config.ts

@@ -3,5 +3,6 @@ import { defineConfig } from "vitest/config";
 export default defineConfig({
   test: {
     testTimeout: 30000,
+    include: ["test/**/*.test.ts"],
   },
 });