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 PatternPurpose
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 → 1

Read

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);       // 1

Replace

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 unchanged

Delete

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:

FieldTypeDescription
servicestringService namespace (e.g. "session")
documentIdstringDocument ID
revisionnumberNew revision number after the change
dataobjectFull document data (included unless returnMode is 'diff')
diffJsonDiffWhat 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:

ModedatadiffUse 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