Skip to content

Vector search

Embedding-based similarity search via the vector field type and the nearVector list-API params.

Vaultbase ships a built-in vector field type and cosine-similarity search on the standard records list endpoint. Single-binary, no extension to install, no external service to call — embeddings live next to the rest of your collection.

{ "name": "embedding", "type": "vector",
"options": { "dimensions": 1536 } }
  • dimensions — the vector length (1-4096). Every value stored on this field must be a number[] of exactly this length; non-numeric or wrong-length arrays fail validation with 422.
  • Stored as JSON-encoded number[] in the underlying SQLite column — no schema change beyond that.
GET /api/v1/docs?nearVector=[0.12,-0.05,...]&nearVectorField=embedding&nearLimit=10
ParamNotes
nearVectorJSON-encoded number[]. Length must match the field’s dimensions.
nearVectorFieldName of the vector field on this collection.
nearLimitTop-K results to return (max 1000, default 10).
nearMinScoreOptional minimum cosine similarity in [-1, 1]; matches below this are dropped.

The candidate set is filtered by filter + the collection’s list_rule first, so you can never rank rows the caller can’t otherwise see. After that, the candidates are ranked in-process by cosine similarity and returned sorted descending by _score.

{
"data": [
{ "id": "d1", "title": "...", "embedding": [...], "_score": 0.94 },
{ "id": "d2", "title": "...", "embedding": [...], "_score": 0.81 }
],
"page": 1, "perPage": 10, "totalItems": 10, "totalPages": 1
}

The v1 implementation is pure JS: candidate ids are pulled from SQLite under the existing access scope (filter + list_rule, newest 10 000 rows per query), scored by cosine similarity in process, and only the top K records are then loaded in full.

Parsed vectors are cached in memory as Float32Arrays, keyed by record and updated_at, so a search only parses rows that are new or changed since the last one. On a single host, 10 000 × 384-dim vectors take ~13 ms per repeat search (~30 ms at 1536 dims); the first search after a restart parses everything and costs ~10× more. The cache is capped at ~128 MB, evicting the least recently searched collections first, and is cleared on schema changes.

For larger collections, the planned v2 path is to load the sqlite-vec extension via Database.loadExtension() and push the ANN search down into SQL. The query shape (?nearVector=…&nearVectorField=…&nearLimit=…) is stable across both implementations.

Vaultbase doesn’t bundle an embedding model — bring your own provider. Two common patterns:

Use helpers.http to call your provider on beforeCreate / beforeUpdate:

// beforeCreate on `docs`
if (typeof ctx.record.body === "string") {
const r = await ctx.helpers.http.postJson<{ data: [{ embedding: number[] }] }>(
"https://api.openai.com/v1/embeddings",
{ input: ctx.record.body, model: "text-embedding-3-small" },
{ Authorization: `Bearer ${ctx.helpers.os.env("OPENAI_API_KEY")}` },
);
ctx.record.embedding = r.data[0].embedding;
}

Compute the embedding on the client and PATCH it in. Useful when the embedding source is a raw upload and you’d rather not stream it through the server.

v1 (pure JS)v2 (sqlite-vec, planned)
Index buildNoneOn first write
Search costO(n × d) per queryO(log n + k × d)
MemoryParsed-vector cache, capped at ~128 MBBounded by ANN index
Sweet spot< 50K vectorsMillions
SetupZeroExtension build per platform

If your collection grows past the v1 ceiling, the workaround is to shard across multiple collections (e.g. docs_2026q1, docs_2026q2) and union results client-side until v2 lands.