mcpField
N
node-sqlite-vec

node-sqlite-vec

github.com/mizchiデータ・データベース41665 回インストール1 週間前に更新
リポジトリ

node-sqlite-vec について

Node 24 以降で、組み込みの `node:sqlite` と `sqlite-vec` を組み合わせてベクトルストレージを構築する実践ガイドです。ベクトル検索や RAG を実装する開発者向けに、拡張機能の読み込みや rowid、テストなどの注意点をまとめています。

主な機能

  1. 01node:sqlite と sqlite-vec の設定
  2. 02SQLite ベクトル拡張の読み込み
  3. 03vec0 の rowid を BigInt でバインド
  4. 04KNN によるベクトルデータ検索
  5. 05Node 組み込みテストランナーでのテスト

活用シーン

  1. 01Node プロジェクトへのベクトル検索ストレージ追加
  2. 02SQLite を使った RAG データ層の構築
  3. 03sqlite-vec の挿入や拡張読み込みエラーの調査
  4. 04直接実行する TypeScript の DB コードのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mizchi/skills --skill node-sqlite-vec

インストール後、Claude Code で/node-sqlite-vecを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

node-sqlite-vec

Node 24 組み込みの node:sqlite と sqlite-vec 拡張機能を組み合わせるためのレシピです。以下の各セクションでは、よくある落とし穴と動作する記述を対にして紹介します。時間がない場合は、まず 落とし穴 セクションをお読みください。

使用する場面

  • Node 24 以降のプロジェクトにベクトル検索/RAG ストレージを追加する場合
  • better-sqlite3 のネイティブビルドの手間を避けたい場合
  • --experimental-strip-types で直接実行する TypeScript ソース

次の場合は、このスキルを使用しないでください。

  • 実行環境が Node ≤ 22 の場合(組み込みの node:sqlite がありません)。better-sqlite3 を使用してください
  • サーバーグレードの並行処理/レプリケーションが必要な場合。SQLite は適切なツールではありません

落とし穴(最初にお読みください)

症状原因対処
vitest で Failed to load url sqlite (resolved id: sqlite) が発生するvite が node: プレフィックスを削除し、存在しない sqlite を検索するためですvitest をやめて、組み込みで vite レイヤーのない node --test を使用します
挿入時に ERR_SQLITE_ERROR: Only integers are allows for primary key values on <table>_vec が発生するJS の number は REAL としてバインドされます。vec0 仮想テーブルでは厳密な整数の rowid が必要ですrowid のバインド値を BigInt(id) でラップします
loadExtension is not a function または "Not authorized to use load_extension" が発生する拡張機能の読み込みゲートを両方とも開く必要がありますnew DatabaseSync(path, { allowExtension: true }) と db.enableLoadExtension(true)
CLI を実行するたびに ExperimentalWarning: SQLite is an experimental feature が出力されるNode 24 は node:sqlite のインポート時にこれを出力しますbin の shebang に #!/usr/bin/env -S node --no-warnings=ExperimentalWarning を指定します
An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled が発生するtsc はデフォルトで import "./x.ts" を拒否します。--experimental-strip-types では .ts 拡張子が必要ですtsconfig.json で allowImportingTsExtensions: true と rewriteRelativeImportExtensions: true を設定します

セットアップ

依存関係
pnpm add sqlite-vec
pnpm add -D @types/node typescript

better-sqlite3 も bindings も node-gyp も不要です。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2023",
    "module": "ES2022",
    "moduleResolution": "bundler",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "allowImportingTsExtensions": true,    // import "./x.ts" allowed in source
    "rewriteRelativeImportExtensions": true, // tsc emits "./x.js" in output
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": ["node"]
  }
}

rewriteRelativeImportExtensions を使うと、ソース内では .ts のインポートを維持して(node --experimental-strip-types tests/x.test.ts が動作します)、tsc による有効な .js 出力も生成できます。

package.json スクリプト
{
  "scripts": {
    "build": "tsc",
    "test": "node --experimental-strip-types --test --test-reporter=spec tests/*.test.ts",
    "typecheck": "tsc --noEmit"
  }
}

--experimental-sqlite は指定しないでください。 Node v22.13.0/v23.4.0 ではこのフラグは不要になりました。このスキルが対象とするすべてのバージョンで node:sqlite はそのままインポートでき、現在このフラグは何もしません。モジュールの安定性が 1.2(Node 25.7 時点ではリリース候補)であるため、Node は引き続き ExperimentalWarning を出力します。以下の「ExperimentalWarning を抑制する」を参照してください。

--experimental-strip-types は別のフラグで、Node 22 で .ts ファイルを直接実行するには引き続き必要です。Node 23 以降では、デフォルトで型が除去されます。

拡張機能を開いて読み込む

// src/db.ts
import { DatabaseSync } from "node:sqlite";
import * as sqliteVec from "sqlite-vec";

export function openDB(path: string) {
  const db = new DatabaseSync(path, { allowExtension: true });
  db.enableLoadExtension(true);
  sqliteVec.load(db);
  db.enableLoadExtension(false); // re-disable after load (defense in depth)
  return db;
}

allowExtension: true(コンストラクター)と enableLoadExtension(true)(インスタンス)の両方が必要です。どちらかが欠けていると、読み込み時に例外が発生します。

