Changes as JSON Patch
Structured changes to a document’s JSON data travel as JSON Patch
(RFC 6902) — ordered lists of
add, remove, replace, move, copy, and test operations. Patches are
compact, human- and agent-readable, and double as the document’s audit trail
in the event log.
Guards
Section titled “Guards”A patch that was computed against one version of a document may not be safe to apply to a newer version. Writers choose how strict to be, per update:
- Sequence guard — “apply only if the document is still at sequence N.” The strictest mode: any concurrent change rejects the write.
- Patch guard — the patch carries RFC 6902
testoperations asserting the values it’s about to change. Concurrent changes to other parts of the document are tolerated; a failedtestrejects the write.
When generating a patch by diffing, test operations are emitted
automatically for replace and remove operations, so the patch defends
itself by construction. Additions are the gap — an add has no prior value to
assert, so guarded writes catch same-path changes and deletions but not two
writers creating the same new key (the
add precondition issue).
A rejected guard is expected, not an error — nothing to log and page on — but the write itself is genuinely dropped, not applied. The optimistic entry is rolled back, the client snaps to the winning state, and it’s on the caller to re-derive against that state and retry; nothing replays the lost write automatically. The staged session builds its conflict detection on the same primitive, turning that drop into a reviewable conflict instead.
Ordered collections without arrays
Section titled “Ordered collections without arrays”The array-addressing problem shapes how you model collections. A collection inside a document is stored as records keyed by id, each carrying a fractional index for ordering, rather than as a JSON array. This is deliberate: JSON Patch addresses array elements by position, which is unstable when several writers insert and remove concurrently. Fractional indices make “insert between A and B” a single-key write that doesn’t disturb neighbors. (This is one of the workarounds discussed in JSON Patch RFC issues.)
Why patches — not CRDT operations, not domain events?
Section titled “Why patches — not CRDT operations, not domain events?”For structured data, datadata deliberately uses server-ordered patches with
guards rather than CRDTs — and rather than an application-defined event
vocabulary with reducers: a patch is self-applying data, so history can be
read and replayed in any context without the application’s code. The
trade-offs of both choices are discussed in
Design decisions. For collaborative text,
where convergence-per-keystroke matters, it uses
Yjs instead. Patches carry a real cost here
worth naming: a diffed edit to a text field is a whole-value replace, so each
change ships and stores the entire string — the
event log and the wire bloat fast for
large, frequently edited prose. Yjs sends compact per-edit deltas instead, so
for that shape of data it’s the better field type. The two travel together in
the same update.