Context
The CRDT store is append-only: every committed write is permanent history, so fine-grained writes to large values grow quadratically (a keystroke-bound update of a growing 5 KB note costs ~4 MB of history for one note; a naive notes app reaches GB in a night). The journal/effects layers are self-pruning and are not the growth vector. The point of no return is commit — so growth control lives in front of it: opt-in write coalescing that merges rapid verb calls into one committed write.
1. API — an opt-in decorator per verb
export const editNote = s.coalesced(
s.mutation("editNote", app.schema.notes.update, { effects: [...] }),
{ window: "2s" },
)
- Opt-in per verb, never default. The wrapping form keeps coalescing visible at the declaration and off the core
s.mutation signature
- Author contract (the opt-in's precondition, documented and cheap-checked at runtime): coalesceable patches are absolute — static set-column-to-value objects, never read-modify-write — so replay after a crash is sound regardless of what synced in between
2. Durable intent buffer (not a source of truth)
Verb calls append to a durable staging log immediately; the coalescer merges per (row, verb) and commits one write when the window closes. Strict lifecycle: append → (replay on boot if a crash left staged intents) → commit merged → delete entries at local durability. The store remains the only source of truth; the log is an outbox buffer and is never read as app state.
3. Settlement convergence
4. Flush barriers and multi-tab
5. v2, designed but deferred: offline-length staging
Extending the window while unsynced would collapse an offline editing session into one commit at reconnect (near-SQLite growth offline). Gated on a real gap: staged values are invisible to live queries, so long windows require overlaying staged state onto query snapshots — significant machinery. v1 is short-window only; the overlay design is the entry criterion for v2.
6. Docs and diagnostics
7. Measurement spike
Out of scope
- History compaction of already-committed transactions (signed hash chain; no vendor mechanism at the pinned alpha) — recorded as an upstream wish
- Collaborative text columns (
s.text delta CRDT) — the per-datatype answer to keystroke granularity, tracked with the schema follow-ups
Context
The CRDT store is append-only: every committed write is permanent history, so fine-grained writes to large values grow quadratically (a keystroke-bound update of a growing 5 KB note costs ~4 MB of history for one note; a naive notes app reaches GB in a night). The journal/effects layers are self-pruning and are not the growth vector. The point of no return is commit — so growth control lives in front of it: opt-in write coalescing that merges rapid verb calls into one committed write.
1. API — an opt-in decorator per verb
s.mutationsignature2. Durable intent buffer (not a source of truth)
Verb calls append to a durable staging log immediately; the coalescer merges per (row, verb) and commits one write when the window closes. Strict lifecycle: append → (replay on boot if a crash left staged intents) → commit merged → delete entries at local durability. The store remains the only source of truth; the log is an outbox buffer and is never read as app state.
3. Settlement convergence
WriteHandles from coalesced calls resolve with the merged write's fate; one journal entry; effects fire exactly once, for the merged write — intermediate calls have no effect-level identity (documented contract: effects see final merged state)usePendingWritessurfaces the coalescing state honestly (N calls, one pending write)rejected, oneonRejected; the engine's own rollback covers row state. No serial re-send fallback (rejection is not caused by coalescing; un-coalescing cannot cure it and would manufacture N rejections and N× history). Known limitation, documented: a merged patch spanning columns with mixed policy outcomes fails whole4. Flush barriers and multi-tab
pagehide/visibilitychange (shrinks the crash-loss window for tab-close to near zero)5. v2, designed but deferred: offline-length staging
Extending the window while unsynced would collapse an offline editing session into one commit at reconnect (near-SQLite growth offline). Gated on a real gap: staged values are invisible to live queries, so long windows require overlaying staged state onto query snapshots — significant machinery. v1 is short-window only; the overlay design is the entry criterion for v2.
6. Docs and diagnostics
7. Measurement spike
Out of scope
s.textdelta CRDT) — the per-datatype answer to keystroke granularity, tracked with the schema follow-ups