PrivateopfsPrivateindexPrivateorderPrivatetotalPrivatemaxPrivatedatasetPrivatebasePrivatecontentPrivatefilePrivatevalidationPrivatelastPrivatereadPrivatewritePrivatemissPrivatecanceledPrivateoversizedPrivatequotaPrivateevictionsPrivatewritePrivatecorruptedPrivateclobberPrivatebucketsPrivatemetadataPrivate Static ReadonlyMETADATA_Private Static ReadonlyMETADATA_Private Static ReadonlyMETADATA_Private StaticliveStatic ReadonlyORPHAN_Per-open budget of the orphan reconcile: at most this many unindexed files are re-indexed or deleted per session (the crawl resumes next session). Bounded so a large backlog cannot turn a session's first seconds into a disk sweep; with the index saved at least every METADATA_SAVE_MAX_WAIT the backlog one interrupted session leaves is small.
Static ReadonlyORPHAN_Per-open cap on file names the orphan crawl examines, orphan or not.
PrivatependingPrivaterecoveryPrivateprobePrivateorphansPrivatereconcilePrivateidentityPrivateidentityPrivate Static ReadonlyQUOTA_Private Static ReadonlyQUOTA_PrivatequotaPrivatequotaPrivatequotaPrivatependingPrivate StaticpendingPrivategenerationPrivatedisposedPrivateinitializedPrivatependingPrivatependingPrivatependingPrivateconsecutivePrivatebreakerPrivatetimedRun one OPFS operation under the per-op timeout, feeding the
circuit breaker. Counts ONLY the OPFS timeout rejection: any
other settlement (success or a fast error such as NotFoundError)
proves the backend is responsive and resets the count — the
breaker targets systemic stalls, not error rate. Applied to the
three hot real-I/O sites (get/set/delete) only: the init canary's
catch already degrades the store itself, the delete-barrier awaits
a delete whose own timeout already counted, and the dispose drain
runs on an already-dead store.
PrivatetripDisable the L2 tier for the rest of this store's lifetime after
repeated consecutive timeouts. Nulling opfsRoot reuses the
init-failure degradation path — every entry point already
short-circuits on it, so all later ops are instant no-ops instead
of serial full-timeout burns, and getStats().available flips the
existing opfs-unavailable badge. The pending debounced metadata
save is cancelled so its timer cannot fire a full-index write
against the hung backend and stall dispose().
StaticlistEnumerate every zarr-cache-* dataset present under the viewer's
luxar/ OPFS namespace directory. Reads each dataset's
_cache_meta.json directly — no OPFSStore instance is created. Used by
the debug-cache helpers (window.__luxarDebug) and the cache E2E suite
to inspect persisted datasets without mounting them.
Never creates the namespace directory: on a cold origin (no luxar/
yet) the lookup's NotFoundError resolves to an empty list.
Initialize OPFS directory, prove writability, and load metadata.
PrivatedoPrivateprobeProve OPFS is actually WRITABLE, not merely mounted. WebKit (Safari and
the WKWebView native launcher) implements navigator.storage .getDirectory() and directory/file handles but NOT the main-thread
FileSystemFileHandle.createWritable() — WebKit supports OPFS writes
only through worker-side createSyncAccessHandle. Without this probe
the store mounts "healthy" there and then fails EVERY put: tens of
thousands of write errors, a permanently empty L2, and an all-miss read
path (observed in the native macOS app's cache monitor). One tiny probe
write at init converts that failure mode into the ordinary
OPFS-unavailable degradation (L1-only, opfs-unavailable badge) with a
single clear warning. Timeout-wrapped like every other OPFS operation so
a hung handle cannot stall startup. Throws on failure — init()'s catch
nulls opfsRoot.
PrivateapplyPrivateisA key some in-flight or completed operation of THIS store owns.
PrivatereconcileReconcile chunk files the persisted index does not list (see
opfs-store/orphan-reconcile.ts). Each orphan is RE-INDEXED when its name
decodes to a key that hashes to the bucket it sits in, a recovery hash is
set (recoveryHash), the file is non-empty, and it fits under maxSize;
otherwise it is DELETED. Recovered entries join the LRU head (oldest), so
they are the first evicted. Bounded per session by
ORPHAN_RECONCILE_MAX_ACTIONS. The end of the crawl disarms the
unindexed-read probe (probeUnindexed).
Runs concurrently with normal traffic, so every action re-checks against the live state: a key this store is writing or has indexed is never touched, a delete is registered in the same-key delete barrier a later set() waits on (#1073), and a clear()/dispose() (generation bump) or a breaker trip stops the crawl and discards anything not yet merged.
PrivateorphanByte size of an orphan file, or null when it cannot be read in time.
PrivateremoveDelete one orphan file. A decodable key's removal is registered in the same-key delete barrier, so a set() of that key arriving mid-removal waits for it instead of being clobbered by it (#1073).
PrivatemergeFold recovered orphans into the index as its LRU head. Synchronous, so it
is atomic against every other index mutation; a key indexed meanwhile
(a concurrent write) keeps its live entry. Writes during the crawl may
have used the room it budgeted, so a recovery that no longer fits under
maxSize is skipped (its file waits, unindexed, for the next reconcile).
Test/diagnostic hook: resolves once this session's background orphan reconcile has finished (immediately when none is running).
PrivatemetadataPrivategetget() of a key the index lists (anything else is a miss here).
Optionalsignal: AbortSignalPrivatecanWhether a key the index does not list may be read from its file: recovery
is armed (probeUnindexed) and nothing of this store is writing or
deleting that file right now.
Optionalsignal: AbortSignalPrivategetget() for a key the index does not list, while unindexed files may still
be on disk (see probeUnindexed). A key's file path is a pure function of
the key, so the file is read where a write would have put it; a hit is
indexed (the index never recorded it: a reload landed before the save) and
served, a NotFound is an ordinary miss. Gated on the same validated hash as
every indexed entry, and discarded if a clear() ran meanwhile.
Optionalsignal: AbortSignalPrivatereadPrivateadoptIndex a file found by an unindexed read, as the MRU entry. One that would
push the index past maxSize is served but left unindexed, for the
reconcile to decide (it deletes what does not fit).
PrivatereadableOptionalsignal: AbortSignalPrivatecountOptionalsignal: AbortSignalPrivatehasWrite a file to OPFS with LRU eviction.
PrivatedoInternal write implementation (called by set() after serialization).
Delete a file from OPFS.
The CALLER await is bounded by opfsOperationTimeoutMs (preserving #991 —
delete() runs inside doSet()'s eviction loops and get()'s corrupted-entry
path, none of which may stall). The chain LINK a later same-key write waits
on, however, is the REAL (un-timed-out) removeEntry settlement (doDelete),
registered in pendingDeletesByDataset: when the caller returns on timeout the
non-cancellable removeEntry keeps running, and a replacement write must not
start until it has ACTUALLY settled (#1073 clobber invariant). Index/size
are reconciled off that real settlement, never off the early timeout return.
PrivateregisterRecord an in-flight REAL removal of key's file in the static same-key
delete registry (see pendingDeletesByDataset); self-cleans on settlement.
real must never reject.
PrivatedoReal (un-timed-out) delete of a single file plus index/size reconcile. Runs to ACTUAL settlement as a link in the per-key serialization chain so a later same-key write can never start (and then be clobbered) while this removeEntry is still in flight (#1073). Never rejects.
PrivatelruFirst key in LRU order (Map insertion order) that is not exclude. Used by
doSet()'s eviction loops to skip the key currently being written (#1073) —
see the loop comments for the self-deadlock rationale.
Clear all OPFS data for this dataset.
Uses atomic delete-and-recreate instead of iterating entries, which avoids race conditions when a previous page context still holds open file handles (e.g., quick-succession page refreshes with fire-and-forget L2 writes).
PrivatedoCheck if enough storage quota is available (10% safety margin).
Served from the cached estimate while it is fresh and its debited headroom
covers the write; otherwise re-estimates (see quotaEstimate).
PrivatedebitAccount bytes this store put on (positive) or took off (negative) disk
against the cached quota estimate. grossBytes counts toward the
re-estimate trigger.
Get cache statistics.
Unindexed files recovered into the index (open-time reconcile or a first read).
S2: true when OPFS was reachable on init and the store is
still alive. false when init couldn't acquire a directory
handle (browser without OPFS support, private mode in some
configs), after the circuit breaker trips, or after dispose().
Drives the opfs-unavailable status badge.
true once the circuit breaker disabled the tier after
consecutive OPFS timeouts. Distinguishes "gave up after repeated
stalls" from "never had OPFS" in debug snapshots and tests; the
monitor badge keys on available alone.
Update LRU order for a key.
Set content hash for cache invalidation.
PrivateadoptMake hash the content hash. Index entries were written under the old
hash's file tag, so a different tag retires them: the index empties (their
files become orphans the next reconcile deletes) and the generation bump
discards any write still in flight under the old tag. In practice the index
is already empty here — a hash change goes through clear() — except for
entries written while no hash was known.
PrivatestopRecover no unindexed file from here on (the reconcile deletes them).
PrivatepersistPut hash in the identity file, chained behind any earlier identity write.
Chunk writes wait for it (doSet). A failed write removes the file instead,
so a stale identity can never vouch for chunks written under a new hash.
Get cached content hash.
Record the validation mode used for this dataset. Persisted to
_cache_meta.json so a follow-up session can re-evaluate (e.g.
a TTL window).
validated (default true) means a genuine validation just succeeded,
which stamps lastValidatedAt = now. The no-token/offline branch passes
validated: false: it still records the mode, but must NOT slide the TTL
clock forward on every revisit — it only establishes the baseline the
first time (when lastValidatedAt is still unset) so a headerless-server
cache can still age out.
Optionaloptions: { validated?: boolean }Read the current validation mode and last-validated timestamp. Returned together so callers can apply a TTL check atomically.
PrivatecompactRenumber all order entries to prevent orderCounter overflow. Called when orderCounter exceeds a safe threshold (1e12).
Tear down the store. Marks the store disposed, drains pending writes, awaits any in-flight metadata save, then flushes a final snapshot so a read-only session's LRU order still gets persisted.
Every caller shares ONE completion (pendingDispose): dispose() resolving is the take-over signal for a newer same-URL store, so a concurrent or repeat caller must wait for the same drain rather than resolve early on the disposed flag.
Order matters:
disposed
guard — init()'s re-checks, clear(), the validation setters — must
observe the flag from the moment dispose() is invoked, not only once
the final flush completes; otherwise a mid-init store could keep
probe-writing or orphan-cleaning the shared directory during that
window. The final flush below calls the metadata manager directly, so
it is unaffected by the flag.PrivatedoStart any pending (debounced) index save NOW. Best-effort and non-blocking: the write is started synchronously and not awaited, because the page may be frozen or killed right after the lifecycle event that asks for it. A no-op when nothing is pending or the store is not live.
StaticflushflushMetadata for every live store. Wired to pagehide and
visibilitychange → hidden (core/app/lifecycle/unload-handling.ts): the
signals that still fire when beforeunload (and therefore dispose) does
not — mobile tab switches, bfcache navigations, discarded tabs.
Privateschedule
OPFS persistence layer (L2 cache) with LRU eviction and shallow bucketing.
Uses 256 bucket directories to distribute files and avoid filesystem limits. Files are stored as:
{bucket}/{hash tag}.{base64-encoded-key}(seehashTag)Structure (everything under the viewer's
luxar/OPFS namespace dir, seeopfs-store/opfs-root.ts):Persistence contract (see the cache README, "L2 index persistence"): a chunk file whose write completed is served by the next session of the same dataset even if no index save recorded it.
_cache_identity.jsonholds the content hash and is written before any chunk under that hash; a lookup of a key the index does not list reads the key's deterministic path while the open-time reconcile, which re-indexes or deletes the files the index never recorded, runs.Benefits: