# KeyedFetchMixin

Auto-generated @jbrowse/mobx-state-tree API for the current JBrowse release — see [pluggable elements](https://jbrowse.org/jb2/docs/developer_guide/) for concepts. Built into JBrowse core. [View source](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-kit/src/KeyedFetchMixin.ts).

`FetchMixin` plus the one freshness compare a single-payload fetch runs: the
key of what the view, settings and adapter call for (`currentFetchKey`)
against the key the held data was committed under (`loadedFetchKey`). The
LGV global family composes it under `GlobalFetchMixin` and the comparative
family under `ComparativeFetchMixin`; the per-region family answers the same
question per region through `isCacheValid`, so it stays on `FetchMixin`.

It was `GlobalFetchMixin`'s middle until 2026-09, while the comparative
displays carried a second spelling of every member here on
`SyntenyFetchStateMixin` — the same stamp, the same compare, a `fetching`
flag standing in for `activeStopToken`, and a `reload` that dropped the stamp
for the same reason. ADR-105.

Members a composed model contributes are listed here too, so these tables are the whole surface.

## Volatiles

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="volatile-loadedfetchkey">**loadedFetchKey**</span><br><code>loadedFetchKey: undefined as string &#124; undefined</code> | `currentFetchKey` as it stood when the held data was committed — the loaded half of the freshness compare. Written only by `commitFetchResult`, so a display cannot stamp data it did not fetch, and cleared by `reload` for the overlay's sake rather than the refetch's (the skeleton's reload epoch is what overrides its gate). The data itself stays display-owned: arc keeps stale arcs on screen under the loading overlay, HiC the stale matrix, synteny the stale ribbons. | KeyedFetchMixin |
| <span id="volatile-activestoptoken">**activeStopToken**</span><br><code>activeStopToken: undefined as StopToken &#124; undefined</code> | <span data-pagefind-ignore>stop token of the in-flight fetch, or undefined when idle</span> | [FetchMixin](../fetchmixin#volatile-activestoptoken) |
| <span id="volatile-fetchgeneration">**fetchGeneration**</span><br><code>fetchGeneration: 0</code> | <span data-pagefind-ignore>bumps at every fetch end; autoruns read it to re-evaluate, and it doubles as the staleness epoch inside runFetch</span> | [FetchMixin](../fetchmixin#volatile-fetchgeneration) |
| <span id="volatile-reloadcounter">**reloadCounter**</span><br><code>reloadCounter: 0</code> | <span data-pagefind-ignore>Bumped by `reload()` and read unconditionally by the fetch autoruns, so a user retry re-runs the body even where nothing else moved — after an error every other fetch input is unchanged. It is also the half that survives a `reload()` override that forgets to invalidate, which is the dead Retry button `makeRetryContractCheck` reports. Declared here because this is the one mixin every fetch foundation composes, the same argument that put `fetchInert` below; the comparative family carried its own until ADR-105.</span> | [FetchMixin](../fetchmixin#volatile-reloadcounter) |
| <span id="volatile-statuswindow">**statusWindow**</span><br><code>statusWindow: createStatusWindow(writeStatus(self))</code> | <span data-pagefind-ignore>This display's status field, and the only thing that writes it: one throttle window, one slot per concurrent operation, so N parallel per-region fetches thin to one stream between them rather than N and a second operation cannot end the first one's label (ADR-081). Lent whole to `createStopTokenRotation` by a display that also runs a bare-autorun fetch — see `StatusReporter`.</span> | [FetchMixin](../fetchmixin#volatile-statuswindow) |
| <span id="volatile-error">**error**</span><br><code>error: undefined as unknown</code> | <span data-pagefind-ignore>last non-abort fetch error, or undefined</span> | [FetchMixin](../fetchmixin#volatile-error) |
| <span id="volatile-statusmessage">**statusMessage**</span><br><code>statusMessage: undefined as string &#124; undefined</code> | <span data-pagefind-ignore>work-in-progress status string</span> | [FetchMixin](../fetchmixin#volatile-statusmessage) |
| <span id="volatile-statusprogress">**statusProgress**</span><br><code>statusProgress: undefined as number &#124; undefined</code> | <span data-pagefind-ignore>determinate progress fraction [0,1] for the current status, or undefined when the in-flight phase is indeterminate</span> | [FetchMixin](../fetchmixin#volatile-statusprogress) |
| <span id="volatile-fetchcanceled">**fetchCanceled**</span><br><code>fetchCanceled: false</code> | <span data-pagefind-ignore>true after the user explicitly cancels a load (the loading overlay's cancel button → `cancelFetchByUser`). A durable, blocking state — unlike `cancelFetch`, it does not retrigger the fetch autoruns — so the load stays stopped until the user retries (`reload`) or the viewport changes. Any new fetch clears it (`runFetch` resets it at the start).</span> | [FetchMixin](../fetchmixin#volatile-fetchcanceled) |
| <span id="volatile-fetchrotation">**fetchRotation**</span><br><span class="cell-more"><button type="button" class="cell-more-trigger"><code>fetchRotation: createStopTokenRotation(self, { statusWindow: se…</code></button><dialog class="cell-dialog"><form method="dialog"><button class="cell-dialog-close" aria-label="Close">✕</button></form><pre><code>fetchRotation: createStopTokenRotation(self, {&#10;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;statusWindow: self.statusWindow,&#10;&#160;&#160;&#160;&#160;&#160;&#160;})</code></pre></dialog></span> | <span data-pagefind-ignore>**The latest-wins machine this mixin is a wrapper around**, and not a second one: `createStopTokenRotation` owns token rotation, the `isCurrent` guard, the status slot and the supersede-versus-end rule (ADR-080, ADR-081), for every fetch in the codebase that has one. `runFetch` adds the observable bookkeeping a display needs on top — `isLoading`, `error`, `fetchGeneration`, `fetchCanceled` — and nothing else.<br><br>It was two implementations of that machine until 2026-08-20, which is how they came to disagree about whether a completed fetch releases its token. A display's *primary* fetch is this wrapper; a second concurrent fetch on the same node holds a rotation of its own, which is why the primitive is the thing that exists and this is the thing built on it (ADR-054 §1, the one section ADR-105 keeps).<br><br>It is lent this display's `statusWindow`, so the fetch takes a slot on the one field rather than opening a second window over it — the whole point of `StatusReporter`.</span> | [FetchMixin](../fetchmixin#volatile-fetchrotation) |

## Getters

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="getter-viewsignature">**viewSignature**</span><br><code>string &#124; undefined</code> | Overridable hook, the one freshness input a display supplies: the signature of what the current *view* calls for — its block set (`blockKeySignature`) plus any view-derived fetch tier, like HiC's binsize; both comparative views' region sets, zoom buckets and LOD tier. `undefined` means "not computable yet" (view unmeasured, a prerequisite header still in flight) and holds the fetch off.<br><br>Settings and the adapter are deliberately not the display's half: `currentFetchKey` below appends `rpcPropsCacheKey` and `adapterConfigKey`, so a field added to `rpcProps()` or a track re-pointed in the config editor invalidates held data structurally. HiC hand-folded one settings term in and would have silently missed the second; the comparative family folded the adapter in at its installer and compared without it at its export gate.<br><br>Default `undefined`, so a display that forgets the override never fetches and never exports — hung is diagnosable, stale ships wrong pixels. | KeyedFetchMixin |
| <span id="getter-datasuperseded">**dataSuperseded**</span><br><code>boolean</code> | Overridable hook (default false): the held data answers the key, but this display knows it is not what the screen will settle on — a dependent fetch of its own is still out, or a fetch input it writes itself has moved. The same hook `MultiRegionDisplayMixin` declares, for the same reason: the key compare is structurally blind to anything the display fetches outside its primary fetch, and an export sampling `svgReady` in that window paints the half-filled frame.<br><br>A term of `dataCurrent` and NOT of the skeleton's freshness gate, so it holds the export and never re-runs the primary fetch. It fails hung, not stale: a value that latches true parks `awaitSvgReady` on its backstop, so state only what a later commit is guaranteed to clear. | KeyedFetchMixin |
| <span id="getter-currentfetchkey">**currentFetchKey**</span><br><code>string &#124; undefined</code> | Key of the fetch the current view, settings and adapter call for — the display's `viewSignature` plus the serialized `rpcProps()` axis plus the adapter config. The fetch skeleton's freshness key: captured at issue, compared against the stamp above, and written to it at commit. | KeyedFetchMixin |
| <span id="getter-datacurrent">**dataCurrent**</span><br><code>boolean</code> | The shared freshness answer every foundation gives: data has been committed (`loadedFetchKey` is only ever written beside it), it was fetched for the current view and settings, and the display is not about to supersede it itself. A pan inside the loaded blocks stays current; a block entering, a tier step, a settings change or a `reload()` moves one side of the compare. **What the fetch autorun gates on is the same compare inside `installFetch`**, not this getter — the skeleton owns it so a reload can override it. This one is for the readers outside the fetch, the export gate above all. The per-region twin is `isCacheValid`: what decides a refetch, and deliberately not the whole freshness answer. | KeyedFetchMixin |
| <span id="getter-isloading">**isLoading**</span><br><code>boolean</code> | <span data-pagefind-ignore>true while a fetch is active</span> | [FetchMixin](../fetchmixin#getter-isloading) |
| <span id="getter-isloadingorcanceled">**isLoadingOrCanceled**</span><br><code>boolean</code> | <span data-pagefind-ignore>`isLoading` widened to cover a user-canceled load. **This, not `isLoading`, is what a `displayPhase` loading term wants.** `cancelFetchByUser` clears the stop token synchronously, so `isLoading` goes false the instant the user clicks Cancel — and the loading overlay that unmounts on it is carrying the Retry button, which is the only way back: the state is deliberately durable, so no autorun restarts the fetch on its own. A bare `isLoading` therefore reads as `ready` over a display that is stopped, empty and offering nothing.<br><br>Arc read `isLoading` directly and had exactly that hole. It is a getter here so no family has to remember the second term.</span> | [FetchMixin](../fetchmixin#getter-isloadingorcanceled) |
| <span id="getter-fetchinert">**fetchInert**</span><br><code>boolean</code> | <span data-pagefind-ignore>Overridable hook (default false): the states where this display deliberately never fetches, so it holds no data and none is coming. Sequence sets it past base resolution ("Zoom in to see sequence"); LD sets it with the triangle toggled off.<br><br>**One hook, three readers**, and that is the whole point — a display that grows such a state has one thing to say rather than three, and the reader it would have forgotten is always the one outside itself:<br><br>- the loading scrim (`computeLoadingTerm`), which otherwise parks over the placeholder, permanently once a cancel has been clicked; - the SVG export (`computeSvgReady`'s `extraTerminal`), whose `awaitSvgReady` is an unbounded `when`, so one such display hangs the whole view's export; - the retry contract check (`makeRetryContractCheck`), which would otherwise report a dead Retry on a display correctly declining to load anything.<br><br>It was three hooks — `loadingSuppressed`, `svgReadyExtraTerminal` on each of the two foundations, and `fetchInert` on the comparative family, which had already collapsed them. Both LGV displays that override it returned one expression for all three, and one of the three was hard-coded `false` on the global family for a while, which is how LD came to be able to express only half its own state. One declaration for all three fetch families since the comparative one composed this mixin (ADR-105), so the retry check reads one field everywhere. ADR-082.<br><br>A hook rather than a `displayPhase` override, because overriding the getter means restating the whole loading condition — which is how sequence came to hold a verbatim copy of the other terms, one `git blame` away from silently missing the next one added.<br><br>It lives **here** because this is the one mixin all three display foundations compose. Same argument, one level down, that put `rendersCanvas` on `RenderLifecycleMixin` beside `canvasDrawn`.</span> | [FetchMixin](../fetchmixin#getter-fetchinert) |
| <span id="getter-awaitingprerequisite">**awaitingPrerequisite**</span><br><code>boolean</code> | <span data-pagefind-ignore>Overridable hook (default false), read only by the retry contract check (`makeRetryContractCheck`): "this run declined because a prerequisite fetch in another autorun has not landed, and its arrival wakes this one again". It **defers** the retry verdict to that later run rather than waiving it, so a display cannot spend its retry on a decline it called preliminary.<br><br>Two displays say it, one per fetch foundation, which is why it lives beside `fetchInert` rather than on either: HiC's contacts fetch declines until `CoreGetInfo` lands, and `MultiSampleVariantBaseModel`'s `fetchNeeded` declines until `sourcesBase` does. Both have a `reload()` that wakes the prerequisite's autorun as well as their own.<br><br>**It has to be strictly narrower than the gate it explains.** One that restates the gate's negation makes every decline a deferred one, so no run is ever judged and the display has silently opted out — an exemption by another name. HiC is in that shape deliberately, because its gate and its prerequisite are one condition; what covers its retry instead is `LinearHicDisplay/infoFetchFailure.test.ts`.<br><br>Not for a display deliberately not fetching at all — that is `fetchInert` above, which the loading scrim and the export read too.</span> | [FetchMixin](../fetchmixin#getter-awaitingprerequisite) |
| <span id="getter-awaitingdependentdata">**awaitingDependentData**</span><br><code>boolean</code> | <span data-pagefind-ignore>Overridable hook (default false), read by `computeLoadingTerm`: a load this display depends on beyond its primary fetch has not landed for the first time, so the frame the primary fetch calls current is still missing something. Multi-way synteny says it until its lane genes and lane links first arrive, so an export or a capture never lands between the ortholog fetch and the gene models that fill the lanes.<br><br>A hook rather than a `displayPhase` override, for the reason `fetchInert` is one: that display carried the override, restating the foundation's two arguments verbatim to append one term, which is the shape that silently misses the next term added.<br><br>Not `dataSuperseded`, which holds the export through every later refetch too: a display saying this wants the scrim on the first landing only, since later lane fetches redraw over lanes already on screen.</span> | [FetchMixin](../fetchmixin#getter-awaitingdependentdata) |
| <span id="getter-rpcpropscachekey">**rpcPropsCacheKey**</span><br><code>string</code> | <span data-pagefind-ignore>The RPC cache key both fetch foundations invalidate on: this display's `rpcProps()` payload serialized to a string. `serializeRpcProps` owns the why, including the silently-dead-axis corollary.<br><br>Here, beside the two hooks above, for the same reason they are: it describes the display, and every foundation composes this mixin. The per-region family watches it from `SettingsInvalidate` and the global one from its fetch autorun's trigger list — one getter and one name, so the two cannot come to invalidate on different axes. The global side built its own local `computed` over the same function until 2026-08, which was the same value under a second spelling.</span> | [FetchMixin](../fetchmixin#getter-rpcpropscachekey) |
| <span id="getter-adapterconfigkey">**adapterConfigKey**</span><br><code>string</code> | <span data-pagefind-ignore>The adapter axis of the same key, watched by the same two readers as `rpcPropsCacheKey`: `SettingsInvalidate` per-region and the global family's `currentFetchKey`. A track re-pointed in the config editor is a different fetch, and until 2026-09 only the comparative family said so.</span> | [FetchMixin](../fetchmixin#getter-adapterconfigkey) |

## Actions

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="action-commitfetchresult">**commitFetchResult**</span><br><code>(commit: () =&gt; void, key: string) =&gt; void</code> | The commit half of a keyed fetch: run the display's own store in the same transaction as the key stamp, so no observer can see fresh data under a stale key or the reverse. Being the only writer of `loadedFetchKey` is what makes `dataCurrent` derivable — a display cannot commit without stamping. | KeyedFetchMixin |
| <span id="action-reload">**reload**</span><br><code>() =&gt; void</code> | `FetchMixin.reload` (error, cancel, counter — the shared skeleton's reload epoch is what makes that bump override the freshness gate, even against a fetch that commits mid-reload, so nothing here has to remember to invalidate for the retry's sake) plus this layer's one addition, for the export gate rather than the refetch: dropping the loaded key sends `dataCurrent` false, so an export started after the click waits for the refetch instead of capturing what the retry is about to replace. The data itself survives, staying on screen under that overlay. A subclass whose reload needs extra teardown can override and chain. | KeyedFetchMixin |
| <span id="action-seterror">**setError**</span><br><code>(error?: unknown) =&gt; void</code> |  | [FetchMixin](../fetchmixin#action-seterror) |
| <span id="action-setstatusmessage">**setStatusMessage**</span><br><code>(status?: RpcStatus &#124; undefined) =&gt; void</code> | <span data-pagefind-ignore>Unthrottled: a display writing a phase label by hand must see every write land. The high-frequency RPC stream is thinned one level up, by the streams `self.statusWindow` hands out — and aggregated across them, which a write by hand is not.</span> | [FetchMixin](../fetchmixin#action-setstatusmessage) |
| <span id="action-stopactivefetch">**stopActiveFetch**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>Abort the in-flight fetch (if any) and retire its slot. The shared preamble of both cancel paths; the difference between them is only what they do to `fetchCanceled` / `fetchGeneration` afterward.</span> | [FetchMixin](../fetchmixin#action-stopactivefetch) |
| <span id="action-openstatusstream">**openStatusStream**</span><br><code>(isCurrent: () =&gt; boolean) =&gt; StatusStream</code> | <span data-pagefind-ignore>Open one operation's slot on the display's status field: an RPC `statusCallback` throttled through the display-wide window and guarded so a callback that fires after the node is torn down (RPCs resolve their status stream asynchronously) is a safe no-op, plus the `clear` that retires the slot when the operation ends.<br><br>**Every operation on the display opens one**, and the two come back together because an operation that never retires goes on voting for a phase that is over. The viewport fetch (`runFetch`), the clustering run and a lent `createStopTokenRotation` are three of them on one field; before ADR-081 each blanked the field outright and the last one to finish decided what the other two were still saying.<br><br>`isCurrent` is required and has no "node is alive" default, because alive is not the interesting question: a *superseded* fetch is on a live node, and its late status repainting the overlay of the fetch that replaced it is the failure this guards. `runFetch` passes `!isStale()`, which is what every display gets for free through `ctx.statusCallback`; a caller outside a fetch (the clustering autorun) passes its own run's flag. Defaulting to `isAlive` made the loose answer the easy one and five displays took it.<br><br>`runFetch`'s own slot is opened by the rotation; this is for an operation outside any fetch, the tree sidebar's clustering run.</span> | [FetchMixin](../fetchmixin#action-openstatusstream) |
| <span id="action-cancelfetch">**cancelFetch**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>cancel any in-flight fetch and bump fetchGeneration (always bumps, so callers can retrigger fetch autoruns even when nothing was in flight). This is the *internal* reset `clearAllRpcData` runs — it clears any user-cancel flag so the retrigger actually re-fetches.</span> | [FetchMixin](../fetchmixin#action-cancelfetch) |
| <span id="action-cancelfetchbyuser">**cancelFetchByUser**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>User-initiated cancel from the loading overlay. Stops the in-flight fetch and lands in a durable `fetchCanceled` state. Unlike `cancelFetch`, it does NOT bump fetchGeneration — so the fetch autoruns don't immediately restart the load. The user retries via `reload` (the overlay's retry button), or it clears on the next viewport change.</span> | [FetchMixin](../fetchmixin#action-cancelfetchbyuser) |
| <span id="action-beforedestroy">**beforeDestroy**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>Release an in-flight fetch's stop token on teardown. Without this, a display destroyed mid-fetch (track/view closed while loading) never signals the worker to abort the now-useless work, and its in-flight HTTP reads keep downloading. MST auto-chains lifecycle hooks, so a composing display can still define its own beforeDestroy.</span> | [FetchMixin](../fetchmixin#action-beforedestroy) |
| <span id="action-beginfetch">**beginFetch**</span><br><code>(stopToken: StopToken) =&gt; void</code> | <span data-pagefind-ignore>The `onBegin` half of a fetch's bookkeeping: publish the in-flight token (`isLoading`) and clear the durable user-cancel — a load starting is the single clear point that covers every retrigger path (reload, viewport change, settings invalidate). An action of its own for the same reason `endFetch` is: `installFetch`'s lifecycle callbacks run outside any MST flow this mixin owns.</span> | [FetchMixin](../fetchmixin#action-beginfetch) |
| <span id="action-endfetch">**endFetch**</span><br><code>(current: boolean) =&gt; void</code> | <span data-pagefind-ignore>The `finally` half of `runFetch`'s bookkeeping, an action of its own because `runFetchOnce`'s `finally` resumes on a microtask the flow does not own — a direct volatile write there is outside the action context, which is the one thing hoisting the sequence into a shared function costs. The stale branch is a superseded fetch, which must not clear the loading flag the run that replaced it just set. The stop token itself is released by the rotation's own `end()`, one layer down.</span> | [FetchMixin](../fetchmixin#action-endfetch) |
| <span id="action-runfetch">**runFetch**</span><br><code>(work: (ctx: FetchContext) =&gt; Promise&lt;void&gt;) =&gt; Promise&lt;void&gt;</code> | <span data-pagefind-ignore>Run a cancel-safe fetch (cancels any prior). The work callback gets a FetchContext with a stopToken to forward to the RPC and an isStale() check to short-circuit commits once the user has moved on.<br><br>**The MST-flow wrapper over the shared `runFetchOnce` sequence**, and only the wrapper: the begin/clear/run/commit/error/end order, and the rules that keep a superseded run from writing back, are the same function every other fetch in the tree runs. What this adds is the observable bookkeeping a display needs — `isLoading` through `activeStopToken`, `fetchGeneration`, the user-cancel clear — and the flow itself, which is an action, so `work`'s synchronous prefix runs untracked wherever a fetch autorun calls this.</span> | [FetchMixin](../fetchmixin#action-runfetch) |

