Real-time
Two channels keep clients in sync. Server-sent events push a small "this table changed" signal — perfect for keeping a list fresh. WebSockets add bidirectional rooms for chat, presence, and collaboration. Both are scoped server-side, so clients only hear about changes they're allowed to see.
Live invalidation with SSE
Every CRUD mutation broadcasts an event carrying {table, action} — never the row itself. Clients re-fetch on the event. The SDK handles the connection for you:
const refresh = async () => render(await bm.table('messages').list({ limit: 200 }));
await refresh();
// Re-fetch whenever a message is inserted, updated, or deleted.
const stop = bm.live('messages', refresh);
// call stop() on unmount / route change
Using EventSource directly? The events are named (event: change), so you must wire addEventListener('change', …) — a bare onmessage silently misses every framework broadcast. bm.live already does this correctly.
Rooms and presence with WebSockets
For collaboration — chat, typing indicators, live cursors, WebRTC signaling — use a room. Peers join a named room and broadcast arbitrary JSON to everyone in it; the server relays each message and emits presence events as people come and go.
const room = bm.room('lobby');
// Handlers are keyed by the message's `kind`; 'presence' fires on join/leave.
room.on('chat', (payload, from) => appendChat(from, payload.text));
room.on('presence', (payload) => updateRoster(payload));
room.send({ kind: 'chat', text: 'hello' }); // broadcast to everyone in the room
// room.leave() when you're done
Which channel?
| Use | For |
|---|---|
SSE (bm.live) | Keeping lists and dashboards fresh after writes. One-way, auto-reconnecting, simplest. |
WebSocket rooms (bm.room) | Chat, presence, cursors, and signaling between a handful of peers. |
Broadcast (bm.broadcast) | One-to-many livestreaming to many viewers — the publisher uploads once and the platform fans it out. |
Scoping and anonymous access
Real-time respects the same access rules as the API. On owner-scoped tables, only the owner and admins get the detailed change event; everyone else gets a generic "refetch" hint with no row detail. Tables you mark read: anon or everyone broadcast in full to every viewer — needed for public feeds and live chat. By default both channels require a session; set features.ws_anonymous: true in app.yaml to open them for public-broadcast use cases.
Chat history belongs in a table, not in pure broadcast — a reload would erase messages otherwise. Store each message, mark the table read: anon, and re-fetch on each live event.
Related: Access & roles for how events are scoped, and API & the SDK for the full client.