SDK
Subscribing to Topics
Subscribe to domain topics with optional filters to receive real-time events.
The subscription system is the primary way clients receive real-time events. You subscribe to domain topics and the platform delivers matching events from any source (database CDC, sync, or socket).
subscribe(options)
subscribe(options: SubscribeOptions): string| Parameter | Type | Required | Description |
|---|---|---|---|
options | SubscribeOptions | Yes | Subscription configuration (see fields below) |
SubscribeOptions fields:
| Field | Type | Required | Description |
|---|---|---|---|
topic | string | Yes | Domain topic name to subscribe to (e.g. "session.status", "order.updated") |
filter | SubscriptionFilter | No | Key-value filter object — only events whose payload matches every filter key are delivered to this client |
returnMode | SyncEventReturnMode | No | Controls what data is included in sync document change events: 'full' (default), 'diff', or 'both'. See Return Mode below. |
Returns: string — a unique subscription ID (e.g. "sub_1", "sub_2") that you pass to unsubscribe() later.
const subId = realtime.subscribe({
topic: 'session.status',
});
console.log(subId); // "sub_1"Listening for Events
on('event', handler) — Global Listener
Receive all events across all active subscriptions:
realtime.on('event', (event: RealtimeEvent) => {
console.log('Topic:', event.topic); // e.g. "session.status"
console.log('Source:', event.source); // "database" | "sync" | "socket"
console.log('Payload:', event.payload); // The actual event data
});on('event:<topic>', handler) — Topic-Specific Listener
Listen for events on a single topic only:
realtime.on('event:session.status', (event: RealtimeEvent) => {
console.log('Session update:', event.payload);
});
realtime.on('event:order.updated', (event: RealtimeEvent) => {
console.log('Order update:', event.payload);
});Subscription Filters
Filters narrow down which events you receive after topic routing. Only events whose payload matches the filter are delivered to your client.
SubscriptionFilter
type SubscriptionFilter = Record<string, unknown>;Filter values can be:
- Scalar — exact equality match against the payload field
{ in: [...] }— match if the payload field equals any value in the array
All filter keys use AND logic — every key must match for the event to be delivered.
Equality Filter
realtime.subscribe({
topic: 'session.status',
filter: { campusId: 12 },
});
// Only receives events where payload.campusId === 12Multiple Fields (AND)
realtime.subscribe({
topic: 'session.status',
filter: {
campusId: 12,
status: 'active',
},
});
// Both conditions must matchin Operator
Match against multiple possible values for a single field:
realtime.subscribe({
topic: 'session.status',
filter: {
status: { in: ['pending', 'active'] },
},
});
// Receives events where status is 'pending' OR 'active'Combined Filters
realtime.subscribe({
topic: 'session.status',
filter: {
campusId: 12,
status: { in: ['pending', 'active'] },
},
});
// campusId must be 12 AND status must be 'pending' or 'active'Return Mode
When subscribing to sync document topics (e.g. sync.session.updated), you can control how much data each event carries over the wire using the returnMode option:
| Mode | payload.data | payload.diff | Description |
|---|---|---|---|
'full' (default) | ✅ | ❌ | Full document state — backward-compatible default |
'diff' | ❌ | ✅ | Only what changed — minimal wire traffic |
'both' | ✅ | ✅ | Full state + change context |
Full Mode (Default)
Receive the complete document data on every change. This is the default and requires no extra configuration:
realtime.subscribe({ topic: 'sync.session.updated' });
realtime.on('event:sync.session.updated', (event) => {
console.log(event.payload.data); // Full document: { status: 'active', count: 5 }
});Diff Mode
Receive only the changes — ideal for large documents where you want to minimize bandwidth:
realtime.subscribe({
topic: 'sync.session.updated',
returnMode: 'diff',
});
realtime.on('event:sync.session.updated', (event) => {
console.log(event.payload.diff);
// {
// added: { newField: 42 },
// removed: ['oldField'],
// changed: { status: { from: 'active', to: 'ended' } }
// }
});Both Mode
Receive both the full document and the diff:
realtime.subscribe({
topic: 'sync.session.updated',
returnMode: 'both',
});
realtime.on('event:sync.session.updated', (event) => {
console.log(event.payload.data); // Full document
console.log(event.payload.diff); // What changed
});Diff Shape
The diff object has the following structure:
interface JsonDiff {
added: Record<string, unknown>; // Keys present in new but not old
removed: string[]; // Keys present in old but not new
changed: Record<string, { // Keys present in both with different values
from: unknown;
to: unknown;
}>;
}Note:
returnModeonly affects sync-sourced events. Non-sync events (database CDC, socket) are delivered unchanged regardless of this setting.
unsubscribe(subscriptionId)
unsubscribe(subscriptionId: string): void| Parameter | Type | Required | Description |
|---|---|---|---|
subscriptionId | string | Yes | The subscription ID returned by subscribe() |
Returns: void — the server stops delivering events for this subscription.
const subId = realtime.subscribe({ topic: 'session.status' });
// Later, when you no longer need these events:
realtime.unsubscribe(subId);Multiple Subscriptions
You can hold multiple active subscriptions simultaneously. Each returns its own subId:
const sub1 = realtime.subscribe({ topic: 'session.status' });
const sub2 = realtime.subscribe({ topic: 'order.updated' });
const sub3 = realtime.subscribe({ topic: 'chat.room.message', filter: { roomId: 'room1' } });
// All events arrive through the same listener
realtime.on('event', (event) => {
switch (event.topic) {
case 'session.status':
handleSession(event);
break;
case 'order.updated':
handleOrder(event);
break;
case 'chat.room.message':
handleChat(event);
break;
}
});
// Unsubscribe individually
realtime.unsubscribe(sub3);Reconnection Behavior
When the WebSocket connection drops and reconnects, all active subscriptions are automatically re-established. You don't need to re-subscribe manually. The client internally tracks all subscription IDs and options, and re-emits them to the server on reconnect.
realtime.on('connection', ({ state }) => {
if (state === 'reconnecting') {
console.log('Reconnecting... subscriptions will be restored');
}
if (state === 'connected') {
console.log('Connected — all subscriptions active');
}
});Event Envelope
Every event delivered to subscribers follows the standard RealtimeEvent envelope:
interface RealtimeEvent {
id: string; // Unique event ID (e.g. "evt_a1b2c3d4")
topic: string; // Domain topic (e.g. "session.status")
source: 'database' | 'sync' | 'socket'; // Which service produced the event
type: string; // Event type (e.g. "database.update")
payload: Record<string, unknown>; // The actual event data
metadata: {
timestamp: number; // Unix timestamp in milliseconds
table?: string; // Source table name (database events only)
operation?: 'insert' | 'update' | 'delete'; // DB operation (database events only)
revision?: number; // Document revision (sync events only)
};
}