Skip to content

Documentation / @etherfold/core / IndexerGeneration

Class: IndexerGeneration<ABI, ProcessResultType> ​

Defined in: packages/core/src/indexer.ts:333

ONE GENERATION: one stream, one processor, one state.

A generation is a stream plus a fold over it, and this class is exactly that. An indexer is the CONTAINER that holds several generations and points at the one that answers reads (Indexer, container.ts); CONTEXT.md has said so since ADR-0036, and this class's name is what makes the code say it too. Nothing about what it DOES changed with the name: it still fetches, still derives reorgs, still drives one EventProcessor, and a container drives one of these per generation.

It is handed ONE already-constructed processor over ONE already-constructed state, which is precisely what a container holding N generations cannot be handed -- so a caller builds a container through openIndexer and hands it the FACTORIES instead, and the container builds one of these per generation.

It is handed the fold's IDENTITY the same way, and for the reason ADR-0086 gives: an author cannot state one, so it comes from the ARRIVAL that produced the processor. See IndexerGenerationOptions.processorIdentity.

Type Parameters ​

ABI ​

ABI extends Abi

ProcessResultType ​

ProcessResultType = void

Constructors ​

Constructor ​

new IndexerGeneration<ABI, ProcessResultType>(provider, processor, source, config, options): IndexerGeneration<ABI, ProcessResultType>

Defined in: packages/core/src/indexer.ts:494

Parameters ​

provider ​

EIP1193ProviderWithoutEvents

processor ​

EventProcessor<ABI, ProcessResultType>

source ​

IndexingSource<ABI>

config ​

ProvidedIndexerConfig<ABI>

options ​

IndexerGenerationOptions

Returns ​

IndexerGeneration<ABI, ProcessResultType>

Properties ​

_feed ​

protected _feed: object

Defined in: packages/core/src/indexer.ts:479

ifNotExecuting ​

ifNotExecuting: (args) => Promise<LastSync<ABI>>

Parameters ​
args ​
lastSyncFetched ​

LastSync<ABI>

newEvents ​

LogEvent<ABI>[]

replay? ​

boolean

Returns ​

Promise<LastSync<ABI>>

next ​

next: (args) => Promise<LastSync<ABI>>

Parameters ​
args ​
lastSyncFetched ​

LastSync<ABI>

newEvents ​

LogEvent<ABI>[]

replay? ​

boolean

Returns ​

Promise<LastSync<ABI>>

now ​

now: (args) => Promise<LastSync<ABI>>

Parameters ​
args ​
lastSyncFetched ​

LastSync<ABI>

newEvents ​

LogEvent<ABI>[]

replay? ​

boolean

Returns ​

Promise<LastSync<ABI>>

once ​

once: (args) => Promise<LastSync<ABI>>

Parameters ​
args ​
lastSyncFetched ​

LastSync<ABI>

newEvents ​

LogEvent<ABI>[]

replay? ​

boolean

Returns ​

Promise<LastSync<ABI>>

executing ​

Get Signature ​

get executing(): Promise<T> | undefined

Returns ​

Promise<T> | undefined

block() ​

block(): void

Returns ​

void

cancel() ​

cancel(): void

Returns ​

void

reset() ​

reset(): void

Returns ​

void

unblock() ​

unblock(): void

Returns ​

void


_follow ​

protected _follow: object

Defined in: packages/core/src/indexer.ts:484

ifNotExecuting ​

ifNotExecuting: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

next ​

next: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

now ​

now: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

once ​

once: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

executing ​

Get Signature ​

get executing(): Promise<T> | undefined

Returns ​

Promise<T> | undefined

block() ​

block(): void

Returns ​

void

cancel() ​

cancel(): void

Returns ​

void

reset() ​

reset(): void

Returns ​

void

unblock() ​

unblock(): void

Returns ​

void


_index ​

protected _index: object

Defined in: packages/core/src/indexer.ts:478

ifNotExecuting ​

ifNotExecuting: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

next ​

next: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

now ​

now: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

once ​

once: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

executing ​

Get Signature ​

get executing(): Promise<T> | undefined

Returns ​

Promise<T> | undefined

block() ​

block(): void

Returns ​

void

cancel() ​

cancel(): void

Returns ​

void

reset() ​

reset(): void

Returns ​

void

unblock() ​

unblock(): void

Returns ​

void


_load ​

protected _load: object

Defined in: packages/core/src/indexer.ts:483

ifNotExecuting ​

ifNotExecuting: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

