Skip to content

Documentation / @etherfold/processor-entities / applyEventStream

Function: applyEventStream() ​

applyEventStream<ABI, ProcessorConfig>(store, processor, eventStream, config, cursor?, report?): Promise<void>

Defined in: packages/processor-entities/src/apply.ts:119

Apply a whole stream to a store, reverting first if any of it is a retraction.

This is the backend-agnostic half of processing, and every backend gets it identically because it is written once, here, against StateStore. The stream is a flat list in which reorged-out events carry removed: true and are followed by the canonical replacements, if there are any. Three things about how that becomes storage calls are load-bearing:

  1. Revert ONCE, at the fork point, not per removed event. The fork point is one below the LOWEST removed block in the stream, and it has to be computed over the whole stream before anything is applied. Reverting per event would issue N reverts where the second is a no-op at best and, if the events were ordered high-to-low, would revert to the wrong height.

  2. A removed event is a removed event, whatever caused it. The engine emits retractions both when a height is replaced by a different hash (a contradiction) and when a block's logs simply vanish with no replacement (an absence: the transaction went back to the mempool). This reads removed and never looks at what replaced anything, so the second case cannot be missed. Wiring the revert to "a new hash appeared at this height" would reproduce d24872f one layer down, where the symptom is a row nobody notices instead of a state object somebody prints.

  3. Revert precedes apply, which is also what makes replay safe. A store records a block plainly and a re-applied block raises on purpose. revertTo(fork) drops every block above the fork, and the canonical events in the same stream are all at or above fork + 1, so the replacements cannot collide with the branch they replace.

One block is exactly one applyBlock, which is one atomic unit, which is why a handler never has to reason about more than the block it is in.

  1. The cursor rides WITH the block, when one is given. cursor is the LastSync this stream ends at, and each block is applied together with the cursor that describes THAT block (syncedThrough), in the store's own transaction. That is why the cursor lives behind the storage seam: nothing above the store can make the two atomic, and the gap between them is not self-healing in either direction (see cursor.ts). A caller that omits it -- a test, or the conformance workload -- gets the old behaviour and owns its own cursor.

  2. A block that was applied is REPORTED, when a reporter is given, and it is reported from HERE because this is where the mutations are. The Mutation objects carry the entity name and @etherfold/core has no mutation vocabulary at all, so the touched-entity set is produced at this layer and RELAYED upward into the signal a reader learns from (ADR-0083). One report per applyBlock that RETURNED, after it returned: a block that threw was not applied, and reporting it would tell a reader to re-read for a change that is not there. A block whose handlers produced NO mutation is still reported, with an empty set -- it was applied, the cursor moved with it, and "applied" is the thing being reported.

  3. The REVERT is reported too, naming the fork point, and it is reported from here for the same reason: rule 1's fork point is derived HERE, from the removed markers core emitted, and core is where the reader-facing signal is assembled. A reorg WITHDRAWS data, so a channel that reported only rule 5 would leave a reader rendering the branch the chain abandoned. It is reported AFTER revertTo returned and BEFORE the replacements are applied, which is the order they happened in: a reader told the other way round would re-read the replacement and then be told to throw it away. What it names is the fork point and NOT the rows that moved -- revertTo answers void at the seam, on purpose, and the token that rotates with this retraction already says invalidate everything.

  4. An applied block is reported WITH ITS HASH, in the store's spelling. A reader pins its re-read to the block it was told about (block: {hash}), and a number cannot name that block across a reorg. The hash is normalised HERE (normalizeBlockHash, the rule every backend applies on write) and not in @etherfold/core, which relays it, because core cannot import the storage seam that owns the rule (ADR-0016). So it equals the hash the query layer reads back out of the store (extensions.blockHash) byte for byte.

Type Parameters ​

ABI ​

ABI extends Abi

ProcessorConfig ​

ProcessorConfig

Parameters ​

store ​

WritableStateStore

processor ​

EntityProcessor<ABI, ProcessorConfig>

eventStream ​

readonly LogEvent<ABI>[]

config ​

ProcessorConfig

cursor? ​

key ​

string

lastSync ​

LastSync<ABI>

report? ​

FoldReporter

Returns ​

Promise<void>