Documentation / @etherfold/core / StateMovedPublisher
Class: StateMovedPublisher ​
Defined in: packages/core/src/stateMoved.ts:278
THE PRODUCER'S HALF, as ONE assembly both containers hold.
There are two things in this system that apply blocks -- the chain-facing Indexer and the chain-free ReceivingIndexer -- and ADR-0083 says both publish THE SAME signal FROM THE SAME ASSEMBLY, because one notification model is the claim being made and two producers that drift is how that claim dies. So the subscription, the containment and the token live here, and a container supplies only what a container knows: which block, which generation, and whether the fold that applied it is the one that answers reads.
It holds NOTHING per subscriber ​
Nothing is buffered, nothing is retried, nothing is remembered about who is listening: one handler reference each, and that is the whole of it. That is what stops a SharedWorker's memory growing with the number of open tabs, and it is the property every transport downstream depends on, so it belongs here rather than in each of them. A client that missed a notification is repaired by the next one plus the token, which is the trade ADR-0083 makes explicitly.
Constructors ​
Constructor ​
new StateMovedPublisher():
StateMovedPublisher
Returns ​
StateMovedPublisher
Accessors ​
subscriberCount ​
Get Signature ​
get subscriberCount():
number
Defined in: packages/core/src/stateMoved.ts:305
How many handlers are attached. Reported so a test can see that nothing else is kept.
Returns ​
number
token ​
Get Signature ​
get token():
string
Defined in: packages/core/src/stateMoved.ts:310
The token every notification is currently carrying.
Returns ​
string
Methods ​
publish() ​
publish(
moved):void
Defined in: packages/core/src/stateMoved.ts:349
PUBLISH one applied block, stamped with the token in force and with its CASE.
Both stamps are made HERE rather than supplied by the caller, and for one reason: a producer must not be able to publish a token it has not rotated, nor an append wearing a retraction's tag. publishRetraction and rotateForPointerMove beside this are the only other ways out, and both rotate.
Parameters ​
moved ​
Omit<StateApplied, "coherence" | "kind">
Returns ​
void
publishRetraction() ​
publishRetraction(
retraction):void
Defined in: packages/core/src/stateMoved.ts:369
PUBLISH a retraction, ROTATING the token as part of publishing it.
The rotation is not the caller's to remember. "A missed notification is repaired by the next one" is true of an APPEND and false of a RETRACTION -- after a reorg the stale entities are the ABANDONED branch's, and those are generally not in the changed-set of whatever block arrives next -- so a retraction published under the OLD token would leave a reader that missed it invalidating narrowly and keeping dead-branch rows indefinitely. Putting the two in one call is what makes that unexpressible, here, in the one assembly both containers publish from (ADR-0083).
The retraction therefore carries the NEW token: a reader that RECEIVED it invalidates everything and is then holding exactly the token the appends after it carry, so it invalidates narrowly again from the next block on.
Parameters ​
retraction ​
Omit<StateRetracted, "coherence" | "kind">
Returns ​
void
rotate() ​
rotate(
reason):string
Defined in: packages/core/src/stateMoved.ts:335
ROTATE THE TOKEN: something happened that may have made ANYTHING a reader holds wrong.
ONE LINE AT THE POINT WHERE THE REASON OCCURS, which is the whole shape of it: a RETRACTION rotates it because the stale entities are the abandoned branch's and no changed-set names them (publishRetraction below does it, so that a retraction which forgot to rotate is unexpressible), and a POINTER MOVE rotates it because a different fold now answers, which from a cache's point of view is indistinguishable from "everything you hold may be wrong" (rotateForPointerMove below does it, for the same reason). One comparison and one code path in a reader rather than three.
It rotates whether or not anybody is listening, because the token is a fact about THIS PRODUCER's history and not about a delivery: a reader that attaches afterwards, or a transport that reconnects, must still be told that what it holds from before is suspect.
reason is LOGGED and never published: the token names nothing, because a reader that could read a reason out of it would be parsing it.
Parameters ​
reason ​
string
Returns ​
string
rotateForPointerMove() ​
rotateForPointerMove(
reason): (moved) =>void
Defined in: packages/core/src/stateMoved.ts:395
ROTATE for a POINTER MOVE now, and answer the call that ANNOUNCES it.
Two steps rather than one, because the two halves belong at two different points of a move, and getting either order wrong is visible to a reader:
- the ROTATION belongs FIRST, before anything tells a reader to re-read (a container's pointer-moved callback, its state notification), so that no question about the new generation is answered under the retired one's token;
- the ANNOUNCEMENT belongs LAST, once the read path has followed the pointer, so that a reader re-reading the instant it is told is answered by the generation the announcement names rather than by the one it replaced.
The announcer carries the token rotated HERE, so a pointer move published under a token it did not rotate is unexpressible, exactly as a retraction's is. It is called once; calling it again announces the same move again, which is a caller's bug and not one this guards (it holds nothing to guard it with).
A move that is NOT announced (a caller that rotated and then threw) leaves the token rotated, which is the safe direction: the next notification carries it.
Parameters ​
reason ​
string
Returns ​
(moved) => void
subscribe() ​
subscribe(
handler):StateMovedDetach
Defined in: packages/core/src/stateMoved.ts:297
BE TOLD THE STATE MOVED. Returns the detach.
const detach = indexer.onStateMoved(({entities, coherence}) => {
if (coherence !== held) return queryClient.invalidateQueries();
for (const entity of entities) queryClient.invalidateQueries({queryKey: [entity]});
});Best-effort: a handler is called as the block lands and is never called again for one it missed.