Core Concepts

Data Ingestion Strategies

Not all data shrinks and grows the same way. A homepage is one object that gets replaced. A pricing table is a short list fetched in full. A library of articles is unbounded and needs a ceiling. Flux asks you to name which shape a channel has once, up front, so caching and eviction behave correctly for that shape without any extra configuration later.

Why the shape of your data has to be declared

A cache that doesn't know whether it's holding one object or an open-ended collection has to guess — and guessing wrong either means throwing away data that should have stayed, or holding on to data that should have been evicted. ingestionType exists so nothing has to be guessed. You state up front which of four shapes a channel's data has, and Flux applies the right storage and freshness behavior for that shape automatically.

This is a one-time decision per channel, made once at register() time — not something you manage on every read or write afterward.

1

SNAPSHOT — a single object

The simplest shape: one object that gets replaced wholesale every time it changes. A homepage payload, a site config document, a terms-of-service page — anything where there's exactly one current version and no notion of individual items within it. Whenever new data arrives, it fully overwrites what was cached before.
lib/flux.ts (excerpt)
1flux.register({
2 channel: 'homepage',
3 idbKey: 'homepage:latest',
4 ttl: 'long',
5 ingestionType: 'SNAPSHOT',
6 event: 'UPDATE',
7 store: homepageStoreAdapter,
8})
2

COLLECTION_ALL — a bounded list, fetched whole

A list with a known, reasonable ceiling — a services list, a team roster, a pricing table. The entire collection is fetched and cached as one array, and any update replaces the whole array rather than patching individual entries. This is the right shape whenever you know in advance the list won't grow into the thousands — if it might, that's a sign the data actually belongs under LRU instead.
3

LRU — an unbounded collection, capped by budget

Detail pages that accumulate without a natural ceiling — a library of articles, a catalog of products — can't be cached as one growing array without eventually filling up the device. LRU caches each item individually and holds the collection to a byte budget rather than an item count, evicting the least recently accessed entries first once that budget is reached. A visitor's device never fills up just because they browsed a lot of articles.
lib/flux.ts (excerpt)
1flux.register({
2 channel: 'blog_post_detail',
3 idbKey: 'blog_post_detail',
4 ttl: 'medium',
5 ingestionType: 'LRU',
6 event: 'UPDATE',
7 store: articleStoreAdapter,
8
9 // Optional — defaults to a 10MB budget if omitted.
10 cacheStrategy: {
11 type: 'LRU',
12 maxBytes: 15 * 1024 * 1024, // 15MB
13 },
14})
Budgets are set in bytes, not item counts

maxBytes is the field to set — it's a direct byte budget for the partition, not an approximation. If you only have an entry count in mind rather than a byte figure, maxEntries is accepted as a fallback and converted using a rough per-entry estimate, but that conversion exists purely for backward compatibility and isn't precise enough to plan storage around. If neither is set, Flux defaults to a 10MB budget, and whatever you configure is clipped to a 50MB ceiling regardless — mobile Safari in particular reclaims storage aggressively once an origin's usage spikes, so this ceiling exists to keep a misconfigured budget from becoming a real-device problem.

4

PAGINATED — large datasets with a virtual view window

For datasets too large to reasonably treat item-by-item even under an LRU budget — think a searchable table with tens of thousands of rows — PAGINATED is reserved as a fourth shape, built around a virtual view window rather than a full or per-item cache. This strategy is on the roadmap and not yet available — if your data currently needs this shape, LRU is the closest supported strategy in the meantime.
One StoreRegistration only ever declares one ingestionType — a single channel doesn't mix shapes. If a screen needs both a bounded list and an unbounded detail view (a product grid and its individual product pages, say), that's two separate registrations: one COLLECTION_ALL channel for the grid, one LRU channel for the detail pages it links to.

See ingestion/snapshot, ingestion/collection-all, ingestion/lru, and ingestion/paginated for a full field reference and worked example per strategy, and core-concepts/5-layer-stack for how the chosen strategy interacts with caching and eviction underneath.