canvas-store-server
Manages a single collaborative document session with real-time synchronization.
new Room(options?)
Section titled “new Room(options?)”Creates a room instance.
const room = new Room({ storage: new FileStorage({ dir: './data', roomId: 'my-room' }), onDataChange: (room) => console.log('Data changed'), onSessionRemoved: (room, { sessionId, remaining }) => { if (remaining === 0) console.log('Room empty'); }, saveThrottleMs: 10000,});RoomOptions
Section titled “RoomOptions”| Option | Type | Default | Description |
|---|---|---|---|
initialSnapshot | RoomSnapshot | - | Restore from a previous snapshot |
storage | Storage | - | Pluggable persistence backend |
onDataChange | function | - | Called when document state changes |
onSessionRemoved | function | - | Called when a session disconnects |
saveThrottleMs | number | 10000 | Minimum ms between persistence saves |
Room Methods
Section titled “Room Methods”| Method | Returns | Description |
|---|---|---|
load() | Promise<void> | Load state from storage |
handleSocketConnect(options) | string | Register a new client connection, returns sessionId |
handleSocketMessage(sessionId, data) | void | Process incoming WebSocket message |
handleSocketClose(sessionId) | void | Handle client disconnection |
handleSocketError(sessionId) | void | Handle client error |
getSnapshot() | RoomSnapshot | Get current document state |
getSessionCount() | number | Get number of connected sessions |
getSessions() | SessionInfo[] | Get all session info |
getSessionPermissions(sessionId) | SessionPermission | Get session permissions |
setSessionPermissions(sessionId, permissions) | void | Update session permissions |
close() | void | Close room and all connections |
handleSocketConnect Options
Section titled “handleSocketConnect Options”room.handleSocketConnect({ socket: websocket, clientId: 'client-uuid', permissions: 'readwrite', // or 'readonly'});RoomManager
Section titled “RoomManager”Manages multiple rooms with automatic lifecycle management.
new RoomManager(options)
Section titled “new RoomManager(options)”Creates a room manager instance.
const manager = new RoomManager({ createStorage: (roomId) => new FileStorage({ dir: './data', roomId }), idleTimeout: 30000,});RoomManagerOptions
Section titled “RoomManagerOptions”| Option | Type | Default | Description |
|---|---|---|---|
createStorage | (roomId: string) => Storage | - | Factory for creating storage per room |
idleTimeout | number | 30000 | Auto-close empty rooms after this many ms |
RoomManager Methods
Section titled “RoomManager Methods”| Method | Returns | Description |
|---|---|---|
getRoom(roomId) | Promise<Room> | Get or create a room, loading from storage |
getExistingRoom(roomId) | Room | undefined | Get room only if it exists |
getRoomIds() | string[] | List all active room IDs |
closeRoom(roomId) | void | Close and remove a specific room |
closeAll() | void | Shut down all rooms |
Storage
Section titled “Storage”Storage Interface
Section titled “Storage Interface”interface Storage { load(): Promise<RoomSnapshot | null>; save(snapshot: RoomSnapshot): Promise<void>;}FileStorage
Section titled “FileStorage”File-based persistence using JSON files.
import { FileStorage } from '@woven-ecs/canvas-store-server';
const storage = new FileStorage({ dir: './data', roomId: 'my-room',});FileStorageOptions
Section titled “FileStorageOptions”| Option | Type | Description |
|---|---|---|
dir | string | Directory for storing snapshots |
roomId | string | Room identifier (becomes filename) |
MemoryStorage
Section titled “MemoryStorage”In-memory storage for testing.
import { MemoryStorage } from '@woven-ecs/canvas-store-server';
const storage = new MemoryStorage();Protocol Types
Section titled “Protocol Types”PROTOCOL_VERSION
Section titled “PROTOCOL_VERSION”Current protocol version constant for client-server compatibility.
import { PROTOCOL_VERSION } from '@woven-ecs/canvas-store-server';ClientMessage
Section titled “ClientMessage”Messages sent from client to server.
type ClientMessage = PatchRequest | ReconnectRequest;PatchRequest
Section titled “PatchRequest”interface PatchRequest { type: 'patch'; messageId: string; documentPatches?: Patch[]; ephemeralPatches?: Patch[];}ReconnectRequest
Section titled “ReconnectRequest”interface ReconnectRequest { type: 'reconnect'; lastTimestamp: number; protocolVersion: number; documentPatches?: Patch[]; ephemeralPatches?: Patch[];}ServerMessage
Section titled “ServerMessage”Messages sent from server to client.
type ServerMessage = | AckResponse | PatchBroadcast | ClientCountBroadcast | VersionMismatchResponse;AckResponse
Section titled “AckResponse”interface AckResponse { type: 'ack'; messageId: string; timestamp: number;}PatchBroadcast
Section titled “PatchBroadcast”interface PatchBroadcast { type: 'patch'; documentPatches?: Patch[]; ephemeralPatches?: Patch[]; clientId: string; timestamp: number;}ClientCountBroadcast
Section titled “ClientCountBroadcast”interface ClientCountBroadcast { type: 'clientCount'; count: number;}VersionMismatchResponse
Section titled “VersionMismatchResponse”interface VersionMismatchResponse { type: 'version-mismatch'; serverProtocolVersion: number;}Data Types
Section titled “Data Types”RoomSnapshot
Section titled “RoomSnapshot”Complete room state for persistence/restore.
interface RoomSnapshot { timestamp: number; state: Record<string, ComponentData>; timestamps: Record<string, FieldTimestamps>;}SessionInfo
Section titled “SessionInfo”interface SessionInfo { sessionId: string; clientId: string; permissions: SessionPermission;}SessionPermission
Section titled “SessionPermission”type SessionPermission = 'readonly' | 'readwrite';ComponentData
Section titled “ComponentData”type ComponentData = Record<string, unknown> & { _exists?: boolean; _version?: string;};FieldTimestamps
Section titled “FieldTimestamps”Per-field timestamps for conflict resolution.
type FieldTimestamps = Record<string, number>;type Patch = Record<string, ComponentData>;WebSocketLike
Section titled “WebSocketLike”Interface for WebSocket compatibility (works with any WebSocket implementation).
interface WebSocketLike { send(data: string): void; close(): void;}