Skip to Content
Client SDKRecords

Client Records

Mesh supports subscribing to individual records stored in Redis. When a record changes, clients receive either the full value or a JSON patch describing the update—depending on the selected mode. This page explains how to subscribe to and update records on the client side.

Subscribing to Records

Use the subscribeRecord method to subscribe to a record:

let userProfile = {}; const { success, record, version } = await client.subscribeRecord( "user:123", (update) => { // update contains { recordId, full, version } userProfile = update.full; console.log( `Received full update for ${update.recordId} v${update.version}:`, update.full ); } ); if (success) { userProfile = record; console.log("Initial record:", record); console.log("Initial version:", version); }

The first parameter is the record ID, and the second parameter is a callback function that will be called whenever the record is updated.

By default, the client receives the entire updated record every time (full mode). This is simpler to use and ideal for small records or when patching isn’t needed.

Subscribing in Patch Mode

For larger records or when bandwidth is a concern, you can use patch mode:

import { applyPatch } from "@mesh-kit/core/client"; let productData = {}; const { success, record, version } = await client.subscribeRecord( "product:456", (update) => { // update contains { recordId, patch?, full?, version } if (update.patch) { // normally you'll receive `patch`, but if the client falls out of sync, // the server will send a full update instead to resynchronize. applyPatch(productData, update.patch); console.log(`Applied patch for ${update.recordId} v${update.version}`); } else { productData = update.full; console.log( `Received full (resync) for ${update.recordId} v${update.version}` ); } }, { mode: "patch" } ); if (success) { productData = record; }

In patch mode, the client receives only changes as JSON patches and must apply them locally. This is especially useful for large records that only change in small ways over time.

💡

Patch mode only works for records that are objects or arrays. Primitive values like strings, numbers, or booleans aren’t representable as JSON patches, so you should use full mode for those cases.

Unsubscribing from Records

When you no longer need to receive updates for a record, you should unsubscribe:

await client.unsubscribeRecord("user:123");

This will stop the client from receiving updates and clean up any resources associated with the subscription.

Updating Records

If a record has been exposed as writable via exposeWritableRecord on the server, clients can publish updates using the publishRecordUpdate method:

const userId = "123"; const success = await client.publishRecordUpdate(`cursor:user:${userId}`, { x: 100, y: 250, timestamp: Date.now(), }); if (success) { console.log("Cursor position updated successfully."); } else { console.error("Failed to update cursor position (maybe permission denied?)."); }

The method takes two parameters:

  1. The record ID
  2. The new value for the record

The method returns true if the server accepted the write, and false if it was rejected (e.g., due to a failed guard).

Handling Self-Updates

When a client publishes an update to a record using publishRecordUpdate, it will also receive that update through its subscription callback—just like any other client. This ensures consistency and avoids special-casing updates based on origin.

If your app logic already applies local updates optimistically, you may choose to ignore redundant self-updates in your callback:

// Track the last update we sent let lastSentUpdate = null; // Subscribe to the record await client.subscribeRecord("cursor:user:123", (update) => { // Check if this is our own update if ( lastSentUpdate && JSON.stringify(update.full) === JSON.stringify(lastSentUpdate) ) { console.log("Ignoring echo of our own update"); lastSentUpdate = null; // Reset after handling return; } // Handle the update normally console.log("Received update:", update.full); }); // When sending an update const newPosition = { x: 100, y: 250, timestamp: Date.now() }; lastSentUpdate = newPosition; await client.publishRecordUpdate("cursor:user:123", newPosition);

Versioning and Resync

Every update includes a version. Clients track the current version and, in patch mode, expect version === localVersion + 1. If a gap is detected (missed patch), the client will automatically be sent a full record update to resync.

This happens transparently to your code - the callback will receive a full update instead of a patch update, and you should update your local state accordingly.

Handling Reconnection

When a client reconnects after a disconnection, you’ll need to resubscribe to records:

// Store active record subscriptions const activeRecords = new Map(); // Subscribe to a record and track it const subscribeToRecord = async (recordId, callback, options) => { const result = await client.subscribeRecord(recordId, callback, options); if (result.success) { activeRecords.set(recordId, { callback, options }); } return result; }; // Unsubscribe from a record and stop tracking it const unsubscribeFromRecord = async (recordId) => { await client.unsubscribeRecord(recordId); activeRecords.delete(recordId); }; // Restore subscriptions after reconnection client.onReconnect(async () => { console.log("Reconnected, restoring record subscriptions..."); for (const [recordId, { callback, options }] of activeRecords.entries()) { await client.subscribeRecord(recordId, callback, options); } console.log("Record subscriptions restored"); });

Use Cases

Records are particularly useful for:

  1. User profiles

    • Store user information that needs to be synchronized across clients
    • Update profile fields in real-time
  2. Collaborative editing

    • Shared cursors and selection ranges
    • Document state synchronization
  3. Game state

    • Player positions
    • Game board state
    • Inventory and status
  4. Dashboard configuration

    • Widget layouts
    • User preferences
    • Real-time metrics

Best Practices

  1. Choose the right mode

    • Use full mode for small records
    • Use patch mode for large records or when bandwidth is a concern
  2. Handle reconnection

    • Track active subscriptions
    • Resubscribe after reconnection
  3. Consider optimistic updates

    • Apply updates locally before sending to the server
    • Handle conflicts if the server rejects the update
  4. Unsubscribe when done

    • Always unsubscribe from records when they’re no longer needed
    • This reduces unnecessary network traffic and server load

Record Persistence

The server can be configured to persist records to a durable storage backend (like SQLite), which means:

  1. Automatic restoration - When the server restarts, persisted records are automatically restored from storage
  2. Historical data preservation - Important record data survives server restarts and Redis failures
  3. Seamless client experience - Clients don’t need to handle persistence themselves; they’ll receive the restored data automatically upon subscription

This is particularly useful for:

  • User profiles that should be preserved between sessions
  • Game states that need to survive server restarts
  • Document content that requires long-term storage
  • Application settings that should persist across deployments

From a client perspective, persistence is completely transparent - you interact with records the same way whether they’re persisted or not. The server handles all the storage and restoration logic automatically.

Last updated on
© 2025