Skip to content

HTTP-Semantics Aware Caching: ETag/304 Revalidation, stale-while-revalidate, Cache Tags & Inspector #3

@hoangsonww

Description

@hoangsonww

HTTP-Semantics Aware Caching: ETag/304 Revalidation, stale-while-revalidate, Cache Tags & Inspector

Summary
Upgrade GhostCache from time-only TTL to full HTTP-aware caching: parse and honor Cache-Control, support conditional requests (ETag/Last-Modified → 304), add stale-while-revalidate/stale-if-error, introduce cache tags for targeted invalidation, and ship a tiny DevTools-style inspector API for visibility.


Motivation

Many APIs expose validators and caching hints (ETag, Last-Modified, Cache-Control, Vary). Today we rely primarily on TTL. Supporting HTTP semantics improves correctness, reduces bandwidth, and enables instant UI while background-refreshing.


Goals

  1. Conditional Revalidation

    • Store ETag / Last-Modified from responses.
    • On refresh, send If-None-Match / If-Modified-Since; treat 304 as a fast cache hit (update age/headers).
  2. Cache-Control Parsing

    • Respect max-age, s-maxage, no-store, no-cache, must-revalidate, stale-while-revalidate, stale-if-error.
    • Normalize with existing ttl (explicit ttl overrides headers unless respectHeaders: true).
  3. Stale While Revalidate (SWR)

    • Serve stale immediately when within stale-while-revalidate window, then refresh in background and update store; expose a hook/callback for “refreshed” events.
  4. Stale-If-Error

    • If network/5xx during refresh and stale exists within stale-if-error, serve stale and surface a soft warning event/metric.
  5. Cache Key Normalization

    • Option to include/exclude query params, headers, and method; support a custom keyFn(req) to unify semantically identical requests.
  6. Cache Tags & Batch Invalidation

    • Allow setting tags: string[] per entry; expose invalidateByTag("user:42") to drop related keys (useful after POST/PUT/DELETE).
  7. Inspector / DevTools Hooks

    • Optional tiny overlay API:

      • getEntry(key), listEntries({tag}), stats() (hits/misses/revalidations).
      • Event hooks: onHit, onMiss, onRevalidate, onInvalidate.
  8. Axios & fetch Parity

    • Implement revalidation and header handling for both adapters.

Non-Goals

  • Full HTTP proxy behavior or full RFC 9111 edge cases.
  • Persisted header normalization across all custom adapters beyond documented subset.

API Proposal

enableGhostCache({
  ttl: 60_000,
  respectHeaders: true,     // NEW: honor Cache-Control / validators
  swr: true,                // NEW: enable stale-while-revalidate behavior
  keyFn: (req) => string,   // NEW: optional custom cache key builder
});

setCache("user/42", data, { tags: ["user:42"] });       // NEW: tags
invalidateByTag("user:42");                             // NEW
const info = GhostCacheInspector.getEntry(key);         // NEW
GhostCacheInspector.on("revalidate", (e) => { ... });   // NEW

Design Notes

  • Storage Layout: extend stored metadata to { body, status, headers, etag, lastModified, cacheControl, tags, createdAt, lastValidatedAt }.

  • Revalidation Flow:

    1. Lookup entry; if fresh → hit.
    2. If stale and swr → return stale immediately; kick off background fetch with If-None-Match / If-Modified-Since.
    3. On 304 → update timestamps/headers; on 200 → replace body/headers.
    4. On error and stale-if-error window → serve stale, emit event.
  • Key Normalization: default includes method + URL + sorted query; exclude transient headers unless varyHeaders: ["Accept-Language"] is set.

  • Security/Privacy: never cache Authorization-bearing responses unless cacheAuth: true and private: true are set; scrub PII headers from stored metadata by default.


Acceptance Criteria

  • ETag & Last-Modified captured and used for conditional requests with 304 handling.
  • Cache-Control directives respected when respectHeaders: true.
  • SWR path serves stale immediately and refreshes in background; events emitted.
  • stale-if-error returns stale on refresh failure within window.
  • Cache tags supported; invalidateByTag() removes affected entries across adapters.
  • Inspector API exposes getEntry, listEntries, stats, and event hooks.
  • Works with both fetch and axios; 95%+ unit test coverage for new logic.
  • README updated with examples and migration notes.

Testing Plan

  • Unit tests covering:

    • 200 → store validators; later 304 → metadata refresh only.
    • max-age/no-cache/SWR/stale-if-error branches.
    • Tag invalidation correctness.
    • Key normalization edge cases (query order, header variations).
  • Integration tests:

    • Axios and fetch parity using a mock HTTP server returning ETag/Last-Modified and varying directives.
  • Performance:

    • Ensure overhead for header parsing and background revalidation is negligible (<1ms average per call in Node 18+).

Sub-issues

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentationenhancementNew feature or requestgood first issueGood for newcomershelp wantedExtra attention is neededquestionFurther information is requested

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions