Documentation / @etherfold/browser / BrowserStateStoreConfig
Type Alias: BrowserStateStoreConfig ​
BrowserStateStoreConfig = {
backend?:"indexeddb";databaseName?:string;finalityDepth?:number;oneTransactionAtATime?:boolean;retention?:RetentionSetting; } | {backend:BrowserStateStoreFactory; }
Defined in: packages/browser/src/storage/state-store/BrowserStateStore.ts:127
What a browser deployment says about where its state lives.
The two arms are deliberately exclusive: the options below configure the DEFAULT backend, and a store a host builds itself has already been configured by the host. An option that silently did nothing would be worse than a type error.
Union Members ​
Type Literal ​
{ backend?: "indexeddb"; databaseName?: string; finalityDepth?: number; oneTransactionAtATime?: boolean; retention?: RetentionSetting; }
backend? ​
optionalbackend?:"indexeddb"
Versioned rows in IndexedDB. The default, and what ADR-0024 decided.
databaseName? ​
optionaldatabaseName?:string
The IndexedDB database to keep this state in. Defaults to etherfold-state.
It is the IDENTITY of the state: several tabs of one app share it (which is the point), and two unrelated indexers in one origin must not.
finalityDepth? ​
optionalfinalityDepth?:number
The reorg depth this deployment protects against, in block numbers.
Required beside a {blocks: N} window, and load-bearing beside revert-only, where it is the whole of what that store keeps and therefore its prune floor.
oneTransactionAtATime? ​
optionaloneTransactionAtATime?:boolean
Never hold two IndexedDB transactions open at once. Off by default, and a workaround for a WebKit defect rather than a correctness setting.
On WebKit -- measured on an iPhone 12 running Safari 18.3.1, and on a recent upstream build -- a worker that ends while it has BOTH a readwrite and a readonly transaction in flight can wedge the whole database PERMANENTLY: indexedDB.open keeps succeeding and every transaction after it hangs for ever, in the tab as much as in a replacement worker, across a reload and a new tab, with deleteDatabase blocked. Since a browser may evict a worker at any time (ADR-0082), an app that folds in a worker can lose its local index with no recovery but a different database name. A worker that never holds two transactions at once did not reproduce it in 1,000 runs.
It is not free, and it buys nothing on Chromium or Firefox. A single-request read costs a third to nearly double what it did on every engine, because the commit round trip is the same size as the read; a listing barely moves, because its requests share one transaction. The numbers are in work/notes/findings/webkit-does-not-abort-a-terminated-workers-indexeddb-transaction.md.
Nothing decides this for you, deliberately: inside a worker a WebKit engine cannot be identified at all. Decide it in the TAB, where navigator.vendor === 'Apple Computer, Inc.' and 'GestureEvent' in window both identify WebKit (checked on an iPhone against Safari, Chrome for iOS and Firefox for iOS -- all three are WebKit, all three are caught), and pass the answer to the worker that builds the store.
retention? ​
optionalretention?:RetentionSetting
How far back superseded versions are kept, in BLOCK NUMBERS. Defaults to unbounded.
A window is {blocks: N} and needs finalityDepth beside it, since a window below the depth a reorg can reach would prune the versions the revert itself needs. Note the trap recorded in ADR-0019: N BLOCKS is not N updates of history -- on the real measured stream, event-bearing blocks are median 429 blocks apart.
What is set here is also what is RECLAIMED, and a caller has nothing further to schedule: createIndexerState's cycle prunes the state of every generation it holds, so a store that states a FLOOR physically drops the versions below it instead of only refusing to answer about them. A floor is what {blocks: N} states and what revert-only states ALONGSIDE a finalityDepth (that kind keeps superseded versions exactly as long as reorg revert needs them, and the depth is how long that is); unbounded, and revert-only with no depth, state none and nothing is ever dropped.
Reorg safety and NO history: write revert-only, not a small window ​
revert-only is how this configuration says "survive a reorg, keep no history", and it is the setting to reach for when that is what the app wants. It is a retention value like any other, so it is available on this default IndexedDB backend and not only on the light store that is revert-only by construction (ADR-0023).
It also SAYS so, before any read: a revert-only store reports capabilities.asOf === false, so an app that turns out to need history learns it at startup from the store's own report rather than from a refused read halfway through a session. (asOf is reported separately from retention because the two fail differently: a window answers inside itself and refuses outside it, while a store that reconstructs no history refuses everywhere.)
A small window is not a substitute for it, and the reason is measured rather than stylistic. Retention is counted in BLOCK NUMBERS and never in updates (ADR-0019), and on the real measured stream (the launched stratagems game on Base, work/notes/findings/sqlite-in-the-browser.md) event-bearing blocks are median 429 blocks apart, max 1,226,194. So {blocks: 64} typically holds exactly ONE of them, the tip's, and often none: what it buys is a store that refuses almost every historical read while looking configured for history. revert-only asks for the same thing honestly, and reports it.
What revert-only still guarantees is the reorg half. Revert keeps working, because its floor is the finality depth: the superseded versions a revert reopens are exactly the ones it keeps (retentionFloor). That is why finalityDepth belongs beside it -- it is the depth the revert is protected to, and the floor the scheduled prune deletes against.
Type Literal ​
{ backend: BrowserStateStoreFactory; }
backend ​
backend:
BrowserStateStoreFactory
Any other backend: a function from the processor's declarations to a store.
This is the whole of "choosing another backend is a configuration change": @etherfold/state-store-patch for a tab that only needs current state and reorg safety, MemoryStateStore in a test, or something a host wrote itself. What it may not be is a backend this package has to know about, which is what keeps the browser package from importing every store that exists.