Documentation / @finchart/core / index / infiniteHistory
Function: infiniteHistory()
Call Signature
infiniteHistory<
T>(host,sink,fetch,options):CursorHistoryLoader<never>
Defined in: packages/core/src/extensions/infinite-history.ts:332
Loads older data as the view approaches or passes the left edge of what is held — the bookkeeping every infinite-scroll consumer was writing by hand: the cursor, the threshold test, in-flight dedup, and the re-judgment after a landing (a prepend doesn't move the domain, so no event ever announces it).
The threshold is measured in pixels, which makes it coordinate- system-proof: under bar-index x it equals a bar-count test (immune to weekend and session gaps), under continuous x it equals a screen-width test. The cursor is always a real point's x, so the conversion is an interpolation — never the left-of-data extrapolation whose slope shifts when a page lands.
Two triggers, deliberately different:
- Gap fill — the screen shows a stretch with no data (more blank than the half bar a fit leaves before the first point). Fires regardless of gesture and chains page after page until the screen is covered. At the left wall, pan is clamped and emits no events at all, so this landing-driven loop is the only way out — which is why pages that make progress are never capped: capped, the chart would stall on a blank screen forever. The screen itself bounds the chain (zoom limits bound the screen). In x mode an empty page ends it; in cursor mode an empty page with a
nextmoves the token and chains on, so only a run of pages that keep no older point is capped (nine in a row terminate). - Prefetch — runway is short (slack under
screensAheadscreens) and the user actually moved left. One page per gesture. AsetDatarefit or a fit-all reports zero slack without a leftward move and must not fire — the user wasn't going to the past.
A landed page is the cursor owner's to defend (the sorted-data rulebook itself stays where it lives, in the data layer):
- Points at or after
beforeare trimmed off quietly — inclusive end bounds are the norm for exchange REST APIs, and every consumer would otherwise rediscover the same one-line filter. Left alone, the boundary bar would slip through prepend's seam check (equal x is legal for line data; bars declareuniqueXand reject it loudly) and silently double. - In x mode, a non-empty page trimmed to nothing throws, carrying
beforeand the page's last x: a fetch that ignores its cursor must not read as "the end of history". In cursor mode a token names a page, not a time, so such a page is no progress — the token moves on, and only a run of them terminates. - An out-of-order page throws and terminates the loader: the fetch's shape is wrong, so a retry is an exception fountain, not a recovery. (
[...page].reverse()belongs inside the fetch, like backoff.)
A rejected fetch is transient by contrast: loading recovers and the next gesture retries naturally.
Landing cost — a landing pays for the points held, not the page: a prepend copies the whole array and shifts any populated cached x values by the page. A derivation pays by its door. With a head door (deriveFirst), the built-in manager, prior output and a non-empty page, the door's head returns outputs for the page plus up to its declared lookback of old ones; when some old output outlives that lookback the result is spliced over the retained tail — same objects, normally only the head and the seam validated — and when the lookback covers all of it the head goes through setData and full validation. Without a head door the whole input is re-derived and fully validated. Measured at 100k candles, +500 bars per landing, at a 500-bar window: 0.40ms bare, 3.90ms with four SMA derivations through their head doors — under the 8ms hitch line. Landings equal pages, so a chart whose derivations re-derive wholesale still wants larger, rarer pages.
Changing worlds under the loader is undefined. Swapping symbols by calling setData on the same handle cannot be detected here — dispose first, then wrap again with the new from:
const loader = infiniteHistory(plot, (page) => handle.prepend(page),
(before) => api.candlesBefore(before), { from: candles[0].x });
// later: loader.dispose() — or scope.add(() => loader.dispose())Type Parameters
T
T extends BaseDataPoint
Parameters
host
sink
HistorySink<T> | HistoryHandle<T>
fetch
HistoryFetch<T>
options
Returns
CursorHistoryLoader<never>
Call Signature
infiniteHistory<
T,C>(host,sink,fetch,options):CursorHistoryLoader<C>
Defined in: packages/core/src/extensions/infinite-history.ts:347
Cursor mode: fetch is asked with options.cursor, then with each taken page's next. The loader trims and judges by x as in x mode. A page that keeps older bars moves the token only once they're delivered — a sink that throws leaves the same token for the retry; a page that keeps none moves it with no delivery. next: null ends the history after that page is delivered; an empty page with a next moves the token and keeps filling a gap. A page that isn't { bars, next } terminates.
Type Parameters
T
T extends BaseDataPoint
C
C extends object
Parameters
host
sink
HistorySink<T> | HistoryHandle<T>
fetch
HistoryCursorFetch<T, C>
options
InfiniteHistoryCursorOptions<T, C>