Vector search
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: candidates are pulled from SQLite under
the existing access scope (capped at 10 000 rows per query), then ranked in
process. This is fast enough for collections up to a few tens of thousands
of vectors on a single host — the cosine loop is tight (O(n × dimensions)
multiplications) and SQLite happily streams the candidate set.
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 | Bounded by candidate window (≤10K) | 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.