Sync / foldkit-sync
What to show the user
The three milestones each have a reader.
Visible is the Model. Nothing to do.
Saved is the persist. Between the edit showing and the persist finishing,
a reload would lose what the user saw, so the browser asks before the page
unloads (beforeunload) until every shown edit is stored; it usually takes
milliseconds. onPersistenceFailure runs when it fails, after the
edit has been reverted; the Model it returns is what the user sees next. The
error names why: ReplayError for a Message that cannot be durable,
StorageError for IndexedDB.
Committed is the replica's status:
const status = yield* replica.status
// { pending: number, cursor: Sequence, lastError: string | undefined, rejected: ReadonlyArray<Rejection> }pending above zero means edits the server has not confirmed; lastError is
the last failed exchange, cleared by the next success; rejected lists recent
edits the server refused, most recent first, each { opId, reason, operation },
where reason is an Option of the server's words for the person and
operation is the refused operation itself, which the replica has dropped
from the outbox, so a page reads what it changed from here. replica.statusChanges is a Stream of the same,
emitted now and after every submit and exchange (subscribed before its first
read, so a subscriber slow to take one misses no change); replica.changes
pairs it with the optimistic shared value so one subscription sees both.
A refused edit arrives as a rejection on the next exchange, not as an
error. The replica drops it and replays the rest; the mount re-installs the
shared slice, and the field the user edited reverts. onPersistenceFailure
does not run for this, since the local save succeeded. Watch rejected to
explain the revert: journalExchange gives each rejection a reason, the
authorize rule's own when it gave one, and otherwise a fixed sentence
("Not a valid operation"). It never sends an error's message, which may carry
internals, so a rule a person should read about belongs in authorize with a
reason, not in validate.
Waiting for the server. The Model shows an edit before the server has it.
Something that must not report success until the edit is committed, an agent
tool for instance, waits on mounted.committed: the shared slice as the server
confirmed it, with no pending edit applied. It is a source, { get, subscribe },
told after every exchange:
completion: Agent.when({
source: mounted.committed,
predicate: (shared, request) => shared.todos.some(todo => todo.id === request.id),
})That completes after the exchange that commits the todo and never for one the server rejects.