next ​

next: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

now ​

now: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

once ​

once: () => Promise<LastSync<ABI>>

Returns ​

Promise<LastSync<ABI>>

executing ​

Get Signature ​

get executing(): Promise<T> | undefined

Returns ​

Promise<T> | undefined

block() ​

block(): void

Returns ​

void

cancel() ​

cancel(): void

Returns ​

void

reset() ​

reset(): void

Returns ​

void

unblock() ​

unblock(): void

Returns ​

void


_save ​

protected _save: object

Defined in: packages/core/src/indexer.ts:485

ifNotExecuting ​

ifNotExecuting: (args) => Promise<StreamWriteOutcome>

Parameters ​
args ​
eventStream ​

LogEvent<ABI>[]

lastSync ​

LastSync<ABI>

source ​

IndexingSource<ABI>

Returns ​

Promise<StreamWriteOutcome>

next ​

next: (args) => Promise<StreamWriteOutcome>

Parameters ​
args ​
eventStream ​

LogEvent<ABI>[]

lastSync ​

LastSync<ABI>

source ​

IndexingSource<ABI>

Returns ​

Promise<StreamWriteOutcome>

now ​

now: (args) => Promise<StreamWriteOutcome>

Parameters ​
args ​
eventStream ​

LogEvent<ABI>[]

lastSync ​

LastSync<ABI>

source ​

IndexingSource<ABI>

Returns ​

Promise<StreamWriteOutcome>

once ​

once: (args) => Promise<StreamWriteOutcome>

Parameters ​
args ​
eventStream ​

LogEvent<ABI>[]

lastSync ​

LastSync<ABI>

source ​

IndexingSource<ABI>

Returns ​

Promise<StreamWriteOutcome>

executing ​

Get Signature ​

get executing(): Promise<T> | undefined

Returns ​

Promise<T> | undefined

block() ​

block(): void

Returns ​

void

cancel() ​

cancel(): void

Returns ​

void

reset() ​

reset(): void

Returns ​

void

unblock() ​

unblock(): void

Returns ​

void


config ​

protected config: UsedIndexerConfig<ABI>

Defined in: packages/core/src/indexer.ts:358


defaultFromBlock ​

readonly defaultFromBlock: number

Defined in: packages/core/src/indexer.ts:338


finality ​

protected finality: number

Defined in: packages/core/src/indexer.ts:359


followedEmissions ​

protected followedEmissions: string[] | undefined

Defined in: packages/core/src/indexer.ts:418

THE EMISSIONS THIS FOLLOWER HAS ALREADY FOLDED, over the range it resumes from -- and the whole of how a follower decides there is something new.

A follower advances by re-reading the stream from getFromBlock and replaying what is beyond its own fold, so it needs a cheap "has this stream changed" question or every idle cycle re-walks and re-decodes the window. That question used to be asked of the stream's CURSOR (lastToBlock), and a cursor is a SUMMARY of a stream, which can lie: a PAUSED writer caps its toBlock (ADR-0045), so a reorg it detects at or below the cap is appended to the stream with the cursor NEVER MOVING, and a follower level with the cap concluded there was nothing to follow and kept a branch the chain had abandoned. The guard was written when a frozen cursor could not exist -- a running writer's lastToBlock rises with the tip every cycle -- so neither that rule nor pause is wrong on its own; the defect lived only where they met.

So this remembers the EMISSIONS THEMSELVES (emissionMarkOf, in stream order) rather than a number describing them. It cannot go stale the way lastToBlock did, because it is not a claim the writer makes ABOUT the stream: it is the content of the stream over exactly the range this follower would replay, compared against the content it last folded. Anything appended there -- a retraction, its replacement, a block the cursor does not mention -- makes the two differ, whatever any cursor says. ADR-0049 records the decision and why a cursor-derived shortcut is the thing to be suspicious of here.

Set ONLY where this generation has just folded that exact slice, so "equal" always means "already folded", and undefined (nothing folded yet, or the stream this follows has changed underneath) means FOLD, which is the safe direction: a replay of what is already folded is de-duplicated against the unconfirmed window (generateStreamFromReplay) and delivers nothing.


lastSync ​

protected lastSync: LastSync<ABI> | undefined

Defined in: packages/core/src/indexer.ts:366


logEventFetcher ​

protected logEventFetcher: LogEventFetcher<ABI>

Defined in: packages/core/src/indexer.ts:364


onLastSyncUpdated ​

onLastSyncUpdated: ((lastSync) => void) | undefined

