@monlite/vector
Local vector / semantic search for @monlite/core,
powered by sqlite-vec. Adds collection.findSimilar()
— RAG and AI-agent memory, all in your local .db.
A monlite plugin. Store documents with an embedding field, and search by nearest neighbour. The
index is maintained automatically on every write, including changes applied by @monlite/sync.
import { createDb } from "@monlite/core";
import { vector } from "@monlite/vector";
const db = createDb("./app.db", {
allowExtensions: true,
plugins: [vector({ docs: { field: "embedding", dimensions: 384 } })],
});
await db.collection("docs").create({
data: { title: "Black holes", embedding: await embed("Black holes …") },
});
const hits = await db.collection("docs").findSimilar({
vector: await embed("astrophysics"),
topK: 5,
where: { published: true },
});
You bring the embeddings (from any model — OpenAI, local, etc.); monlite stores and searches them.
Install
npm install @monlite/core @monlite/vector
@monlite/vector depends on sqlite-vec, which ships prebuilt native binaries. It works on
both monlite backends (better-sqlite3 and node:sqlite), but the database must be opened with
{ allowExtensions: true }.
API
Plugin
vector(spec: Record<string, {
field: string;
dimensions: number;
distance?: "l2" | "cosine";
}>): MonlitePlugin
findSimilar
collection.findSimilar({
vector: number[],
topK?: number,
where?: WhereInput<T>,
}): Promise<Array<WithId<T> & { _distance: number }>>
Results are ordered nearest-first; _distance is the raw metric (smaller = closer). Documents
without a valid embedding field are not indexed.
Reindex
import { reindex } from "@monlite/vector";
reindex(db, "docs", { field: "embedding", dimensions: 384 });
Dynamic store — createVectorStore(db)
The vector() plugin attaches semantic search to a collection with a static spec. For a
programmatic store over collections created at runtime — RAG corpora, per-tenant indexes,
"give me a vector table for this id" — use createVectorStore(db):
import { createDb } from "@monlite/core";
import { createVectorStore } from "@monlite/vector";
const db = createDb("./rag.db", { allowExtensions: true });
const store = createVectorStore(db);
store.ensureCollection("docs", { dimensions: 384, indexedFields: ["docId"] });
store.upsert("docs", [{ id: "c1", vector: emb, metadata: { docId: "d1", text } }]);
store.search("docs", { vector: q, topK: 5, where: { docId: "d1" } });
store.delete("docs", { where: { docId: "d1" } });
Synchronous (raw SQLite). Each collection maps to its own vec0 table; indexedFields become
filterable metadata columns, and the rest of metadata rides in a +payload column. Scales
well to ~1M vectors locally; beyond that use a dedicated vector database.
How it works
For each configured collection, the plugin creates a sqlite-vec vec0 virtual table keyed by
the document _id, backfills existing documents on init, and keeps the index current via the
plugin afterWrite hook. findSimilar runs a KNN query and returns the live documents in
distance order.
Multi-process ingest
afterWrite only sees writes from its own connection. If a separate process ingests vectors
(the common agent pattern), call collection.catchUp() in the searching process to
incrementally index new vectors and reconcile cross-process deletes before querying — no full
reindex needed:
db.collection("memories").catchUp();
await db.collection("memories").findSimilar({ vector, topK: 5 });
Hybrid search
Combine keyword (FTS) and semantic (vector) results for the best retrieval quality.
hybridSearch runs both and fuses the rankings with Reciprocal Rank Fusion — no score
normalization needed.
import { createDb } from "@monlite/core";
import { fts } from "@monlite/fts";
import { vector, hybridSearch } from "@monlite/vector";
const db = createDb("./app.db", {
allowExtensions: true,
plugins: [
fts({ docs: ["title", "body"] }),
vector({ docs: { field: "embedding", dimensions: 384 } }),
],
});
const hits = await hybridSearch(db.collection("docs"), {
text: "black holes",
vector: await embed("black holes"),
topK: 10,
where: { published: true },
});
If @monlite/fts is not configured on the collection, it falls back to vector-only.
License
MIT