Skip to content

2.3 Networking and Caching

coding man edited this page Nov 4, 2026 · 1 revision

Networking and Caching

Relevant source files

The following files were used as context for generating this wiki page:

This page details the networking abstraction, error handling, browser-side local storage caching, Electron disk caching, freshness/TTL policies, and offline behavior implemented in VariantScope. All network requests and cache operations route through a unified architectural layer that decouples browser execution from Electron desktop operations.


1. Unified HTTP Abstraction (src/core/http.js)

All network communication across VariantScope flows through getJson(), an abstraction layer defined in src/core/http.js:101-104. Depending on the runtime environment, getJson() automatically dispatches requests either via browser fetch() or through the Electron preload IPC bridge.

Key Components and Classes

  • HttpError: Custom error class extending Error src/core/http.js:10-17. It exposes a classification kind ("blocked", "offline", "http", "aborted", or "invalid") and an optional HTTP status code.
  • isElectron(): Detects whether the app is running inside the Electron preload bridge by inspecting window.api.fetchJson src/core/http.js:20-21.
  • abortable(): Wraps promises with an AbortSignal listener to immediately cancel in-flight requests when a search or operation is aborted src/core/http.js:24-32.

Browser vs. Electron Dispatch Paths

  1. Browser Path (viaBrowser): Checks the local storage cache unless noCache is true src/core/http.js:55-62. If missing, it performs a fetch() request with a 20-second timeout (TIMEOUT_MS) src/core/http.js:7-67 and up to one retry for rate limits (429) or server errors (>=500) src/core/http.js:65-92. Successful responses are written to the browser cache src/core/http.js:83.
  2. Desktop Path (viaElectron): Delegates network calls to the main process via window.api.fetchJson() src/core/http.js:35-51, routing requests through an allowlisted, CORS-free, rate-limited environment with native disk caching.
graph TD
    A["getJson(url, options)"] --> B{"isElectron()"}
    B -->|Yes| C["viaElectron(url, options)"]
    B -->|No| D["viaBrowser(url, options)"]
    C --> E["window.api.fetchJson()"]
    D --> F{"noCache?"}
    F -->|No| G["cacheGet(key)"]
    G -->|Hit| H["Return Cached Data"]
    F -->|Yes| I["fetch(url)"]
    G -->|Miss| I
    I --> J["cacheSet(key, data)"]
    J --> K["Return Fresh Data"]
Loading

Sources: src/core/http.js:1-104, tests/unit/http.test.js:1-134


2. Freshness and TTL Policy (src/core/freshness.js)

VariantScope separates data retention from data freshness. Cached records are never purged automatically due to age; instead, they are flagged as stale after a fixed Time-To-Live (TTL) duration.

  • OUT_OF_DATE_MS: Set to 7 * 24 * 60 * 60 * 1000 (7 days) src/core/freshness.js:3.
  • isOutOfDate(): Compares a given fetchedAt timestamp against the current system time using OUT_OF_DATE_MS src/core/freshness.js:6-8.
  • groupFor(): Normalizes and validates gene symbols (e.g., "tp53" -> "TP53") to ensure only safe, alphanumeric symbols are used as storage keys and file tags src/core/freshness.js:10-16.
graph TD
    A["isOutOfDate(fetchedAt)"] --> B["Calculate age: Date.now() - fetchedAt"]
    B --> C{"Age > OUT_OF_DATE_MS (7 days)?"}
    C -->|Yes| D["Flag entry as stale: stale = true"]
    C -->|No| E["Flag entry as fresh: stale = false"]
Loading

Sources: src/core/freshness.js:1-16, src/core/cache.js:1-109


3. Browser-Side Caching (src/core/cache.js)

In non-Electron environments (such as GitHub Pages or local web development), VariantScope uses an in-memory Map backed by browser localStorage src/core/cache.js:1-7.

Implementation Details

  • Storage Wrapper: storage() safely accesses window.localStorage inside a try/catch block to gracefully handle private browsing modes, storage quotas, or sandboxed frames src/core/cache.js:9-16.
  • Key Formatting: Stored API responses use prefixed keys structured as vs:http:<gene_tag>:<method>\n<url>\n<body> src/core/cache.js:5-6.
  • Group Listing and Deletion: cacheListGroups() scans localStorage and memory to compute byte sizes and entry counts per gene src/core/cache.js:56-80. cacheDeleteGroups() removes cached responses for specified gene tags without disturbing user preferences like themes or recent searches src/core/cache.js:82-104.
