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.
Define a vector field
Section titled “Define a vector field”{ "name": "embedding", "type": "vector", "options": { "dimensions": 1536 } }dimensions— the vector length (1-4096). Every value stored on this field must be anumber[]of exactly this length; non-numeric or wrong-length arrays fail validation with422.- Stored as JSON-encoded
number[]in the underlying SQLite column — no schema change beyond that.
Search
Section titled “Search”GET /api/v1/docs?nearVector=[0.12,-0.05,...]&nearVectorField=embedding&nearLimit=10| Param | Notes |
|---|---|
nearVector | JSON-encoded number[]. Length must match the field’s dimensions. |
nearVectorField | Name of the vector field on this collection. |
nearLimit | Top-K results to return (max 1000, default 10). |
nearMinScore | Optional 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}How it works (v1)
Section titled “How it works (v1)”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.
Generating embeddings
Section titled “Generating embeddings”Vaultbase doesn’t bundle an embedding model — bring your own provider. Two common patterns:
From a hook (server-side)
Section titled “From a hook (server-side)”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;}From a client
Section titled “From a client”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.
Tradeoffs
Section titled “Tradeoffs”| v1 (pure JS) | v2 (sqlite-vec, planned) | |
|---|---|---|
| Index build | None | On first write |
| Search cost | O(n × d) per query | O(log n + k × d) |
| Memory | Parsed-vector cache, capped at ~128 MB | Bounded by ANN index |
| Sweet spot | < 50K vectors | Millions |
| Setup | Zero | Extension 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.