Skip to content

canvas-store

The main synchronization hub that routes mutations between adapters (ECS, History, WebSocket, Persistence).

Creates a canvas-store instance.

const store = new CanvasStore({
persistence: {
documentId: 'my-document',
},
history: {
commitCheckpointAfterFrames: 60,
maxHistoryStackSize: 100,
},
websocket: {
documentId: 'my-document',
url: 'wss://api.example.com',
clientId: crypto.randomUUID(),
},
});
OptionTypeDescription
persistencePersistenceOptionsPersistence adapter options
historyHistoryOptions | trueHistory/undo-redo adapter options. Pass true for defaults.
websocketWebsocketOptionsWebSocket adapter options
OptionTypeDescription
documentIdstringUnique identifier for the document

Pass true to enable with defaults, or an object to customize:

OptionTypeDescription
commitCheckpointAfterFramesnumberFrames of inactivity before committing (default: 60)
maxHistoryStackSizenumberMaximum undo steps to keep (default: 100)
OptionTypeDescription
documentIdstringUnique identifier for the document
urlstringWebSocket server URL
clientIdstringUnique client identifier
startOfflinebooleanStart disconnected
tokenstringAuthentication token
onVersionMismatchfunctionProtocol version mismatch handler
onConnectivityChangefunctionConnection status change handler
MethodReturnsDescription
initialize(options)Promise<void>Initialize all adapters
sync(ctx)voidSynchronize mutations (call every frame)
undo()booleanUndo last change
redo()booleanRedo last undone change
canUndo()booleanCheck if undo is available
canRedo()booleanCheck if redo is available
createCheckpoint()string | nullCreate a history checkpoint
revertToCheckpoint(id)booleanRevert all changes since checkpoint
squashToCheckpoint(id)booleanCombine changes since checkpoint into one undo step
onSettled(callback, options)voidCalled after N frames with no mutations
connect()Promise<void>Connect/reconnect WebSocket
disconnect()voidDisconnect WebSocket
close()voidClose all adapters

Creates a component with sync behavior and stable naming for persistence.

const Shape = defineCanvasComponent(
{
name: 'shapes',
sync: 'document',
migrations: [
{ name: 'v1', upgrade: (data) => ({ ...data, color: data.color ?? '#000' }) }
],
excludeFromHistory: ['lastSelected'],
},
{
x: field.float64(),
y: field.float64(),
color: field.string().max(16).default('#0f3460'),
},
);
OptionTypeDescription
namestringStable identifier for storage/sync
syncSyncBehaviorHow changes propagate
migrationsComponentMigration[]Data migration chain
excludeFromHistorystring[]Fields to exclude from undo/redo
PropertyTypeDescription
namestringStable storage identifier
syncSyncBehaviorSync behavior setting
migrationsComponentMigration[]Migration chain
excludeFromHistorystring[]Fields excluded from history
currentVersionstring | nullLatest migration version

Creates a singleton with sync behavior and stable naming.

const Camera = defineCanvasSingleton(
{ name: 'camera', sync: 'local' },
{
x: field.float64().default(0),
y: field.float64().default(0),
zoom: field.float64().default(1),
},
);
OptionTypeDescription
namestringStable identifier for storage/sync
syncSingletonSyncBehaviorHow changes propagate (excludes 'ephemeral')
migrationsComponentMigration[]Data migration chain
excludeFromHistorystring[]Fields to exclude from undo/redo

type SyncBehavior =
| 'document' // Persisted to database, synced to all clients
| 'ephemeral' // Synced via WebSocket only (cursors, selections)
| 'local' // Persisted locally only (preferences, camera)
| 'none'; // Not synced or stored
type SingletonSyncBehavior = Exclude<SyncBehavior, 'ephemeral'>;

Built-in component that marks entities for persistence/sync.

import { Synced } from '@woven-ecs/canvas-store';
// Add to entities you want persisted
addComponent(ctx, entity, Synced, { id: crypto.randomUUID() });
FieldTypeDescription
idstringStable UUID for persistence

Real-time multiplayer synchronization adapter. When using CanvasStore, configure via the websocket option. The adapter can also be used directly:

OptionTypeDescription
urlstringWebSocket server URL
clientIdstringUnique client identifier
documentIdstringDocument identifier
usePersistencebooleanEnable offline buffer persistence
startOfflinebooleanStart disconnected
tokenstringAuthentication token
onVersionMismatchfunctionProtocol version mismatch handler
onConnectivityChangefunctionConnection status change handler
componentsAnyCanvasComponentDef[]Component definitions for migrations
singletonsAnyCanvasSingletonDef[]Singleton definitions for migrations

interface ComponentMigration {
name: string;
supersedes?: string;
upgrade: (data: Record<string, unknown>, from: string | null) => Record<string, unknown>;
}

migrateComponentData(data, currentVersion, migrations)

Section titled “migrateComponentData(data, currentVersion, migrations)”

Migrate component data through applicable migrations.

const result = migrateComponentData(
{ x: 0, y: 0 },
null,
[{ name: 'v1', upgrade: (data) => ({ ...data, z: 0 }) }]
);
// result: { data: { x: 0, y: 0, z: 0 }, version: 'v1', changed: true }

Returns: MigrationResult

interface MigrationResult {
data: Record<string, unknown>;
version: string | null;
changed: boolean;
}

Validate migrations array for correctness. Throws on duplicate names, missing supersede targets, or conflicts.

validateMigrations([
{ name: 'v1', upgrade: (data) => data },
{ name: 'v2', upgrade: (data) => data },
]);

A map of keys to values representing component changes.

type Patch = Record<string, ComponentData>;
// Key format:
// Components: "<entityId>/<componentName>"
// Singletons: "SINGLETON/<singletonName>"
// Examples:
{ "uuid-123/Position": { _exists: true, x: 0, y: 0 } } // Add
{ "uuid-123/Position": { x: 10 } } // Update
{ "uuid-123/Position": { _exists: false } } // Delete
type ComponentData = Record<string, unknown> & {
_exists?: boolean;
_version?: string;
};
interface VersionMismatchResponse {
type: 'version-mismatch';
serverProtocolVersion: number;
}