Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    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}/{base64-encoded-key}

    Structure (everything under the viewer's luxar/ OPFS namespace dir, see opfs-store/opfs-root.ts):

    luxar/zarr-cache-{hash}/
    ├── 00/ (~250 files per bucket)
    │ ├── UG9pbnRz...
    │ └── ...
    ├── 01/
    ├── ...
    ├── ff/
    └── _cache_meta.json

    Benefits:

    • Max ~250-500 files per directory instead of 65,000+
    • Only 256 bucket handles to cache (trivial memory)
    • clear() iterates 256 directories, not 65,000 files
    • Preserves fast flat access (2 async calls vs 1)
    Index
    opfsRoot: FileSystemDirectoryHandle | null = null
    index: Map<string, { size: number; order: number }> = ...
    orderCounter: number = 0
    totalSize: number = 0
    maxSize: number
    datasetId: string
    baseUrl: string
    contentHash: string | null = null
    validationMode: CacheValidationMode = 'none'
    lastValidatedAt: number | null = null
    readCount: number = 0
    writeCount: number = 0
    missCount: number = 0
    canceledReadCount: number = 0
    oversizedWriteSkipped: number = 0
    quotaWriteSkipped: number = 0
    evictions: number = 0
    writeFailures: number = 0
    corruptedEntries: number = 0
    clobberBarrierWriteSkipped: number = 0
    buckets: OPFSBucketCache = ...
    metadata: OPFSMetadataManager = ...
    METADATA_SAVE_DELAY: 1000
    pendingWrites: Map<string, Promise<void>> = ...
    pendingDeletesByDataset: Map<string, Map<string, Set<Promise<void>>>> = ...
    generation: number = 0
    disposed: boolean = false
    initialized: boolean = false
    pendingInit: Promise<void> | null = null
    pendingClear: Promise<void> | null = null
    pendingDispose: Promise<void> | null = null
    consecutiveTimeouts: number = 0
    breakerTripped: boolean = false
    • Run 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.

      Type Parameters

      • T

      Parameters

      • promise: Promise<T>
      • label: string

      Returns Promise<T>

    • Disable 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().

      Parameters

      • label: string

      Returns void

    • Enumerate 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.

      Returns Promise<CachedDatasetSummary[]>

    • Prove 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.

      Returns Promise<void>

    • Get a file from OPFS and update LRU order.

      Parameters

      • key: string
      • Optionaloptions: { signal?: AbortSignal }

      Returns Promise<Uint8Array<ArrayBufferLike> | undefined>

    • 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.

      Parameters

      • key: string

      Returns Promise<void>

    • Real (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.

      Parameters

      • key: string

      Returns Promise<void>

    • First 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.

      Parameters

      • exclude: string

      Returns string | undefined

    • 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).

      Returns Promise<void>

    • Get cache statistics.

      Returns {
          size: number;
          count: number;
          reads: number;
          writes: number;
          misses: number;
          canceledReads: number;
          activeReads: number;
          queuedReads: number;
          oversizedWriteSkipped: number;
          quotaWriteSkipped: number;
          evictions: number;
          writeFailures: number;
          corruptedEntries: number;
          clobberBarrierWriteSkipped: number;
          metadataParseFailures: number;
          orphanedFilesRemoved: number;
          available: boolean;
          breakerTripped: boolean;
      }

      • size: number
      • count: number
      • reads: number
      • writes: number
      • misses: number
      • canceledReads: number
      • activeReads: number
      • queuedReads: number
      • oversizedWriteSkipped: number
      • quotaWriteSkipped: number
      • evictions: number
      • writeFailures: number
      • corruptedEntries: number
      • clobberBarrierWriteSkipped: number
      • metadataParseFailures: number
      • orphanedFilesRemoved: number
      • available: boolean

        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.

      • breakerTripped: boolean

        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.

    • 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.

      Parameters

      Returns void

    • 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:

      1. Set disposed = true SYNCHRONOUSLY, before the first await. dispose() itself suspends below (drain + awaitInFlight), and every 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.
      2. Bump generation so any in-flight doSet that resolves afterwards detects the mismatch and skips its index update.
      3. Cancel the debounced timer (we flush directly below).
      4. Await any in-flight init() or clear(). Their disposed guards stop FURTHER mutations, but an OPFS operation already initiated at an await cannot be cancelled — dispose() resolving is the signal that a newer same-URL store may take over the shared directory, so nothing this store started may still be outstanding at that point.
      5. Drain pendingWrites so file I/O for in-flight set() calls finishes and the index reflects settled state before we snapshot it. 5b. Drain in-flight real deletes (pendingDeletesByDataset): doDelete chains behind pendingWrites, so a timed-out delete's non-cancellable removeEntry + reconcile can still be outstanding — draining lets it finish before we snapshot (#1073). Bounded by the op timeout; a delete that outlives the drain stays in the static registry, which is what actually protects a successor same-URL store's writes.
      6. Await any metadata save already mid-write so the final save wins on disk (last writer), then write the latest snapshot. The flush is unconditional (not gated on hasPendingSave) because read-driven LRU order no longer schedules its own save (see touch()); dispose is where a read-only session's order gets persisted. Deadline-bounded: a stalled backend must not leave dispose() unresolved.

      Returns Promise<void>