SDK

Sync Client

Create, read, update, and delete real-time documents with revision tracking.

The Sync Client provides CRUD operations on real-time documents stored in RedisJSON. Every mutation automatically increments the document revision and broadcasts changes to subscribers.

Access the Sync Client via realtime.sync:

const syncClient = realtime.sync;

Methods

create(service, data, id?)

Create a new document in the given service namespace.

create(service: string, data: Record<string, unknown>, id?: string): Promise<SyncDocument>
ParameterTypeRequiredDescription
servicestringYesService namespace the document belongs to (e.g. "session", "order")
dataRecord<string, unknown>YesInitial document payload — any JSON-serializable object
idstringNoCustom document ID. If omitted, a prefixed unique ID is generated (e.g. doc_a1b2c3d4)

Returns: Promise<SyncDocument> — the created document with id, revision (1), service, and data.

// Auto-generated ID
const doc = await realtime.sync.create('session', {
  status: 'active',
  participantCount: 0,
});
console.log(doc.id);       // "doc_a1b2c3d4"
console.log(doc.revision); // 1

// Custom ID
const doc2 = await realtime.sync.create('session', {
  status: 'active',
}, 'my-custom-id');
console.log(doc2.id);      // "my-custom-id"

get(service, id)

Fetch the current state of a document.

get(service: string, id: string): Promise<SyncDocument>
ParameterTypeRequiredDescription
servicestringYesService namespace (e.g. "session")
idstringYesDocument ID to retrieve

Returns: Promise<SyncDocument> — the document with its current data, revision, and metadata.

Throws: Error if the document does not exist.

const doc = await realtime.sync.get('session', 'doc_a1b2c3d4');
console.log(doc.data.status);  // "active"
console.log(doc.revision);     // 3

replace(service, id, data, expectedRevision?)

Replace the entire document data. All existing fields are overwritten.

replace(service: string, id: string, data: Record<string, unknown>, expectedRevision?: number): Promise<SyncDocument>
ParameterTypeRequiredDescription
servicestringYesService namespace
idstringYesDocument ID to replace
dataRecord<string, unknown>YesNew document payload — completely replaces existing data
expectedRevisionnumberNoIf provided, the operation fails with a conflict error if the document's current revision doesn't match

Returns: Promise<SyncDocument> — the updated document with incremented revision.

Throws: Conflict error (409) if expectedRevision doesn't match.

const updated = await realtime.sync.replace('session', 'doc_a1b2c3d4', {
  status: 'ended',
  endedAt: Date.now(),
});
console.log(updated.revision); // 4

// With optimistic concurrency
await realtime.sync.replace('session', 'doc_a1b2c3d4', newData, 4);

merge(service, id, partial, expectedRevision?)

Deep merge partial data into the existing document. Only the specified fields are updated — all other fields remain unchanged.

merge(service: string, id: string, partial: Record<string, unknown>, expectedRevision?: number): Promise<SyncDocument>
ParameterTypeRequiredDescription
servicestringYesService namespace
idstringYesDocument ID to merge into
partialRecord<string, unknown>YesPartial data to deep merge into the document
expectedRevisionnumberNoOptimistic concurrency guard

Returns: Promise<SyncDocument> — the updated document with incremented revision.

await realtime.sync.merge('session', 'doc_a1b2c3d4', {
  notes: 'Session completed successfully',
});
// Only 'notes' is added/updated; status, participantCount, etc. remain unchanged

mutate(service, id, mutations, expectedRevision?)

Apply structured mutations to the document. Unlike merge, the mutations object is passed directly to the document service which applies them as discrete field-level operations in a single revision bump.

mutate(service: string, id: string, mutations: Record<string, unknown>, expectedRevision?: number): Promise<SyncDocument>
ParameterTypeRequiredDescription
servicestringYesService namespace
idstringYesDocument ID to mutate
mutationsRecord<string, unknown>YesMutation operations to apply atomically
expectedRevisionnumberNoOptimistic concurrency guard

Returns: Promise<SyncDocument> — the updated document with incremented revision.

await realtime.sync.mutate('session', 'doc_a1b2c3d4', {
  status: 'ended',
  participantCount: 0,
  endedAt: Date.now(),
}, 5);

delete(service, id)

Soft-delete a document (sets deleted: true on the document).

delete(service: string, id: string): Promise<void>
ParameterTypeRequiredDescription
servicestringYesService namespace
idstringYesDocument ID to delete

Returns: Promise<void>

await realtime.sync.delete('session', 'doc_a1b2c3d4');

Return Type: SyncDocument

All write operations return a SyncDocument:

interface SyncDocument {
  service: string;            // Namespace (e.g. "session")
  id: string;                 // Document ID
  revision: number;           // Auto-incrementing version number
  data: Record<string, unknown>; // Document payload
}

Optimistic Concurrency

The replace, merge, and mutate methods accept an optional expectedRevision parameter. If the document's current revision doesn't match, the operation fails with a conflict error:

try {
  await realtime.sync.replace('session', docId, newData, 3);
} catch (err) {
  if (err.message.includes('conflict')) {
    // Document was modified by another writer since revision 3
    const latest = await realtime.sync.get('session', docId);
    // Retry with the latest revision
  }
}

Listening for Document Changes

Subscribe to sync events to receive real-time updates when any client modifies a document:

realtime.subscribe({ topic: 'sync.session.updated' });

realtime.on('event:sync.session.updated', (event) => {
  console.log('Operation:', event.metadata.operation);
  console.log('Revision:', event.metadata.revision);
  console.log('Data:', event.payload.data);
});

Receiving Diffs

By default, events include the full document data. You can opt into receiving a diff of what changed by setting returnMode when subscribing:

// Only receive the diff — minimal wire traffic
realtime.subscribe({
  topic: 'sync.session.updated',
  returnMode: 'diff',
});

realtime.on('event:sync.session.updated', (event) => {
  const { diff } = event.payload;
  console.log('Added fields:', diff.added);
  console.log('Removed fields:', diff.removed);
  console.log('Changed fields:', diff.changed);
  // changed: { status: { from: 'active', to: 'ended' } }
});

Use returnMode: 'both' to receive both the full document and the diff in every event.

See Subscribing — Return Mode for the full reference.