Custom transports
state-sync is transport-agnostic. You provide two interfaces:
| Part | Signature | Role |
|---|---|---|
| Subscriber | subscribe(handler: (e: InvalidationEvent) => void): Promise<Unsubscribe> | Delivers invalidation events |
| Provider | getSnapshot(): Promise<SnapshotEnvelope<T>> | Returns the latest snapshot |
WebSocket
typescript
import { createRevisionSync } from '@statesync/core';
const subscriber = {
async subscribe(handler: (event: { topic: string; revision: string }) => void) {
const ws = new WebSocket('wss://api.example.com/sync');
ws.onmessage = (e) => {
const event = JSON.parse(e.data);
handler(event); // { topic: 'settings', revision: '42' }
};
// Return cleanup function
return () => ws.close();
},
};
const provider = {
async getSnapshot() {
const res = await fetch('https://api.example.com/settings');
return res.json(); // { revision: '42', data: { theme: 'dark' } }
},
};
const sync = createRevisionSync({
topic: 'settings',
subscriber,
provider,
applier: myApplier,
});Server-Sent Events (SSE)
typescript
const subscriber = {
async subscribe(handler) {
const source = new EventSource('/api/events');
source.addEventListener('invalidation', (e) => {
handler(JSON.parse(e.data));
});
return () => source.close();
},
};Electron IPC
typescript
// In renderer process
import { ipcRenderer } from 'electron';
const subscriber = {
async subscribe(handler) {
const listener = (_event, data) => handler(data);
ipcRenderer.on('state:invalidated', listener);
return () => ipcRenderer.removeListener('state:invalidated', listener);
},
};
const provider = {
async getSnapshot() {
return ipcRenderer.invoke('get-state');
},
};BroadcastChannel (browser tabs)
typescript
const channel = new BroadcastChannel('my-sync');
const subscriber = {
async subscribe(handler) {
const listener = (e: MessageEvent) => handler(e.data);
channel.addEventListener('message', listener);
return () => channel.removeEventListener('message', listener);
},
};Handling reconnection
state-sync does not manage transport connections. After a reconnect, call sync.refresh() to re-fetch the latest snapshot:
typescript
ws.onopen = () => {
sync.refresh();
};Filtering events with shouldRefresh
Skip unnecessary refreshes via the shouldRefresh callback:
typescript
const sync = createRevisionSync({
topic: 'settings',
subscriber,
provider,
applier,
shouldRefresh(event) {
return event.sourceId !== myWindowId;
},
});The callback receives a validated InvalidationEvent with topic, revision, and optional sourceId / timestampMs.
Retrying failed snapshots
Wrap the provider with withRetry for automatic retries with exponential backoff:
typescript
import { createRevisionSync, withRetry } from '@statesync/core';
const sync = createRevisionSync({
topic: 'settings',
subscriber,
provider: withRetry(provider, {
maxAttempts: 3,
initialDelayMs: 500,
backoffMultiplier: 2,
maxDelayMs: 10_000,
}),
applier,
});See also
- How state-sync works — the invalidation-pull protocol
- Source of truth example — in-memory transport demo
- Multi-window patterns — BroadcastChannel + Tauri examples
