Errors + ETags
Every SDK method that hits the network throws on non-2xx (except where
explicitly noted). Errors are normalised into VaultbaseError with a
discriminated kind field.
VaultbaseError shape
Section titled “VaultbaseError shape”import { VaultbaseError, isVaultbaseError } from "@vaultbase/sdk";
try { await vb.collection("posts").create({ title: "" });} catch (e) { if (isVaultbaseError(e)) { e.kind; // "validation" | "forbidden" | "not_found" | … e.status; // HTTP status e.message; // server's `error` field e.details; // server's `details` map (validation only) e.etag; // current server tag (precondition_failed only) } throw e;}kind | Status | Meaning |
|---|---|---|
bad_request | 400 | Malformed input the SDK didn’t catch |
unauthenticated | 401 | Missing / invalid token, expired |
forbidden | 403 | Rule denied access |
not_found | 404 | Collection or record missing |
gone | 410 | One-time file token replayed |
conflict | 409 | Unique violation |
precondition_failed | 412 | ETag mismatch — see optimistic concurrency below |
unsupported_media | 415 | MIME outside the global allow-list |
validation | 422 | Schema validation; see details |
rate_limited | 429 | Token bucket exhausted |
server_error | 500 | Unexpected server failure |
network | — | TCP / DNS / TLS failure (no HTTP exchange) |
aborted | — | AbortController triggered or auto-cancel |
timeout | — | Per-request timeout exceeded |
Optimistic concurrency
Section titled “Optimistic concurrency”Vaultbase emits an ETag header on single-record GET / POST / PATCH
responses. The SDK caches it per (collection, id) and attaches
If-Match: <tag> on the next mutation automatically.
When two clients race:
// Client Aconst post = await vb.collection("posts").get("p_42"); // tag W/"42"// Client B updates same record → server's tag is now W/"43"
await vb.collection("posts").update("p_42", { title: "x" });// → throws VaultbaseError, kind: "precondition_failed", etag: "W/\"43\""Recovery strategies:
import { isVaultbaseError } from "@vaultbase/sdk";
async function safeUpdate() { for (let i = 0; i < 3; i++) { try { return await vb.collection("posts").update("p_42", { title: "x" }); } catch (e) { if (!isVaultbaseError(e) || e.kind !== "precondition_failed") throw e; // Re-read with the fresh tag and retry await vb.collection("posts").get("p_42"); } } throw new Error("Concurrency contention — gave up after 3 retries");}Override / disable concurrency
Section titled “Override / disable concurrency”// Use an explicit ETag (e.g. from a prior response):await vb.collection("posts").update("p_42", { title: "x" }, { ifMatch: 'W/"42"' });
// Disable optimistic concurrency for this call:await vb.collection("posts").update("p_42", { title: "x" }, { ifMatch: false });
// Force "auto" (default) explicitly:await vb.collection("posts").update("p_42", { title: "x" }, { ifMatch: "auto" });Inspect / clear the ETag cache
Section titled “Inspect / clear the ETag cache”vb.client.etags.get("posts", "p_42"); // → string | undefinedvb.client.etags.set("posts", "p_42", 'W/"42"'); // manual seedvb.client.etags.delete("posts", "p_42");vb.client.etags.clear(); // wipe allThe cache is in-memory per Vaultbase instance — multiple Vaultbase
objects don’t share state.
Validation errors
Section titled “Validation errors”try { await vb.collection("posts").create({});} catch (e) { if (isVaultbaseError(e) && e.kind === "validation") { e.details; // → { title: "title is required", author: "author is required" } }}details is a { field → message } map suitable for binding directly
to form-field error UI.
Rate-limit errors
Section titled “Rate-limit errors”When the server returns 429, VaultbaseError.kind = "rate_limited".
The Retry-After header is exposed via e.retryAfterSeconds.
try { await vb.collection("posts").list();} catch (e) { if (isVaultbaseError(e) && e.kind === "rate_limited") { await new Promise((r) => setTimeout(r, e.retryAfterSeconds! * 1000)); // …retry } throw e;}Network errors
Section titled “Network errors”kind: "network" covers anything that didn’t reach the server: DNS
failure, TLS error, connection refused, browser offline.
if (isVaultbaseError(e) && e.kind === "network") { showOfflineBanner();}Best practices
Section titled “Best practices”- Always narrow with
isVaultbaseError(e)before reading typed fields. - Don’t retry
validation/forbidden/not_found— they won’t resolve themselves. - Do retry
precondition_failedafter re-reading. - Do retry
network/timeout/rate_limitedwith exponential backoff (capped) — but only for idempotent ops. - Never retry
POSTcreate blindly without an idempotency key (server issues UUIDs, so duplicate creates are visible as two records).