Defined in: packages/core/src/indexer.ts:341


onLoad ​

onLoad: ((state) => Promise<void>) | undefined

Defined in: packages/core/src/indexer.ts:339


onStateUpdated ​

onStateUpdated: ((state) => void) | undefined

Defined in: packages/core/src/indexer.ts:340


pausedToBlock ​

protected pausedToBlock: number | undefined

Defined in: packages/core/src/indexer.ts:470

maxToBlock: the block a paused generation will not fetch above, and the WHOLE of what a pause is.

PINNED BY THE FIRST PAUSED CYCLE rather than by pause() itself, which is the one subtlety here. pause() is synchronous and a fetch may be in flight when it is called; a cap pinned from the cursor at that moment would then sit BELOW the cursor the racing batch leaves behind, and getFromBlock would immediately ask for a block above it -- so the generation would go idle holding an unconfirmed window it never re-scanned, which is exactly the hazard draining exists to remove. Pinned inside the serialized index action instead, it is by construction the cursor the paused generation actually has.


pauseRequested ​

protected pauseRequested: boolean = false

Defined in: packages/core/src/indexer.ts:455

Whether this generation has been asked to PAUSE. The cap itself is pausedToBlock, pinned by the first paused cycle.


processor ​

protected processor: EventProcessor<ABI, ProcessResultType>

Defined in: packages/core/src/indexer.ts:496


provider ​

protected provider: MethodDeclaringProvider

Defined in: packages/core/src/indexer.ts:355

The node, behind the engine's declared surface (ADR-0073, ADR-0002).

Wrapped rather than held raw so that every call this class and everything it builds makes is seen by ONE guard: logEventFetcher is constructed from this field, so the log path is covered by the same wrapper as the tip and identity reads. A method outside the declared set is REFUSED here rather than answered by whatever double or node happens to be underneath.


source ​

protected source: IndexingSource<ABI>

Defined in: packages/core/src/indexer.ts:356


sourceHashes ​

protected sourceHashes: SourceHashEntry[]

Defined in: packages/core/src/indexer.ts:361


streamConfigHash ​

protected streamConfigHash: string

Defined in: packages/core/src/indexer.ts:362


streamDeclineReported ​

protected streamDeclineReported: boolean = false

Defined in: packages/core/src/indexer.ts:449

So the decline is said once rather than once per cycle.


streamFrozen ​

protected streamFrozen: boolean = false

Defined in: packages/core/src/indexer.ts:446

The cache has failed too often and is no longer allowed to stop the indexer.

FROZEN, not cleared: what is on disk is a contiguous prefix with a cursor that describes it honestly, which replays as a partial seed. Throwing it away would cost a re-fetch from the source's first block, which on a public node can be impossible.


streamLastToBlock ​

protected streamLastToBlock: number | undefined

Defined in: packages/core/src/indexer.ts:385

How far the STORED stream claims to reach, as this session last saw it, or undefined when there is no stream (none kept, absent, or just cleared).

This is the whole of the no-hole rule. A batch is a DELTA against the STATE's cursor, so it is only safe to append when the stream is at or AHEAD of that cursor: then everything between what is stored and what the batch carries is already stored. When the stream is BEHIND -- which only a frozen cache can produce -- the blocks in between were emitted to nobody, and appending would claim coverage of them forever after.

Comparing the two BLOCK RANGES instead would not do: the batch's fetch range reaches back into the finality window on every cycle, so it OVERLAPS a stream the state has already run past, while the events it carries start above the gap. That is the shape a keeper-side guard cannot see, which is why this lives here.


streamWriteFailureLimit ​

protected streamWriteFailureLimit: number = DEFAULT_STREAM_WRITE_FAILURE_LIMIT

Defined in: packages/core/src/indexer.ts:472


streamWriteFailures ​

protected streamWriteFailures: number = 0

Defined in: packages/core/src/indexer.ts:436

Consecutive failed writes, reset by any successful one.


streamWriteRetryDelay ​

protected streamWriteRetryDelay: number = DEFAULT_STREAM_WRITE_RETRY_DELAY

Defined in: packages/core/src/indexer.ts:473


streamWrittenNotProcessed ​

protected streamWrittenNotProcessed: LogEvent<ABI>[] | undefined

Defined in: packages/core/src/indexer.ts:433

The batch that IS written but that the processor has not accepted yet.

