Remote / foldkit-remote
Live data
Data.live marks a Projection so active Data.subscriptions follow server
changes for it.
The Remote Model owns each stream's cursor. Live events carry monotonically ordered cursors:
- an old/repeated cursor is a duplicate and is ignored;
- the next cursor is applied;
- a cursor ahead of the expected value is a gap and is not applied.
Entity patches update normalized fields, deletes create tombstones, and connection insert/remove/invalidate events reconcile the same connection model used by query pages and optimistic overlays. A stream failure becomes a Remote failure Message rather than mutating anything out of band.
When a stream breaks
A stream breaks when its transport fails or when an event arrives ahead of
its cursor (a gap). Either way, what the server said meanwhile is lost: no
server replays it, since after only numbers the events that follow. So
recovery is a refetch, and the live entry does it on its own:
break -> gap marked, restarts + 1 -> backoff
-> RefreshStarted (what the stream covers) + GapCleared -> resubscribe from the cursor
\-> read entry refetches- The backoff is Sync's transport policy: 50 ms after the first break,
doubling for each break in a row, at most 5 s, with ±20% jitter.
retryBaseandmaxRetryDelayin theData.subscriptionsoptions change it. An event applied after a resubscribe starts it over. - A gap stays until the restart. An in-order event that a broken stream still delivers is applied, but does not clear it. The event that arrived ahead was dropped, and only the refetch brings back what it said.
- What a stream covers is its requirements. A list the server watches
for it (a
ConnectionInvalidateit sends) is the source's to re-send when the stream opens; the cloudflare example's poll invalidates its list on every open for this reason.
Data.liveStatus(model, active) says how what one active entry reads live is
doing, for a view that shows it:
Match.value(Data.liveStatus(model, Watched)).pipe(
Match.tagsExhaustive({
Idle: () => 'not live',
Live: () => 'live',
Reconnecting: ({ attempt }) => `reconnecting (attempt ${attempt})`,
}),
)Reconnecting lasts from the break until the stream has resubscribed, and
carries the error, which is none for a gap. RemoteModel.streams holds each
stream's restarts, its breaks in a row and its last error. forget clears it
with every other server-derived fact.