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>| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Service namespace the document belongs to (e.g. "session", "order") |
data | Record<string, unknown> | Yes | Initial document payload — any JSON-serializable object |
id | string | No | Custom 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>| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Service namespace (e.g. "session") |
id | string | Yes | Document 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); // 3replace(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>| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Service namespace |
id | string | Yes | Document ID to replace |
data | Record<string, unknown> | Yes | New document payload — completely replaces existing data |
expectedRevision | number | No | If 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>| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Service namespace |
id | string | Yes | Document ID to merge into |
partial | Record<string, unknown> | Yes | Partial data to deep merge into the document |
expectedRevision | number | No | Optimistic 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 unchangedmutate(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>| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Service namespace |
id | string | Yes | Document ID to mutate |
mutations | Record<string, unknown> | Yes | Mutation operations to apply atomically |
expectedRevision | number | No | Optimistic 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>| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Service namespace |
id | string | Yes | Document 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.