Records
Typed CRUD over collections — list, get, create, update, delete, iterate, ETag concurrency, singletons.
vb.collection("name") returns a typed accessor over a single
collection. Every method maps 1-1 to a server endpoint and returns the
same shape the REST API does.
Quick reference
Section titled “Quick reference”const posts = vb.collection<Post>("posts");
await posts.list({ filter: "published = true", sort: "-created", perPage: 50 });await posts.get("p1");await posts.create({ title: "hi", body: "<p>…</p>" });await posts.update("p1", { title: "renamed" });await posts.delete("p1");
for await (const r of posts.iterate({ filter: "published = true" })) { /* … */ }await vb.collection("posts").list({ page: 1, perPage: 30, filter: 'status = "live" && @request.auth.id = author', sort: "-created,title", expand: "author,tags", fields: "id,title", skipTotal: true, // skip COUNT(*) for huge tables});Returns:
{ data: T[]; page: number; perPage: number; totalItems: number; // -1 when skipTotal totalPages: number; // -1 when skipTotal}iterate — automatic pagination
Section titled “iterate — automatic pagination”for await (const post of vb.collection<Post>("posts").iterate({ filter: 'tags ?= "tutorial"', perPage: 100,})) { console.log(post.title);}Backed by repeated list calls (default perPage: 100) until the server
returns a short or empty page, or the last page. A short page is measured
against the page size the server used, so perPage above the server’s cap
of 500 still reads every record (500 at a time). With skipTotal: true
(totalPages is -1) it keeps paging until a short page.
const post = await vb.collection<Post>("posts").get("p1");
// Expand relations and pick fields, as on list (SDK 0.7.1, server 0.26.1+):const slim = await vb.collection<Post>("posts").get("p1", { expand: "author", fields: "id,title" });A missing record throws VaultbaseError with kind: "server" and
e.data.status === 404 (see Errors). expand and
fields work as on list; a related record the user can’t view is
left out of expand. vb.singleton(name).get() takes the same two options.
create
Section titled “create”const post = await vb.collection<Post>("posts").create({ title: "Hello", body: "<p>World</p>", author: "u_42",});// post.id is the freshly generated UUID; created/updated set by the server.Validation failures throw VaultbaseError with kind: "validation" and
a details: { fieldName: message } map. Match codes pre-1.0:
400→ bad request (kind: "server")403→ rule denied (kind: "auth",reason: "forbidden")404→ collection / parent record missing (kind: "server")405→ the collection takes no creates: aview, asingleton, or anauthcollection (kind: "server"; register users throughvb.auth)409→ delete blocked by arestrictrelation (kind: "conflict")412→ optimistic-concurrency mismatch (kind: "precondition_failed")422→ schema validation, including a unique value already taken (kind: "validation")
update
Section titled “update”const updated = await vb.collection<Post>("posts").update("p1", { title: "Renamed",});PATCH semantics — only the fields you send are touched.
ETag/If-Match concurrency runs automatically (see Errors + ETags):
the SDK caches the ETag from prior reads and attaches If-Match on the
next mutation. Override with:
// Skip the precondition entirely:await posts.update("p1", { title: "x" }, { ifMatch: false });
// Use an explicit tag:await posts.update("p1", { title: "x" }, { ifMatch: "W/\"42\"" });
// The default, spelled out: the cached tag, if anyawait posts.update("p1", { title: "x" }, { ifMatch: "auto" });If the server’s ETag has moved, the SDK throws VaultbaseError with
kind: "precondition_failed" and the server’s current tag in
e.data.currentEtag, so you can retry against the latest state.
delete
Section titled “delete”await vb.collection("posts").delete("p1");Same ifMatch semantics as update. Returns null on success.
Singletons
Section titled “Singletons”A singleton collection has one
record, read and updated at the collection’s own path. vb.singleton(name)
(SDK 0.7.0) returns an accessor with get and update, no id:
const settings = vb.singleton<Settings>("settings");
const current = await settings.get(); // GET /api/v1/settingsawait settings.update({ theme: "dark" }); // PATCH /api/v1/settingsupdate takes the same ifMatch option as collection().update. By default
it sends the ETag from this singleton’s last get or update:
await settings.update({ theme: "dark" }, { ifMatch: false }); // no preconditionawait settings.update({ theme: "dark" }, { ifMatch: "W/\"42\"" }); // explicit tagA stale tag throws VaultbaseError with kind: "precondition_failed".
There is no list, create or delete: the server answers those with 405.
Filter language
Section titled “Filter language”Filters live in the same expression language as collection rules. See
API rules for the full grammar. For ergonomic,
injection-safe construction use the vb.q tag:
const term = req.query.q;const filter = vb.q`title ~ ${term} && status = ${"live"} && deleted = ${false}`;// → 'title ~ "hello" && status = "live" && deleted = false'
await vb.collection("posts").list({ filter });| Interpolation | Encoded as |
|---|---|
string | "…" (escaped) |
number | bare; rejects NaN/Infinity |
boolean | true/false |
null | null |
Date | quoted ISO string |
Array | (…) comma-joined — pairs with ?= / ?~ |
field("name") | bare identifier (validated) |
vb.q ALWAYS quotes strings — to interpolate a column name use the
field() escape:
const sortable = vb.field("title");const filter = vb.q`${sortable} ~ ${"hello"}`;// → 'title ~ "hello"'Request-scoped options
Section titled “Request-scoped options”const ac = new AbortController();posts.list({ signal: ac.signal });ac.abort(); // → throws kind: "aborted"
posts.list({ requestKey: "search-suggest" });posts.list({ requestKey: "search-suggest" });// → first request is auto-cancelled when the second starts (defaultAutoCancel)Disable auto-cancel via requestKey: null per-call or
defaultAutoCancel: false at construction.
With codegen
Section titled “With codegen”When you generate the Schema type via vb-types, every collection
accessor becomes typed:
const vb = new Vaultbase<Schema>({ baseUrl: "..." });
await vb.collection("posts").create({ title: "hi", // ✓ body: "<p>…</p>", // ✓ fakeField: 1, // ✗ TS error});
const list = await vb.collection("posts").list({ filter: "published = true" });list.data[0].title; // string — inferred