Platform Services
Sync Service
Realtime document synchronization with revision tracking, partial updates, and presence.
The Sync Service provides Twilio Sync–style realtime document state management. Documents are stored in Redis (RedisJSON) with automatic revision tracking, concurrent writer safety, and real-time change broadcasting.
Core Concepts
Documents
A sync document is a JSON object identified by a service namespace and a unique ID. Every mutation increments the document's revision number, enabling optimistic concurrency control.
interface SyncDocumentEnvelope {
service: string; // Namespace (e.g. "session")
id: string; // Unique document ID
revision: number; // Auto-incrementing version
data: Record<string, any>; // Document payload
createdAt: number; // Unix timestamp
updatedAt: number; // Unix timestamp
deleted?: boolean; // Soft-delete flag
expiresAt?: number; // Optional TTL
}Redis Storage
Documents are stored in RedisJSON at the following key patterns:
| Key Pattern | Purpose |
|---|---|
syncdoc:data:{service}:{id} | Document data |
syncdoc:channel:{service}:{id} | Change notification channel |
syncdoc:presence:{service}:{id} | Presence tracking |
Operations
Create
Create a new document with optional initial data:
const doc = await realtime.sync.create('session', {
status: 'active',
participantCount: 0,
});
// doc.id → "doc_a1b2c3d4"
// doc.revision → 1Read
Fetch the current state of a document:
const doc = await realtime.sync.get('session', 'doc_a1b2c3d4');
console.log(doc.data.status); // "active"
console.log(doc.revision); // 1Replace
Replace the entire document data:
await realtime.sync.replace('session', 'doc_a1b2c3d4', {
status: 'ended',
endedAt: Date.now(),
});Merge
Apply a partial update (deep merge):
await realtime.sync.merge('session', 'doc_a1b2c3d4', {
notes: 'Session completed successfully',
});
// Only 'notes' field is updated; other fields remain unchangedDelete
Soft-delete a document (sets deleted: true):
await realtime.sync.delete('session', 'doc_a1b2c3d4');Optimistic Concurrency
All write operations support an optional expectedRevision parameter for optimistic concurrency control:
// Only succeeds if the document is still at revision 3
await realtime.sync.replace('session', docId, newData, {
expectedRevision: 3,
});If the current revision doesn't match, the operation returns a conflict error (HTTP 409).
Mutation Flow
Every document write follows this internal flow:
Fetch
Read the current document from RedisJSON
Clone
Deep clone the document to avoid mutation side effects
Apply
Apply the mutation (replace, merge, or custom mutate)
Diff
Compute the diff between old and new states
Persist
Write the updated document back to RedisJSON with incremented revision
Broadcast
Publish a RealtimeEvent with source sync to the document's topic channel
Real-Time Subscriptions
Clients receive document change events automatically when subscribed to the appropriate topic:
realtime.subscribe({ topic: 'sync.session.updated' });
realtime.on('event:sync.session.updated', (event) => {
console.log('Document changed:', event.payload);
// { service, documentId, revision, data }
});Event Payload
Every sync document change event includes these payload fields:
| Field | Type | Description |
|---|---|---|
service | string | Service namespace (e.g. "session") |
documentId | string | Document ID |
revision | number | New revision number after the change |
data | object | Full document data (included unless returnMode is 'diff') |
diff | JsonDiff | What changed (included when returnMode is 'diff' or 'both') |
Return Mode
Subscribers can control how much data each event carries using the returnMode option at subscribe time:
| Mode | data | diff | Use Case |
|---|---|---|---|
'full' (default) | ✅ | ❌ | Full document state — backward-compatible |
'diff' | ❌ | ✅ | Minimal bandwidth — only what changed |
'both' | ✅ | ✅ | Full state + change context |
// Diff-only subscription — ideal for large documents
realtime.subscribe({
topic: 'sync.session.updated',
returnMode: 'diff',
});The diff is computed server-side by comparing the previous and new document data. Its structure:
interface JsonDiff {
added: Record<string, unknown>; // New keys
removed: string[]; // Deleted keys
changed: Record<string, { from; to }>; // Modified keys with before/after values
}For plain WebSocket clients, include returnMode in the subscribe message:
{ "type": "subscribe", "topic": "sync.session.updated", "returnMode": "diff" }Presence
The Sync Service includes built-in presence tracking per document:
- Track which users are currently viewing or editing a document
- Presence data is stored in Redis and automatically cleaned up on disconnect
- Presence updates are broadcast to all subscribers
Admin UI
Use the Document Browser (/documents) and Document Inspector (/documents/inspect) pages in the Admin UI to:
- Browse documents by service namespace
- View document data and revision history
- Merge updates to documents
- Delete documents