確認方法:

const row = db.prepare("SELECT vec_version() AS v").get();
console.log(row.v); // → "v0.1.9"

スキーマ:vec0 仮想テーブル

const EMBEDDING_DIM = 1024;
db.exec(`
  CREATE TABLE IF NOT EXISTS items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    body TEXT NOT NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
  );
`);
db.exec(
  `CREATE VIRTUAL TABLE IF NOT EXISTS item_vec USING vec0(embedding FLOAT[${EMBEDDING_DIM}]);`,
);

通常のテーブル(items)とベクトルテーブル(item_vec)を、id ↔ rowid で結合した状態にします。

挿入:BigInt rowid の落とし穴

const insertItem = db.prepare(
  `INSERT INTO items (body) VALUES (?) RETURNING id`,
);
const insertVec = db.prepare(
  `INSERT INTO item_vec (rowid, embedding) VALUES (?, ?)`,
);

function add(body: string, embedding: Float32Array): number {
  const { id } = insertItem.get(body) as { id: number };

  // ❌ insertVec.run(id, blob)
  //    → ERR_SQLITE_ERROR: Only integers are allows for primary key values on item_vec
  //
  // ✅ Wrap with BigInt to force INTEGER affinity:
  insertVec.run(
    BigInt(id),
    new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength),
  );
  return id;
}

すべての vec0 バインド値に同じ BigInt(id) ラップを適用します(例:DELETE FROM item_vec WHERE rowid = ? など)。通常の items テーブルでは不要です。厳密な整数の rowid を要求するのは vec0 仮想テーブルだけです。

埋め込みは、生のリトルエンディアン浮動小数点バッファ(Float32Array 上の Uint8Array ビュー)として渡します。

KNN クエリ

const sql = `
  SELECT i.id, i.body, v.distance
  FROM item_vec AS v
  JOIN items AS i ON i.id = v.rowid
  WHERE v.embedding MATCH ? AND v.k = ?
  ORDER BY v.distance ASC
  LIMIT ?
`;
const blob = new Uint8Array(query.buffer, query.byteOffset, query.byteLength);
const rows = db.prepare(sql).all(blob, k, k);

v.k = ? は vec0 内で上位 k 件に事前フィルターを適用します。外側の LIMIT は明確にするため、同じ数にしています。WHERE kind = 'X' のようなベクトル検索以外のフィルターも必要な場合は、追加フィルターによって候補が足りなくならないよう、v.k を大きくしてより多くの候補を取得します。

テスト:vitest は使用しないでください

vitest は vite に依存しており、vite のリゾルバーはインポートを解決する際に node: プレフィックスを削除します。node:sqlite にはプレーンな名前へのフォールバックがないため、テストが一つも実行される前に vitest は失敗します。

Error: Failed to load url sqlite (resolved id: sqlite). Does the file exist?

代わりに Node 組み込みのテストランナーを使用してください。

// tests/db.test.ts
import { describe, it, beforeEach, afterEach } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { openDB } from "../src/db.ts";

let dir: string;
let db: ReturnType<typeof openDB>;

beforeEach(() => {
  dir = mkdtempSync(join(tmpdir(), "spike-"));
  db = openDB(join(dir, "test.db"));
});
afterEach(() => {
  db.close();
  rmSync(dir, { recursive: true, force: true });
});

describe("vec0", () => {
  it("loads sqlite-vec", () => {
    const row = db.prepare("SELECT vec_version() AS v").get() as { v: string };
    assert.match(row.v, /^v\d+\.\d+\.\d+/);
  });
});
node --experimental-strip-types --test tests/*.test.ts

実際の DB を使用し、モックは使いません。node:test はデフォルトで TAP を出力します。読みやすい形式にするには、--test-reporter=spec を渡してください。

CLI のシバン

#!/usr/bin/env -S node --no-warnings=ExperimentalWarning
import { DatabaseSync } from "node:sqlite";
// ...

env -S はシバンの残りの部分を空白で分割するため、フラグを渡せます。--no-warnings=ExperimentalWarning を指定しないと、CLI を呼び出すたびに次のように出力されます。

(node:12345) ExperimentalWarning: SQLite is an experimental feature and might change at any time

標準エラー出力を取得するスクリプトでは、これが煩わしくなります。エントリーポイントでのみ抑制し、必要であれば開発中は表示したままにしてください。

安定性のタイムライン

Nodenode:sqlite のステータス
v22.5.0追加。--experimental-sqlite が必要
v22.13.0 / v23.4.0フラグが不要になる。ただし、引き続き実験的(警告は継続)
v25.7.0+安定性 1.2 — リリース候補。警告は引き続き出力

現在の実際の影響(Node 24 LTS「Krypton」、Node 26 current):

  • すべてのスクリプトから --experimental-sqlite を削除してください。指定しても何も起こりません
  • モジュールが 1.0 未満の間は --no-warnings=ExperimentalWarning を維持し、安定性インデックスが 2 に達したら削除してください
  • それ以外(BigInt の rowid、vitest の回避、tsconfig のフラグ)は安定性とは無関係で、引き続き適用されます

参考資料

コメント