Skip to content
BunBase BunBase BunBase Docs Alpha v0.1.0

Realtime

BunBase supports live data subscriptions via WebSocket. Subscribe to collection-level or record-level events and receive push updates instantly.

const ws = new WebSocket("ws://localhost:8080/realtime");

Send an auth message immediately after connecting:

{ "type": "auth", "token": "<access-token>" }

Response:

{ "type": "ack", "message": "authenticated" }

Without auth, subscriptions to non-public collections will be rejected with an error message.

{ "type": "subscribe", "channel": "collection:posts" }

You receive events for every create, update, and delete on the posts collection.

Use the collection:{name}:mine shorthand. The server resolves this to collection:{name}:user:{userId} at subscribe time — the user ID is never exposed to the client.

{ "type": "subscribe", "channel": "collection:posts:mine" }

Requires authentication. Returns an error if the connection is not authenticated.

{ "type": "subscribe", "channel": "record:posts:01JKX..." }

Use the records:{collection} channel with an ids array:

{
"type": "subscribe",
"channel": "records:posts",
"ids": ["01JKX...", "01JKY...", "01JKZ..."]
}

Subsequent subscribe messages with the same channel and new IDs append to the existing set — you can expand the watched set incrementally.

Unsubscribe specific IDs:

{
"type": "unsubscribe",
"channel": "records:posts",
"ids": ["01JKX..."]
}

Unsubscribe all IDs:

{ "type": "unsubscribe", "channel": "records:posts" }

Add an events array to only receive specific event types:

{
"type": "subscribe",
"channel": "collection:posts",
"events": ["create", "update"]
}

Valid values: "create", "update", "delete". If events is omitted, all event types are delivered (default behaviour).

Add a filter object to only receive events where the record matches all key=value pairs:

{
"type": "subscribe",
"channel": "collection:posts",
"filter": { "status": "published" }
}
  • Filter is exact match only — no operators for MVP.
  • Applies to create and update events.
  • delete events always pass the field filter regardless of the record’s fields.
  • Multiple keys must all match (logical AND).

Combined events + filter:

{
"type": "subscribe",
"channel": "collection:posts",
"events": ["create", "update"],
"filter": { "status": "published", "category": "news" }
}
{
"type": "change",
"channel": "collection:posts",
"event": "create",
"collection": "posts",
"record": {
"_id": "01JKX...",
"_created_at": 1709900000000,
"_updated_at": 1709900000000,
"_owner_id": null,
"title": "Hello BunBase",
"published": true
}
}

Event types: create, update, delete.

{ "type": "unsubscribe", "channel": "collection:posts" }

Client sends a ping; server responds immediately with a pong. Critical for mobile where TCP keepalive is unreliable.

{ "type": "ping" }
{ "type": "pong" }

No state is maintained. The client can use this to detect stale connections.

Request the list of resolved topics the server has registered for this connection:

{ "type": "subscriptions" }

Response:

{ "type": "subscriptions", "channels": ["collection:posts", "records:notes"] }

Useful for reconnect recovery — the client can check which topics it was subscribed to before the connection dropped and re-subscribe.

const unsub = client.realtime.subscribe("collection:posts", (event) => {
console.log(event.event, event.record); // "create" { _id: "...", title: "..." }
});
// Later:
unsub();
const unsub = client.realtime.subscribe(
"collection:posts",
(event) => {
console.log(event.record.status); // always "published"
},
{
events: ["create", "update"],
filter: { status: "published" },
},
);
const unsub = client.realtime.subscribe(
"records:posts",
(event) => {
console.log(event.record._id, event.event);
},
{
ids: ["01JKX...", "01JKY..."],
},
);
try {
await client.realtime.ping(); // resolves on pong, default 5s timeout
console.log("connection alive");
} catch {
console.log("ping timed out — connection stale");
}
const channels = await client.realtime.getSubscriptions();
console.log(channels); // ["collection:posts", "records:notes"]

The SDK reconnects automatically on unexpected disconnect using exponential backoff (500ms → 30s). All active subscriptions, including their events and filter options, are re-sent to the server after reconnect. You do not need to re-subscribe manually.

BunBase runs multiple HTTP workers with reusePort. Each WebSocket connection is owned by one worker. The realtime fan-out layer broadcasts events to all workers via BroadcastChannel so every subscriber receives the event regardless of which worker they are connected to.

Filtered subscriptions (those with events or filter options) are processed per-worker. Each worker maintains a local registry of filtered subscriptions and applies filtering before delivering events directly to the WebSocket connection. This is correct because a WebSocket connection is always owned by exactly one worker.

Subscription permissions follow the collection’s read rule:

  • public — any client, authenticated or not.
  • authenticated — requires auth.
  • owner — requires auth; the connection is subscribed to the per-user topic and only receives its own records.
  • disabled — subscription rejected.

Permission is checked at subscribe time against the collection’s current rules.

Query which users are currently subscribed to a channel on this server worker:

{ "type": "presence", "channel": "collection:posts" }

Response:

{ "type": "presence", "channel": "collection:posts", "users": ["01JKX...", "01JKY..."] }

Returns user IDs of authenticated connections subscribed to the channel on this worker. Unauthenticated connections are not included. In multi-worker deployments, presence reflects only connections on the worker that receives the query — not a global count across all workers.

The server pushes auth events over the WebSocket connection when the session state changes:

EventTrigger
session_revokedAdmin revokes this specific session
sessions_purgedAdmin revokes all sessions for this user
password_changedAdmin resets the user’s password
account_deletedAdmin deletes the user’s account
{ "type": "auth", "event": "session_revoked" }

The server sends the auth event and then closes the connection with code 4401 after 500ms (except for password_changed which notifies without closing).

The TypeScript SDK handles auth events automatically — onAuthChange(null) is called and the WebSocket is closed. No polling is needed when the realtime connection is active.

Auth events are broadcast by the DB worker over BroadcastChannel("bunbase:auth") and routed by the fanout layer to the correct connections via per-session and per-user connection indexes.

The BunBase Studio includes a Realtime page (/realtime) that shows a live stream of WebSocket events for any collection. Select a collection from the dropdown and watch create, update, and delete events appear in real time. Auth and presence events are also shown when they arrive on the connection. Each row shows timestamp, event type, collection, record ID, and a toggleable record detail view. Maximum 100 events are retained; use Clear to reset the log.