The in-memory slot the deleted streamNotYetSaved list occupied, inverted: a high-water mark of what is written rather than a buffer of what is not, which is the honest thing to remember once the cursor can no longer run ahead of the write. A processor that throws leaves the events on disk and the cursor put, so the next cycle re-derives the same delta -- and without this the cache would grow by one duplicate copy per retry.

In-memory is the right scope: it only has to survive the retry loop, and a reload is covered by the load path catching the state up to the stream.


suppliedProcessorIdentity ​

protected suppliedProcessorIdentity: string

Defined in: packages/core/src/indexer.ts:517

WHAT THIS FOLD IS CALLED: the identity its ARRIVAL derived (ADR-0086), held beside processor rather than asked of it.

It travels WITH the processor: updateProcessor replaces both together, so a fold and the name it answers to cannot come apart. Not readonly for exactly that reason.

Every place this engine needs the fold half of a generation reads this field, so the cursor it writes, the invalidation it decides and the swap it declines all speak of one value.

Accessors ​

expectedFromBlock ​

Get Signature ​

get expectedFromBlock(): number

Defined in: packages/core/src/indexer.ts:707

Returns ​

number


finalityDepth ​

Get Signature ​

get finalityDepth(): number

Defined in: packages/core/src/indexer.ts:612

The finality depth this indexer actually runs with, defaults applied.

Exposed because it is not derivable from the config a caller PASSED (that one may leave stream.finality unset, and resolveStreamConfig fills it), and because it is what bounds the unconfirmed window: a consumer reasoning about that window (checkTxInclusion) needs the same number this indexer used, not a second copy of the default.

Returns ​

number


maxToBlock ​

Get Signature ​

get maxToBlock(): number | undefined

Defined in: packages/core/src/indexer.ts:621

The block this generation will not fetch above, or undefined when it is running. See pausedToBlock; undefined while paused means the cap has not been pinned yet, because no cycle has run since pause().

Returns ​

number | undefined


pauseState ​

Get Signature ​

get pauseState(): PauseState

Defined in: packages/core/src/indexer.ts:630

DERIVED, never stored: the cap plus the existing getFromBlock already say where a pause has got to, and a second copy of that answer could disagree with the one the fetch loop acts on.

Returns ​

PauseState

Methods ​

_onLastSyncUpdated() ​

protected _onLastSyncUpdated(): void

Defined in: packages/core/src/indexer.ts:2046

Returns ​

void


_onLoad() ​

protected _onLoad(state): Promise<void>

Defined in: packages/core/src/indexer.ts:2056

Parameters ​

state ​

LoadingState

Returns ​

Promise<void>


_onStateUpdated() ​

protected _onStateUpdated(outcome): void

Defined in: packages/core/src/indexer.ts:2036

Parameters ​

outcome ​

ProcessResultType

Returns ​

void


checkGenesisHash() ​

protected checkGenesisHash(expectedGenesisHash): Promise<void>

Defined in: packages/core/src/indexer.ts:1239

The stronger half of the load-time identity guard: the chain's genesis block is the one this source declares.

Three conditions, three refusals ​

The check can fail in three unrelated ways and a caller does something DIFFERENT about each: a wrong chain is a configuration error (stop), a node that will not serve block 0 is an availability fact about the NODE (point elsewhere, or set skipGenesisCheck), and a failed request is transience (retry). They used to be one uncaught throw and one message, so a flaky endpoint at startup was indistinguishable from being pointed at the wrong chain -- and both of them announced a WRONG CHAIN, which is a claim only the first one is entitled to make.

Parameters ​

expectedGenesisHash ​

string

Returns ​

Promise<void>


clearStoredStream() ​

protected clearStoredStream(): Promise<void>

Defined in: packages/core/src/indexer.ts:1171

The other half of the rule above: a substrate that cannot be read cannot be emptied either.

Returns ​

Promise<void>


disableProcessing() ​

disableProcessing(): void

Defined in: packages/core/src/indexer.ts:898

Returns ​

void


feed() ​

feed(eventStream, lastSyncFetched): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:737

Hand over a FETCH: raw logs, complete over the range the cursor asks for, carrying no verdicts of their own.

Every retraction is DERIVED here, by comparing the cursor's unconfirmed window against the incoming blocks, exactly as the live path derives it -- which is why a batch carrying removed markers is refused rather than accepted with them dropped. A stream that already knows what it took back is a REPLAY and goes through replay().

Parameters ​

eventStream ​

LogEvent<ABI>[]

lastSyncFetched ​

LastSync<ABI>

Returns ​

Promise<LastSync<ABI>>


