Sync / Replicated state
2. The client
Open storage and a replica, mount the application over it, start the loop:
ts
import { Effect, Scope } from 'effect'
import { ReplicaId, Sync } from 'foldkit-sync'
const container = document.getElementById('app')!
const scope = Effect.runSync(Scope.make())
const storage = Effect.runSync(
Effect.provideService(Sync.indexedDb('todos/tab-1'), Scope.Scope, scope),
)
const replica = Effect.runSync(TodoSync.openReplica(ReplicaId.make('tab-1'), storage))
const mounted = Sync.mount(App, TodoSync, {
replica,
container,
view: (model, h) => ({
title: 'Todos',
body: h.ul([], model.todos.map(todo => h.li([], [todo.title]))),
}),
onPersistenceFailure: (model, error) =>
modifyFields(model, { lastError: () => Option.some(error.message) }),
})
Effect.runFork(
Effect.provide(replica.start, Sync.transport.socket({ url: 'wss://example.com/sync' })),
)mount runs the ordinary Foldkit runtime. A durable Message applies through
update at once and a Command then saves it to the outbox; a local Message is
untouched. replica.start exchanges with the server now, after every edit, and
whenever the server says something changed, retrying failures on a backoff.
When an exchange moves the replica (a commit, a rejection, a checkpoint), the
mount installs the reconciled slice back into the Model, outside update.
The Sync README walks each call; runtime binding explains the mount's internals.