appendChange appends one local change. This is the hot path — frequent
and small — so an implementation must not rewrite the whole entry to
satisfy it.
It is an upsert keyed by clientSeq: re-appending a change already
stored replaces it rather than duplicating it, so a retried write is safe.
load returns the persisted state for the given document key, or
undefined when nothing has been stored for it. changes must be ordered
by ascending clientSeq.
remove deletes everything persisted for the document key. It is a no-op
when nothing is stored.
saveMeta records the post-sync header. It leaves the snapshot alone —
an online client syncs constantly, and re-snapshotting per sync would
reintroduce the cost this interface exists to avoid — and it leaves the
log alone too.
That second part is load-bearing. The log does two jobs: it holds
un-pushed changes so they survive a reload, and it is the delta between
the snapshot and the document's current content. Deleting acknowledged
entries serves the first job and destroys the second, because nothing
brings the snapshot forward on a push-ack — the content would then exist
in neither place while the persisted serverSeq claims the server has it.
Only compaction trims the log, and it does so by folding the entries into
a new snapshot first.
The header itself carries the acknowledged clientSeq, so a restore reads
it from there; the store needs no separate parameter for it.
It is a no-op when nothing is stored for the key.
saveSnapshot replaces the snapshot and atomically drops every appended
change and any stored meta. This is compaction: the new snapshot
already contains those changes, so keeping them would replay them twice,
and it embeds a newer header than meta holds, so keeping that would
regress the client's clocks.
DocStoreis a pluggable persistence backend for offline document state.It is deliberately a snapshot plus an append-only change log rather than one opaque blob. Re-serializing the whole document on every edit costs time proportional to the document — hundreds of milliseconds on a large one — and a document is at its largest while it is being edited, which is exactly when the writes happen. Appending costs the size of one change, which does not grow with the document at all.
The interface stays byte-oriented and async so a durable backend (IndexedDB, say) can implement it without the client knowing which storage it talks to, and so a backend is free to compress or encrypt what it is handed. The default
MemoryDocStorekeeps everything in a process-local map and carries no dependency.