graph TD
    A["cacheGet(key, options)"] --> B["read(key)"]
    B --> C{"Found in memory?"}
    C -->|Yes| D["Return memory entry"]
    C -->|No| E["Read localStorage viaPREFIX + key"]
    E --> F{"Found in localStorage?"}
    F -->|Yes| G["Populate memory Map & return entry"]
    F -->|No| H["Return null"]
    D --> I{"Expired & !allowStale?"}
    G --> I
    I -->|Yes| J["Return null"]
    I -->|No| K["Return {value, savedAt, stale}"]
Loading

Sources: src/core/cache.js:1-109


4. Electron Disk Caching (electron/disk-cache.js)

In the desktop application, responses are persisted to disk in the user's userData folder through createDiskCache() electron/disk-cache.js:17.

File Layout and Atomicity

  • File Naming: Each cached response is stored in a separate JSON file named <gene_tag>~<hash>.json, where the hash is computed via SHA-256 over the request key electron/disk-cache.js:19-22. This allows gene groups to be listed, sized, and deleted efficiently without parsing file bodies.
  • Atomic Writes: set() writes data atomically by outputting to a temporary file (<file>.<pid>.tmp) and performing an atomic rename() operation, preventing half-written files during crashes electron/disk-cache.js:46-52.
  • Group Management: listGroups() inspects file stats to aggregate byte sizes and entry counts per gene electron/disk-cache.js:55-68, while deleteGroups() targets specific gene tags or performs a full cache wipe ("all") electron/disk-cache.js:70-86.
graph TD
    A["cache.set(key, data, status, group)"] --> B["Ensure directory exists: mkdir(dir)"]
    B --> C["Compute SHA-256 hash of key"]
    C --> D["Generate temp file path: <file>.<pid>.tmp"]
    D --> E["Write JSON {fetchedAt, status, data}"]
    E --> F["Atomic rename temp file to <group>~<hash>.json"]
Loading

Sources: electron/disk-cache.js:1-93, tests/unit/disk-cache.test.js:1-96


5. Electron IPC and Preload Bridge (electron/preload.cjs)

Communication between the sandboxed renderer process and the Node.js main process is strictly restricted through a context bridge electron/preload.cjs:1-2.

Exposed API Surface

The preload script exposes exactly six validated functions on window.api electron/preload.cjs:12-69:

  1. fetchJson(url, options): Invokes the fetch-json IPC channel after validating URL and option boundaries electron/preload.cjs:14-32.
  2. saveFile(kind, suggestedName, data): Triggers native file save dialogs for CSV exports or PNG snapshots electron/preload.cjs:35-42.
  3. openExternal(url): Opens allowlisted external URLs in the system browser electron/preload.cjs:44-48.
  4. listCache(): Queries disk cache metrics grouped by gene electron/preload.cjs:50-53.
  5. deleteCache(target): Purges disk cache entries for specific gene arrays or "all" electron/preload.cjs:55-63.
  6. getAppInfo(): Retrieves application version, platform, and architecture details electron/preload.cjs:65-68.

Input parameters are rigorously guarded using requireString() to enforce string types and maximum length constraints electron/preload.cjs:5-10.

graph TD
    A["Renderer Process (src/core/http.js)"] -->|"window.api.fetchJson()"| B["Preload Bridge (electron/preload.cjs)"]
    B -->|"ipcRenderer.invoke('fetch-json')"| C["Main Process (electron/fetcher.js)"]
    C -->|Disk Cache lookup| D["Disk Cache (electron/disk-cache.js)"]
    D -->|Cache Hit/Miss| C
    C -->|HTTPS Request if Miss| E["External API (UniProt/AlphaFold/ClinVar/gnomAD)"]
    E --> C
    C -->|JSON Reply| B
    B -->|Resolved Promise| A
Loading

Sources: electron/preload.cjs:1-69, electron/disk-cache.js:1-93


Clone this wiki locally