Import it from @grafloria/engine.
Holds ops whose entity has not been added yet, and releases them the instant it is.
Sits BETWEEN the transport and the Replica. Nothing it holds has touched the log, the clock or the LWW registry — which is the whole point: an op the replica has never seen is an op that can still be applied later.
tsclass CausalBuffer
Properties
| Name | Type | Default | Description |
|---|---|---|---|
overflowed | 0 | Ops force-released because the buffer was full. Should be 0. Watch it. |
Methods
constructor( private readonly diagram: DiagramModel, options: CausalBufferOptions = {} )get pendingCount(): numbernoteLocal(op: Op): void— WE created an entity. Record it — the transport will never tell us about it.
THE FUZZ FOUND THIS ONE, AND IT IS A PERMANENT HANG, NOT A HICCUP.
known was fed from exactly two places: entities on the diagram at construction, and
add ops that arrived from PEERS. A node the local user creates during the session is
in NEITHER — so as far as the buffer was concerned, an entity this peer invented did
not exist.
Now watch a peer edit it. Bob creates node X. Alice learns about it, drags it, and
sends set(X, position). It arrives at Bob, whose buffer asks "have I seen an add
for X?", answers NO, and holds it — waiting for an add(X) that is never coming, from
anyone, ever. It cannot come: the only add(X) in existence is Bob's OWN, it is
already in Bob's log, and Alice's anti-entropy correctly concludes that Bob already
has it and never echoes it back.
So Bob's node freezes wherever it was when he made it, Alice watches it move, and the
two documents differ forever — over the single most ordinary interaction in a
collaborative editor: one person moving another person's node. On a healthy transport
it never fires (the set cannot overtake an add that was never sent), which is
exactly why it took a reordering channel to find it.
No drain is needed. A peer can only set an entity it has learned about, which means
our add had already reached it — so nothing can be waiting on an id at the moment we
create it. The registration alone closes the hole.
admit(incoming: readonly Op[]): CausalSplit— Split an arriving batch into what can be applied now and what must wait — and fold in anything that was already waiting and has just become releasable.pending(): Op[]— Everything we are still holding — for a status panel, and for the tests.
Was this page helpful?