Builtin Library
README.md
world_sync
Grafts sync-durability status and per-item conflict resolution onto the
world namespace. In a live world every /source write is synced to
the backend; a write that has not yet reached the backend is a pending
write, and a write that collides with a change another collaborator made
since you last saw the path is a conflict. This module surfaces both
and lets you resolve conflicts one path at a time.
world.syncStatus()
Returns a snapshot of the world's sync state:
local s = world.syncStatus()
-- s.subscribed : bool: attached to a live world
-- s.content_synced : bool: incoming: the world's content, and every change to it the stream delivered, is applied to this client
-- s.outgoing_synced : bool: outgoing: every /source change this session made reached the world
-- s.uploads_outstanding : number: upload jobs queued, in flight, or held for another attempt
-- s.uploads_in_flight : number: the outstanding upload jobs a flush has taken and is carrying to the world
-- s.progress : { applied: number, expected: number?, unapplied: number }
-- s.pending_writes : number: local /source writes and removals not yet round-tripped whose durable record stored
-- s.unrecorded_writes : { string }: owed /source paths whose durable record did not store, which a restart loses
-- s.owed_paths : { string }: /source paths written or removed that the manifest does not reflect yet (the list vfs.pendingWrites() returns)
-- s.unsaved_writes : { string }: /source paths whose write or removal the server never took
-- s.held_uploads : { { path, reason } }: owed changes whose upload failed and is being tried again
-- s.uploads_abandoned : number — uploads the queue stopped carrying
-- s.conflicts : { { path, base_sha, local_sha, backend_sha, isBinary } }
Content sync runs in two directions, and the status answers each apart.
content_synced is the incoming direction: the world's content has been
applied to this client, and so has every change to it that the world's
stream has delivered since. A survey uses it to tell "still syncing" from
"empty world": a freshly attached session reads content_synced = false
until the backend content has landed. Later, while changes other sessions
made are still being applied here, it reads false again, and
progress.unapplied counts the delivered changes still waiting; once it
reads true with outgoing_synced true as well, /source here lists what
the world holds. It reads nothing about this session's own changes
reaching the world.
outgoing_synced is the outgoing direction: every /source change this
session made has reached the world. It reads false while any write or
removal is queued, uploading, held for another attempt, not yet reflected
by the manifest, kept in a durable record (pending_writes,
unrecorded_writes, a conflict among them), or refused by the server. It
is the field that answers "is my work saved?". Both booleans read false
while no world is subscribed.
uploads_outstanding counts the upload jobs outgoing_synced is still
waiting on, each once, and uploads_in_flight the ones among them a flush
has taken and is carrying to the world: a flush runs apart from the frame
loop, so those are sent without waiting for a frame, while the rest wait
for a frame, a blob transfer or another attempt.
pending_writes moves as the queue drains, so it reads the same for a
queue working through a backlog and for one that has given content up.
unsaved_writes and uploads_abandoned separate them: the first names
the /source paths whose bytes are on this client only, the second counts
the uploads the queue stopped carrying after they failed on it repeatedly
while the connection reported itself live. A path listed there stays on
this client until another session's reconcile carries its durable record.
held_uploads names the owed changes whose last upload attempt failed and
went back to the queue, each with the reason that attempt gave: a blob
transfer that ran past its deadline, a refused connection. The queue tries
each again on its own schedule until it lands or is given up on. A held
upload holds nothing else up: a stage or commit of other paths goes ahead,
and a bulk stage passes over it. A call that names the held path waits on
it and, when it does not land, reports the path and the reason. Removing
the path ends the hold, since the removal is what the path owes from then
on.
unrecorded_writes names the owed /source paths whose durable record
this machine could not store, because the pending-write store or the local
blob store could not be written. This session still carries them to the
world; a restart before they arrive loses them. pending_writes counts the
owed records that did store, so it leaves these out.
world.resolveConflict(path, mode, content?)
Resolves one path this session owes the world: a conflicted one, or one
whose write or removal the server refused or the queue gave up on (listed
in unsaved_writes). Waiting never carries such a change, and while one
is held a world.swap or world.checkout away from the world refuses,
naming it; "discard" drops it, "take-local" makes it again. mode is
one of:
"merge"— for text, runs a 3-way merge of the common ancestor, your bytes, and the current backend bytes, and returns{ merged, clean }. A clean merge produces ready-to-apply text; a conflicted merge returns the text with<<<<<<</=======/>>>>>>>markers to resolve. Merge is unavailable for binary content or a path with no common ancestor."apply"— writescontent(the finalized bytes you pass) to the path and clears the conflict."take-local"— keeps your bytes."take-backend"— keeps the backend's bytes."discard"— drops your pending bytes, keeping the backend's.
Retained conflict bytes are also readable under
/zero/runtime/sync/conflicts/, so you can inspect a conflict before
choosing how to resolve it.