@sqlrooms/crdt
v0.27.0-rc.0
Published
CRDT utilities for SQLRooms built on top of Loro Mirror. The package exposes `createCrdtSlice`, a Zustand slice helper that mirrors selected parts of your store into a Loro CRDT document, plus a small set of persistence and sync helpers.
Readme
@sqlrooms/crdt
CRDT utilities for SQLRooms built on top of Loro Mirror. The package exposes createCrdtSlice, a Zustand slice helper that mirrors selected parts of your store into a Loro CRDT document, plus a small set of persistence and sync helpers.
Installation
pnpm add @sqlrooms/crdt loro-crdt loro-mirrorQuick start
import {schema} from 'loro-mirror';
import {createRoomStore, persistSliceConfigs} from '@sqlrooms/room-shell';
import {BaseRoomConfig, LayoutConfig} from '@sqlrooms/room-config';
import {
createCrdtSlice,
createLocalStorageDocStorage,
createWebSocketSyncConnector,
} from '@sqlrooms/crdt';
const mirrorSchema = schema({
room: schema.LoroMap({config: schema.Ignore()}), // mirror only the config portions
layout: schema.LoroMap({config: schema.Ignore()}),
});
const {roomStore, useRoomStore} = createRoomStore(
persistSliceConfigs(
{
name: 'sqlrooms-sync-demo',
sliceConfigSchemas: {room: BaseRoomConfig, layout: LayoutConfig},
},
(set, get, store) => ({
...createCrdtSlice({
schema: mirrorSchema,
bindings: [
{key: 'room', select: (s) => s.room?.config, apply: (value) => set({room: {...get().room, config: value}})},
{key: 'layout', select: (s) => s.layout?.config, apply: (value) => set({layout: {...get().layout, config: value}})},
],
storage: createLocalStorageDocStorage('sqlrooms-sync-demo'),
sync: createWebSocketSyncConnector({
url: 'ws://localhost:4800',
roomId: 'demo-room',
// If your server sends a snapshot on join (like sqlrooms-duckdb-server),
// prefer updates-only to avoid re-sending full snapshots on reconnects.
sendSnapshotOnConnect: false,
// Still seed an empty server once after join (important if you load initial
// state from local persistence without generating CRDT ops).
sendSnapshotIfServerEmpty: true,
}),
})(set, get, store),
// add your other slices here
}),
),
);
// Call roomStore.getState().crdt.initialize() after creating the store to start syncConcepts
- Bindings: map each mirrored CRDT field to a piece of Zustand state. Provide
selectto choose the outbound data and optionalapplyto merge inbound CRDT updates. Ifapplyis omitted, the binding writes to a top-level key. - Storage: implement
CrdtDocStorageto persist snapshots (localStorage helper included). - Sync: plug a
CrdtSyncConnector(websocket helper included) that forwards local updates and applies remote updates viadoc.import.
See examples/sync for an end-to-end demonstration with the Python sync server.
Testing & debugging
- Mirror emits
tagsmetadata; we tag store-origin writes asfrom-storeto avoid loops. Logmirror.subscribein your app if you need deeper inspection. - Use the
storagehook to inject an in-memory or temp store in tests; pass a fakesyncconnector that capturessubscribeLocalUpdatestraffic for assertions. - The websocket connector exposes
onStatusfor basic connection logging; attach console logs or telemetry there during development.