fetchLogsFromProvider() ​

fetchLogsFromProvider<ABI>(lastSync, unlessCancelled): Promise<{ eventStream: LogEvent<ABI>[]; lastSync: LastSync<ABI>; }>

Defined in: packages/core/src/indexer.ts:1917

Type Parameters ​

ABI ​

ABI extends Abi

Parameters ​

lastSync ​

LastSync<ABI>

unlessCancelled ​

<T>(p) => Promise<T>

Returns ​

Promise<{ eventStream: LogEvent<ABI>[]; lastSync: LastSync<ABI>; }>


followMore() ​

followMore(): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:850

ADVANCE FROM THE STORED STREAM ALONE, and fetch NOTHING.

This is how a generation that does not OWN its stream moves: it reads what the stream now holds beyond its own cursor and REPLAYS it. indexMore is the other verb and the two are never both right for one generation -- which one applies is DETERMINED by whether this generation shares its stream with the one indexing it (Indexer.add), and is never a configuration knob.

Why a follower must not poll the head ​

Three things break at once if it does, and the third is the one that matters. The re-fetch stops being zero and becomes merely fewer. The one-writer rule goes, because the save is driven by the indexing loop rather than by the caller's intent (which is why the read-only stream view exists at all). And -- the load-bearing one -- this generation's state becomes a function of ITS OWN FETCH rather than of the stream, so re-folding the stored stream later yields a DIFFERENT state. A generation would stop being "a stream plus a fold over it", and the promise that moving the canonical pointer back restores answers EXACTLY would go with it.

What it is, mechanically ​

The CATCH-UP branch of promiseToLoad, made repeatable. The first call has no cursor, so it loads -- which re-folds the whole stored stream from the start -- and every call after it reads the stream from where this cursor resumes and replays what is new. Nothing here fetches, and nothing here writes or CLEARS: a stream this generation does not own is not its to repair, which is why every branch below simply returns rather than clearing as the load path does.

Returns ​

Promise<LastSync<ABI>>


forgetStoredStream() ​

protected forgetStoredStream(): void

Defined in: packages/core/src/indexer.ts:1193

There is no stream on disk any more, so nothing constrains the next write.

Returns ​

void


freshLastSync() ​

protected freshLastSync(processorHash): LastSync<ABI>

Defined in: packages/core/src/indexer.ts:2019

Parameters ​

processorHash ​

string

Returns ​

LastSync<ABI>


hasAlreadyFolded() ​

protected hasAlreadyFolded(eventsStored): boolean

Defined in: packages/core/src/indexer.ts:1211

Whether this slice of the stream is EXACTLY the one this generation last folded, emission for emission and in order.

The replacement for the cursor comparison, and it is deliberately an equality and not an ordering: "the stream has not changed where I would read it" is the only question whose answer justifies doing nothing. Anything else -- a longer slice, a shorter one, one that shares a prefix -- falls through to the replay, which is always correct and merely costs a walk.

Parameters ​

eventsStored ​

readonly (StoredLogEvent | LogEvent<ABI>)[]

Returns ​

boolean


indexMore() ​

indexMore(): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:804

Returns ​

Promise<LastSync<ABI>>


load() ​

load(): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:714

Returns ​

Promise<LastSync<ABI>>


onStreamWriteFailed() ​

protected onStreamWriteFailed(e, source): Promise<StreamWriteOutcome>

Defined in: packages/core/src/indexer.ts:1762

A write that threw: count it, clear ONLY for the cause the cache itself is, and give up on the cache once it has failed too often.

Parameters ​

e ​

unknown

source ​

IndexingSource<ABI>

Returns ​

Promise<StreamWriteOutcome>


pause() ​

pause(): void

Defined in: packages/core/src/indexer.ts:697

PAUSE: stop indexing WITHOUT being deleted, and without ever answering with state a reorg has invalidated underneath.

It caps toBlock at this generation's cursor and NOTHING ELSE. It truncates nothing, reverts nothing and never touches revertTo, so what this generation answers at the moment it pauses is what it goes on answering -- which is what keeps a paused generation revertible-TO: moving the canonical pointer back to it restores its answers EXACTLY.

Why it needs no new mechanism ​

With toBlock capped at x and lastToBlock = x, the existing getFromBlock does the whole thing. While latestBlock - finality <= x it returns latestBlock - finality, so every round re-scans [that, x] -- a SHRINKING window that still corrects a reorg striking at or below the cap. Once latestBlock - finality > x it returns x + 1, which is above the capped toBlock, so the indexer takes its existing fromBlock > toBlock branch and fetches nothing. A paused generation self-terminates into a no-op poll; there is no timer, no branch and no state machine here to get wrong.

