Skip to content

Realtime

vb.subscribe(collection, filter, cb) opens a long-lived realtime connection and routes server events to your callback. Backed by a WebSocket where supported, with automatic SSE fallback for environments that can’t open WS (some serverless platforms, certain corporate proxies).

const off = vb.subscribe("posts", "*", (ev) => {
console.log(ev.action, ev.record);
// ev.action: "create" | "update" | "delete"
// ev.record: T (typed via Schema generic)
});
// later, drop just this subscription:
off();
vb.subscribe("posts", "p_42", (ev) => {
// only fires for record id = "p_42"
});

Pass the same expression-language filter you’d use in list:

vb.subscribe("posts", { filter: "author = @request.auth.id" }, (ev) => {
// only fires for events on records you own
});

The server applies your auth + the collection’s view_rule BEFORE broadcasting, so users never see records they couldn’t GET.

const offPosts = vb.subscribe("posts", "*", onPost);
const offUsers = vb.subscribe("users", { filter: "verified = true" }, onUser);
// Disconnect the realtime channel entirely (keeps auth):
vb.closeRealtime();

A single WS connection is shared across all vb.subscribe calls — the manager multiplexes topic subscriptions over the same socket.

The connection authenticates with the same token from vb.client.authStore. Token expired → auto-refresh runs once, the WS reconnects, subscriptions re-arm. Token refresh fails → all subscribers receive a final close event and the connection stays down until the next subscribe call.

vb.realtime.on("status", (s) => {
// s: "connected" | "reconnecting" | "closed"
});

Backoff: 1s, 2s, 4s, … capped at 30s. Reconnect retries forever; cap yourself with vb.closeRealtime() if the user logs out.

If WebSocket opens fail (handshake or auth), the SDK falls back to server-sent events at GET /api/v1/realtime + POST /api/v1/realtime for subscription updates. The same subscribe API works either way — your code doesn’t change.

Events on the wire:

{
"topic": "posts",
"action": "create",
"record": { "id": "p_42", "title": "hi" }
}

For the wildcard * subscription, every collection’s events come through the same callback — switch on ev.topic if you need to disambiguate. See Realtime concept for the full wire format.

  • The realtime channel is in-process per server instance. Behind a load balancer with sticky sessions, fan-out works. Without sticky sessions, run vaultbase in cluster mode (SO_REUSEPORT) so one worker receives every WS frame for a given connection.
  • Filters are evaluated server-side per event; complex filters cost CPU on the broadcast hot path.
  • Disconnects emit a synthetic close event with reason: string. Don’t depend on receive order — slow consumers + retries can shuffle.