Each queued write op captures a base version — the server version the device believes is current, assuming its own earlier queued ops will land first. Draining walks the queue strictly FIFO and checks, per op, server.version === op.baseVersion before applying.
for op in queue (in order):
if server.version == op.baseVersion: apply(op); server.version++
else: CONFLICT — policy decides: abort | skip | force
- Ordering, not merging — unlike a CRDT merge, this queue never combines two edits. It enforces that op #2 never applies before op #1, and rejects an op outright if the server moved under it.
- Remote write — simulates another client (or an admin console) writing straight to the server, independent of this device's queue. That is what makes a queued op's base version go stale.
- Abort & preserve — stops draining at the first conflict; the conflicting op and everything behind it in the queue stay queued, untouched, in order.
- Skip & continue — drops just the conflicting op and keeps draining. Because later ops assumed the dropped one would succeed, a single remote write can cascade into several skips.
- Force overwrite — applies the op anyway on top of whatever the server currently holds, silently discarding the remote write it collided with.
- Rebase & retry — after an abort, recomputes every remaining op's base version against the server's current version and resumes draining.
Real-world relevance: this is the pattern behind offline mutation queues (Service Worker Background Sync, React Query/Apollo offline links) that replay conditional PATCH/PUT requests with an ETag or version check per request.