lastSync.latestBlock deliberately keeps tracking the REAL head. Capping that too would make getFromBlock return latestBlock - finality forever and the drain would NEVER idle.

What it is NOT ​

It is NOT INSTANT: draining takes up to finality blocks of continued light polling, and a driver that stops calling indexMore() the moment it pauses leaves the generation holding an unconfirmed window forever. Watch pauseState for the moment it is drained.

It is NOT disableProcessing(), which stops the engine where it stands -- that IS the hazard: a generation carrying an unconfirmed window it can no longer correct never finds out that one of those blocks was reorged away, and its state permanently contains events from blocks that no longer exist.

It is NOT persisted. A reload comes back running, and re-pausing costs one drain; making it durable is a registry record, which is the registry's to add when something needs it. It DOES survive a reconfigure in the session that asked for it -- reinit does not clear the cap -- because "do not go above x" is a decision about this generation and not about the source it was made under; resume() is the only thing that lifts it.

It caps the verb that FETCHES (indexMore). A generation advanced with followMore goes exactly as far as the STREAM it folds and holds no toBlock of its own, so a cap would be a no-op that lied about being a pause: Indexer.pause refuses a follower for that reason (CannotPauseFollowerError).

Returns ​

void


processorIdentity() ​

protected processorIdentity(): string

Defined in: packages/core/src/indexer.ts:520

See suppliedProcessorIdentity: the arrival's identity, answered as given.

Returns ​

string


promiseToFeed() ​

protected promiseToFeed(params, __namedParameters): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:1530

Parameters ​

params ​
lastSyncFetched ​

LastSync<ABI>

newEvents ​

LogEvent<ABI>[]

replay? ​

boolean

The events are a stored STREAM carrying its own verdicts, not a fetch.

__namedParameters ​

CancelOperations

Returns ​

Promise<LastSync<ABI>>


promiseToFollow() ​

protected promiseToFollow(__namedParameters): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:1442

One FOLLOW step: what the stream holds beyond this cursor, replayed.

Every early return leaves the stream exactly where it is. The load path CLEARS on each of these shapes because it owns the stream it is reading; this does not own it, and a follower that cleared would delete the indexing generation's history out from under it. The cost of returning instead is one idle cycle, and the next one asks again.

Parameters ​

__namedParameters ​

CancelOperations

Returns ​

Promise<LastSync<ABI>>


promiseToIndex() ​

protected promiseToIndex(__namedParameters): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:1798

Parameters ​

__namedParameters ​

CancelOperations

Returns ​

Promise<LastSync<ABI>>


promiseToLoad() ​

protected promiseToLoad(): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:1256

Returns ​

Promise<LastSync<ABI>>


promiseToSave() ​

protected promiseToSave(params): Promise<StreamWriteOutcome>

Defined in: packages/core/src/indexer.ts:1612

Write the batch to the cached stream, and SAY what happened.

It reports rather than swallows, because the caller's next move depends on it: a batch that was not written must not be processed, or the state advances past events the stream never received and the cursor claims coverage of a range whose events are simply absent.

What used to be here was an in-memory list of unsaved events carried to the next save on the save action's promise CONTEXT. It never fired -- the context survives only when a save is queued onto one still in flight, and the index cycle awaits its save, so the list was empty at push time every time. It existed only to compensate for processing first; with the order flipped and the cursor held back, the next cycle re-derives those events from the same cursor, so it is redundant and it is gone.

Parameters ​

params ​
eventStream ​

LogEvent<ABI>[]

lastSync ​

LastSync<ABI>

source ​

IndexingSource<ABI>

Returns ​

Promise<StreamWriteOutcome>


readStoredStream() ​

protected readStoredStream(fromBlock): Promise<{ eventStream: StoredLogEvent[]; lastSync: StoredLastSync; } | undefined>

Defined in: packages/core/src/indexer.ts:1125

A CACHE MUST NEVER WEDGE THE INDEXER, applied HERE because this is the caller the rule is about.

fetchFrom and clear are called from the load path below with no try/catch above them, so a keeper that raises from either does not degrade a cache -- it makes load() reject, on this boot and every boot after it, for a LOCAL CACHE whose correct recovery is to throw the bytes away and index again. That is the difference between a slow app and a dead one, and it is story 12 of a-reconfigure-is-not-an-outage. Absent is the honest answer for a substrate that is simply unavailable (IndexedDB refused in private browsing, storage evicted, a database at a version this build cannot open), and it is the one the load path already knows what to do with.

