import { Callout } from "nextra/components";
Records are versioned JSON values stored in Redis. Clients can subscribe to them and receive updates in real time - either full values or patches.
Clients can only subscribe to records you explicitly expose:
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
// ---cut---
// Specific ID
server.exposeRecord("user:123");
// Pattern match
server.exposeRecord(/^product:\d+$/);
// With guard
server.exposeRecord(/^private:.+$/, async (conn, recordId) => {
const meta = await server.connectionManager.getMetadata(conn);
return !!meta?.userId;
});To allow clients to modify a record, use exposeWritableRecord(...){:js}:
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
// ---cut---
// Anyone can write to cursors
server.exposeWritableRecord(/^cursor:user:\d+$/);
// Only allow writing to own profile
server.exposeWritableRecord(/^profile:user:\d+$/, async (conn, recordId) => {
const meta = await server.connectionManager.getMetadata(conn);
return meta?.userId === id.split(":").pop();
});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.
Use writeRecord(...){:js} to update a record's value and notify subscribers:
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
// ---cut---
await server.writeRecord("user:123", {
name: "Alice",
status: "active",
});This:
- Stores the new value in Redis
- Increments the version
- Computes a patch
- Broadcasts to all subscribers
To delete a record:
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
// ---cut---
await server.deleteRecord("user:123");// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
// ---cut---
const { value, version } = await server.getRecord("user:123");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
- User profiles: synced, editable fields
- Collaborative docs: live document state
- Game state: board state, player status
- Dashboards: metrics and layout configs
- Use structured IDs:
"user:123"{:js},"doc:456"{:js},"game:abc"{:js} - Use guards to control read/write access
- Keep records small and focused
- Use patch mode for large or frequently updated records
Mesh supports two persistence modes for records:
- Adapter mode: Mesh stores records as JSON blobs for restore-on-startup
- Hooks mode: You provide custom functions to persist records to your own database
Use adapter mode when you want Mesh to handle persistence internally. Records are stored as JSON blobs and restored on server restart:
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
// ---cut---
server.enableRecordPersistence({
pattern: /^profile:user:.+$/,
adapter: { restorePattern: "profile:user:%" },
});pattern: RegExp or string matched at runtime in JavaScript to decide which records to persistrestorePattern: SQL LIKE pattern used to query the database on startup (use%as wildcard)
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
// ---cut---
// With buffering options
server.enableRecordPersistence({
pattern: /^game:state:.+$/,
adapter: { restorePattern: "game:state:%" },
flushInterval: 1000,
maxBufferSize: 50,
});Use hooks mode when you want to persist records to your own database with real columns:
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({});
const db: any = {};
// ---cut---
server.enableRecordPersistence({
pattern: /^user:\d+$/,
hooks: {
persist: async (records) => {
await db.users.upsertMany(records.map(r => ({
id: r.recordId.split(":")[1],
name: r.value.name,
email: r.value.email,
version: r.version,
})));
},
restore: async () => {
const users = await db.users.findMany();
return users.map(u => ({
recordId: `user:${u.id}`,
value: { name: u.name, email: u.email },
version: u.version,
}));
},
},
});With hooks mode:
persistis called with batched records when flushing (afterflushIntervalormaxBufferSizeis reached)restoreis called on server startup to load records into Redis- You own the schema and can use any ORM or query builder
Both modes support buffering options:
flushInterval{:js}: How often to flush buffered records in ms (default: 500)maxBufferSize{:js}: Maximum records to buffer before forcing a flush (default: 100)
When using adapter mode without specifying a custom adapter, Mesh uses the server's default adapter. Configure it in server options:
// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({
port: 3000,
redisOptions: { /* ... */ },
persistenceOptions: {
filename: "./data/mesh.db",
},
});
server.enableRecordPersistence({
pattern: /^profile:user:.+$/,
adapter: { restorePattern: "profile:user:%" },
});// @noErrors
import { MeshServer } from "@mesh-kit/server";
const server = new MeshServer({
port: 3000,
redisOptions: { /* ... */ },
persistenceAdapter: "postgres",
persistenceOptions: {
host: "localhost",
port: 5432,
database: "mesh_db",
user: "mesh_user",
password: "mesh_password",
},
});
server.enableRecordPersistence({
pattern: /^profile:user:.+$/,
adapter: { restorePattern: "profile:user:%" },
});Records are always stored in Redis for immediate access. The persistence layer provides long-term storage:
- When a record is updated via
writeRecord(), it's stored in Redis - If persistence is enabled, the record is queued for storage (adapter) or your
persisthook is called - On server restart, records are restored to Redis from the adapter or your
restorehook