Getting Started
Installation
pnpm add @vuer-ai/vuer-rtcQuick Start
import { createGraph } from '@vuer-ai/vuer-rtc';
// Create a graph store
const store = createGraph({
sessionId: 'my-session',
onSend: (msg) => websocket.send(msg),
});
// Edit (uncommitted, updates UI immediately)
store.edit({
otype: 'node.insert',
key: 'scene', // Parent node's key
path: 'children',
value: { key: 'cube', tag: 'Mesh', name: 'Cube' },
});
// Commit (sends to server as one message)
store.commit('Create cube');
// Receive from server
websocket.onmessage = (msg) => store.receive(msg);
// Undo/redo (synced across all clients)
store.undo();
store.redo();Core Concepts
State Model
The client maintains four key data structures:
interface ClientState {
graph: SceneGraph; // Current computed state
journal: JournalEntry[]; // Committed messages
edits: EditBuffer; // Uncommitted operations
snapshot: Snapshot; // Checkpoint for fast replay
}| Component | Purpose |
|---|---|
graph | Current state shown in UI. Derived from snapshot + journal + edits. |
journal | Committed messages. Tracks ack status and deletion (undo) state. |
edits | Uncommitted ops. Merged during gestures, committed as one message. |
snapshot | Periodic checkpoint. Bakes in acked entries for fast replay. |
Edit Buffer
Operations are accumulated in the edit buffer during gestures (like dragging), then committed as a single message:
// During drag (60fps)
store.edit({ otype: 'vector3.add', key: 'cube', path: 'position', value: [0.1, 0, 0] });
store.edit({ otype: 'vector3.add', key: 'cube', path: 'position', value: [0.1, 0, 0] });
// ... many more
// On drag end - all edits become ONE message
store.commit('Move cube');
// Sends: { ops: [{ otype: 'vector3.add', value: [5.0, 0, 0] }] }The edit buffer merges additive operations, so 60 small deltas become 1 message.
Messages & Operations
Changes are expressed as messages containing one or more operations:
interface CRDTMessage {
id: string; // Unique message ID
sessionId: string; // Who sent this
clock: VectorClock; // For causal ordering
lamportTime: number; // For LWW ordering
timestamp: number; // Wall clock
ops: Operation[]; // Batch of operations
}Undo / Redo
Undo and redo are synced messages that mark entries with deletedAt:
// Undo the last committed message from this session
const { msg } = store.undo();
// Creates: { ops: [{ otype: 'meta.undo', targetMsgId: 'msg-123' }] }
// Syncs to all clients - everyone sees the undo
// Redo the last undone message
store.redo();
// Creates: { ops: [{ otype: 'meta.redo', targetMsgId: 'msg-123' }] }All clients see the same undo state. Undo is not local-only.
Conflict Resolution
| Operation | Merge Strategy | Example |
|---|---|---|
*.set | Last-Write-Wins (higher lamport) | color.set '#ff0000' |
*.add | Sum values | vector3.add [1, 0, 0] |
meta.undo | Sets deletedAt on target | Synced undo |
meta.redo | Clears deletedAt on target | Synced redo |