This USED to live in a degradingStream wrapper each keeper applied to itself, and it moved here because a policy at the SEAM binds every caller, including ones for whom it is false. "Absence is safe" is a statement about the load path: this generation responds to an absent stream by re-indexing, so losing one costs time. It is NOT true of a caller that responds to an absent stream by WRITING -- installStreamSeed reads emptiness as permission to install, so a swallowed read failure let it append a seed under a stream that was really there, duplicating events beneath a cursor moved backwards. A keeper cannot know which kind of caller it has, so it no longer decides for them: it raises, and each caller applies its own policy (ADR-0068).

The WRITE side is deliberately not guarded here or anywhere: promiseToSave catches saveNewEvents itself and what it does with the failure is load-bearing -- it counts it, paces the retry, freezes the cache, and until then does not process the batch at all. A swallowed write failure would let the state advance past events the stream never received, which is a HOLE.

Parameters ​

fromBlock ​

number

Returns ​

Promise<{ eventStream: StoredLogEvent[]; lastSync: StoredLastSync; } | undefined>


reenableProcessing() ​

reenableProcessing(): void

Defined in: packages/core/src/indexer.ts:907

Returns ​

void


reinit() ​

reinit(provider, source, config): void

Defined in: packages/core/src/indexer.ts:524

Parameters ​

provider ​

EIP1193ProviderWithoutEvents

source ​

IndexingSource<ABI>

config ​

ProvidedIndexerConfig<ABI>

Returns ​

void


replay() ​

replay(eventStream, lastSyncStored): Promise<LastSync<ABI>>

Defined in: packages/core/src/indexer.ts:792

Hand over a STORED emission stream, verdicts included.

The counterpart of feed(), and the entry a rebuild off a cached stream takes (promiseToLoad, both branches). What arrives here is not a re-read of a block range: it is the sequence of applies and retractions some earlier run already decided, so the engine HONOURS it instead of recomputing it from a window a rebuild does not have. It shares promiseToFeed's delivery -- the per-block batching, and the rule that every retraction goes in ONE batch -- and differs only in how the stream and the new cursor are derived; see generateStreamFromReplay.

lastSyncStored is the stream's OWN cursor, whose lastFromBlock must be the block this indexer asked the keeper for. Its unconfirmedBlocks is ignored and may be empty: no stream keeper stores the window (ADR-0035), and the replay rebuilds it from the stream itself.

Parameters ​

eventStream ​

LogEvent<ABI>[]

lastSyncStored ​

LastSync<ABI>

Returns ​

Promise<LastSync<ABI>>


reset() ​

reset(): Promise<ReconfigureOutcome>

Defined in: packages/core/src/indexer.ts:1066

Returns ​

Promise<ReconfigureOutcome>


resume() ​

resume(): void

Defined in: packages/core/src/indexer.ts:702

RESUME: remove the cap, and nothing else. The next round asks the head again.

Returns ​

void


save() ​

protected save(source, eventStream, lastSync): Promise<StreamWriteOutcome>

Defined in: packages/core/src/indexer.ts:1086

Parameters ​

source ​

IndexingSource<ABI>

eventStream ​

LogEvent<ABI>[]

lastSync ​

LastSync<ABI>

Returns ​

Promise<StreamWriteOutcome>


stateMatches() ​

protected stateMatches(lastToBlock, context): boolean

Defined in: packages/core/src/indexer.ts:2010

Whether the persisted STATE is still a fold over what this source means.

Parameters ​

lastToBlock ​

number

context ​

ContextIdentifier

Returns ​

boolean


streamCanReceive() ​

protected streamCanReceive(): boolean

Defined in: packages/core/src/indexer.ts:1718

Whether the stream is at or AHEAD of the state, which is the only position from which an append cannot leave a hole. See streamLastToBlock.

The permissive default was ONE condition and is TWO facts (ADR-0087, amended) ​

