Skip to Content
Server SDKRecords

Server: Records

Records are versioned JSON values stored in Redis. Clients can subscribe to them and receive updates in real time - either full values or patches.

Expose records

Clients can only subscribe to records you explicitly expose:

// Specific ID .("user:123"); // Pattern match .(/^product:\d+$/); // With guard .(/^private:.+$/, async (, ) => { const = await ..(); return !!?.userId; });

Writable records

To allow clients to modify a record, use exposeWritableRecord(...):

// Anyone can write to cursors .(/^cursor:user:\d+$/); // Only allow writing to own profile .(/^profile:user:\d+$/, async (, ) => { const = await ..(); return ?.userId === id.split(":").pop(); });
💡

Writable records are automatically readable. If you want to apply different access rules for reading vs writing, you can expose the same pattern using both exposeRecord(...) and exposeWritableRecord(...).

Only the matching guard for the operation type (read or write) will be used. This lets you allow read access to some clients while restricting who can publish updates.

Update records

Use writeRecord(...) to update a record’s value and notify subscribers:

await .("user:123", { : "Alice", : "active", });

This:

  1. Stores the new value in Redis
  2. Increments the version
  3. Computes a patch
  4. Broadcasts to all subscribers
💡

If the new value is identical to the old value (whether it’s a patchable JSON object or a primitive value like a string, number, or boolean) the record version is not incremented and no update is sent to subscribed clients.

To delete a record:

await .("user:123");

Get current value

const { , } = await .("user:123");

Versioning & patching

Every update increments a version. Clients in patch mode expect sequential versions. If a version is missed, Mesh auto-resyncs the client with a full update.

Clients in:

  • Full mode get the entire value
  • Patch mode get a JSON Patch to apply locally

Use cases

  • User profiles: synced, editable fields
  • Collaborative docs: live document state
  • Game state: board state, player status
  • Dashboards: metrics and layout configs

Tips

  • Use structured IDs: "user:123", "doc:456", "game:abc"
  • Use guards to control read/write access
  • Keep records small and focused
  • Use patch mode for large or frequently updated records

Persistent record storage

For long-term record storage beyond Redis’s in-memory storage, you can enable persistence for records:

// Enable persistence for user profiles .({ : /^profile:user:.+$/, // Matches records for runtime access control : "profile:user:%" // SQL LIKE pattern for database restoration }); // Enable persistence with custom options .({ : "game:state:global", : "game:state:global" }, { : 50, : 1000, // 1 second });

Write vs Restore Patterns

The enableRecordPersistence() method requires both patterns to be explicit:

  • writePattern: RegExp or string used at runtime to check if a record should be persisted when it’s updated
  • restorePattern: String used as a raw SQL LIKE pattern to query the database for records to restore on server startup

Common Pattern Examples

// Persist all user data .({ : /^user:.+$/, : "user:%" }); // Persist specific document types with complex matching .({ : /^doc:(article|post):[A-Za-z0-9_-]+$/, : "doc:%" // Restore all docs, regardless of type }); // Persist exact match .({ : "app:config:main", : "app:config:main" });

Persistence can be configured with these options:

  • flushInterval: How often to flush buffered records to storage in ms (default: 500)
  • maxBufferSize: Maximum records to buffer before forcing a flush (default: 100)
  • adapter: Custom persistence adapter (default: uses server’s configured adapter)

When persistence is enabled, records matching the specified patterns are automatically stored and can be retrieved later, even after server restarts.

This lets you selectively persist data for records where state preservation matters - like user profiles, game states, or document content - without storing transient or high-frequency records such as cursor positions or ephemeral status indicators.

💡

By default, the persistence layer uses SQLite with an in-memory database (":memory:"), which means data is lost when the server restarts.

Configuring persistence storage

To ensure your records truly persist across server restarts, configure a database in your server options. Mesh supports both SQLite and PostgreSQL adapters:

SQLite (default)

import { } from "@mesh-kit/server"; const = new ({ : 3000, : { /* your Redis options */ }, : { : "./data/mesh.db", }, }); .({ : /^profile:user:.+$/, : "profile:user:%" });

PostgreSQL

import { } from "@mesh-kit/server"; const = new ({ : 3000, : { /* your Redis options */ }, : "postgres", : { : "localhost", : 5432, : "mesh_db", : "mesh_user", : "mesh_password", // Optional: use connection string instead // connectionString: "postgresql://mesh_user:mesh_password@localhost:5432/mesh_db" }, }); .({ : /^profile:user:.+$/, : "profile:user:%" });

Relationship with Redis storage

Records are always stored in Redis for immediate access, while the persistence layer provides long-term storage that survives server restarts:

  1. When a record is updated, it’s stored in Redis
  2. If persistence is enabled for that record, it’s also queued for storage in the persistence database
  3. On server restart, persisted records are automatically restored to Redis

This approach provides both fast access to current record values (Redis) and longer-term storage that survives restarts (persistence layer).

Last updated on
© 2025