ADR-0087 first asked for this whole default to REFUSE, on the ground that an unknown stream position must not permit. Measured, the two halves do not get the same answer and only one of them may refuse:

  • !this.lastSync REFUSES, and doing so is inert: promiseToIndex loads before it fetches and load sets lastSync on every branch it returns through, so nothing reaches it today. It refuses anyway, because "we do not know where the STATE is" is genuinely an unknown position, and the one thing this comparison may never do is guess in the permissive direction.
  • this.streamLastToBlock === undefined STAYS PERMISSIVE. It is not an unknown position, it is the documented ABSENCE of a stream (forgetStoredStream: "there is no stream on disk any more, so nothing constrains the next write"). Refusing there declines the first save of every fresh deployment, and it destroys the case streamSeedInstall.test.ts protects by name (locallyIndexedAbove): a stream lost while its STATE survived is re-opened by the next save at the state's resume point, and the new subtree RECORDS that resume point as its own startBlock, so a read from below it is answered does-not-reach-back rather than served. That is an honest partial stream, not a hole.

The DUPLICATE that ADR-0087 attributed to this method was never on its path: this sits over an ExistingStream the load path always reads first, while the hand-over it measured wrote through an append-only EmissionAppender with no hole guard of any kind. That guard now exists, on the thing that FETCHES a stream and therefore writes it (StreamHoleError), where the hazard actually is.

Returns ​

boolean


streamMatches() ​

protected streamMatches(lastToBlock, context): boolean

Defined in: packages/core/src/indexer.ts:2015

Whether the cached raw log STREAM was fetched under a filter this source is still covered by.

Parameters ​

lastToBlock ​

number

context ​

ContextIdentifier

Returns ​

boolean


streamRemainderOf() ​

protected streamRemainderOf(eventStream): LogEvent<ABI>[]

Defined in: packages/core/src/indexer.ts:1744

The part of this batch that is not already on disk, with a retraction for anything written that the chain has since moved under.

The batch handed to a save is a delta against the IN-MEMORY cursor, and that cursor only advances after process RETURNS -- so a processor that throws leaves its events written and the next cycle re-derives the same list plus whatever the tip has added. Appending that again would grow the cache by one duplicate copy per retry, and those duplicates would replay twice once the handler is fixed.

Where the two lists DIVERGE, the chain reorged under events the processor never accepted: the state cannot retract them (it never applied them), and a replay of the stream would apply a dead branch. So they are retracted HERE, at their original block, which is exactly what the emission stream is for.

Parameters ​

eventStream ​

LogEvent<ABI>[]

Returns ​

LogEvent<ABI>[]


takeOverStream() ​

takeOverStream(keepStream): void

Defined in: packages/core/src/indexer.ts:890

TAKE THE PEN of the stream this generation has been FOLLOWING: from here on it fetches that stream and appends to it.

A follower was handed a READ-ONLY VIEW of the keeper at construction (readOnlyStream, ADR-0044), which is what makes the one-writer rule structural rather than conventional -- read and write share one seam, so "merely reading" is expressed by holding a stream whose writes go nowhere. This swaps that view for the keeper itself, which is the ONLY way a fold can stop being read-only, and it is deliberately the whole of the mechanism: nothing else about the engine changes, because everything else a writer needs this fold already has.

It is the CONTAINER's to call and only at a PROMOTION (Indexer, ADR-0090 point 2). This engine cannot know that it is safe: what makes it safe is that the generation losing the stream is being DROPPED in the same act, and that this fold is provably AT that generation's cursor -- under on-catch-up the promotion IS that event, and under immediate the deferred drop has already waited for it. Called at any other moment it would make a second writer, which is the measured data-loss defect the read-only view exists to prevent.

Nothing is re-derived and nothing is re-loaded. streamLastToBlock is already where the follow left it -- every follow cycle reads the stored cursor -- so the hole guard (streamCanReceive) answers over the position this fold actually has, and the first fetch reaches back over its own unconfirmed window, which the replay built, so a re-read block is recognised rather than appended twice.

Parameters ​

keepStream ​

ExistingStream<ABI>

Returns ​

void


updateIndexer() ​

updateIndexer(update): Promise<ReconfigureOutcome>

Defined in: packages/core/src/indexer.ts:912

Parameters ​

update ​
provider? ​

EIP1193ProviderWithoutEvents

source? ​

IndexingSource<ABI>

streamConfig? ​

ProvidedStreamConfig

Returns ​

Promise<ReconfigureOutcome>


updateProcessor() ​

updateProcessor(newProcessor, options): Promise<ReconfigureOutcome>

Defined in: packages/core/src/indexer.ts:1011

Parameters ​

newProcessor ​

EventProcessor<ABI, ProcessResultType>

options ​
force? ​

boolean

processorIdentity ​

string

Returns ​

Promise<ReconfigureOutcome>