Summary
Add a first-class SWR (stale-while-revalidate) cache layer to GhostIO with optional persistent storage (IndexedDB/localForage) and cross-tab coordination (BroadcastChannel). This keeps prefetched data instantly available, survives reloads, avoids duplicate network work across tabs, and revalidates in the background to keep responses fresh.
Motivation
- Instant UX: Serve prefetched data synchronously from cache (no spinners), then refresh in the background.
- Persistence: Current in-memory cache is lost on reload/navigation; keeping hot endpoints warm across sessions boosts hit-rate.
- Multi-tab efficiency: Today each tab may re-prefetch the same endpoints; coordinating avoids waste and rate limits.
- Freshness guarantees: Standardize TTL/ETag handling so devs don’t hand-roll invalidation per endpoint.
Proposed Design
1) New Options (constructor)
type GhostIOConfig = {
// existing...
maxCacheSize?: number;
concurrencyLimit?: number;
prefetchOnHover?: boolean;
prefetchOnScroll?: boolean;
idlePrefetchDelay?: number;
// new
persistence?: 'memory' | 'indexeddb'; // default 'memory'
swr?: boolean; // default true
defaultTTLms?: number; // e.g., 5 * 60_000
revalidateOnFocus?: boolean; // background revalidate when tab regains focus
crossTab?: boolean; // broadcast cache hits/misses, locks
respectValidators?: boolean; // use If-None-Match / If-Modified-Since
};
2) API Additions
class GhostIO {
get(url: string): { data: any; stale: boolean } | null; // indicates staleness
set(url: string, data: any, meta?: { ttlMs?: number; etag?: string; lastModified?: string }): void;
// Forces background revalidation; returns fresh value when done.
revalidate(url: string, opts?: { priority?: 'low'|'normal'|'high' }): Promise<any>;
// Cache management
getMeta(url: string): { insertedAt: number; ttlMs: number; etag?: string; lastModified?: string } | null;
prune(): void; // LRU + expired
clearPersistent(): Promise<void>;
}
3) Behavior
-
SWR Flow:
get(url) returns cached value immediately (even if expired → marked stale: true).
- In parallel,
revalidate(url) runs (deduped by an internal lock).
- If server returns
304 Not Modified (via If-None-Match or If-Modified-Since), extend TTL without replacing data.
-
Persistence:
- Store
{data, meta} in IndexedDB with LRU index.
- Hydrate into memory on first access (no full DB scan).
-
Cross-tab:
- Use
BroadcastChannel('ghost-io') to share: cache set events, in-flight locks, and eviction notices.
- A tab starting a revalidate broadcasts a lock; others avoid duplicate fetches.
-
Revalidate on Focus:
- When tab regains focus, revalidate any entries whose TTL expired.
-
Axios/Fetch integration:
- Existing
registerAxios can inject validators (If-None-Match, etc.) when meta exists.
- Add
registerFetch(fetchImpl?: typeof fetch) for native Fetch users (optional).
4) Example Usage
import { GhostIO } from 'ghost-io';
const ghost = new GhostIO({
persistence: 'indexeddb',
swr: true,
defaultTTLms: 300_000, // 5 minutes
crossTab: true,
revalidateOnFocus: true,
respectValidators: true,
});
// Prefetch somewhere (hover/idle) or manually:
await ghost.prefetch('/api/dashboard');
// Later in UI code:
const cached = ghost.get('/api/dashboard');
if (cached) {
render(cached.data); // instantaneous paint
if (cached.stale) ghost.revalidate('/api/dashboard'); // fire-and-forget
} else {
// fallback: fetch normally or show skeleton
}
5) Storage & Eviction
- LRU + TTL: IndexedDB bucket keyed by URL; secondary index for lastAccessed for O(log n) pruning.
- Quota handling: Detect
QuotaExceededError; fallback to memory mode and log a warning hook.
- Serialization: Structured clone (no JSON precision loss).
6) Edge Cases & Safety
- Error policy: If revalidate fails, keep stale data and backoff (exponential, capped).
- PII/secure endpoints: Opt-out per request:
ghost.prefetch(url, { persist: false }).
- Cache keys: Support a stable key function:
cacheKey(url, init) to include query/body if needed.
Performance/Success Criteria
-
70% cache hit-rate on hot endpoints after reloads (measured across sessions).
- Cross-tab lock dedupes ≥90% of overlapping revalidations.
- Revalidate CPU/network overhead bounded: no more than 1 concurrent revalidate per URL across tabs.
- Cold page load renders with cached data in <16ms on mid-tier devices.
Testing
- Unit: SWR semantics, TTL expiry, ETag/Last-Modified validator logic.
- Integration: Multi-tab BroadcastChannel tests (Playwright).
- Persistence: IndexedDB quota & fallback, LRU eviction correctness.
- Axios/Fetch: 304 handling and header injection.
Docs
- New “Caching & SWR” section with diagrams (cache states, cross-tab flow).
- Recipes: “Realtime dashboards”, “Offline-leaning pages”, “Protecting PII endpoints”.
Summary
Add a first-class SWR (stale-while-revalidate) cache layer to GhostIO with optional persistent storage (IndexedDB/localForage) and cross-tab coordination (BroadcastChannel). This keeps prefetched data instantly available, survives reloads, avoids duplicate network work across tabs, and revalidates in the background to keep responses fresh.
Motivation
Proposed Design
1) New Options (constructor)
2) API Additions
3) Behavior
SWR Flow:
get(url)returns cached value immediately (even if expired → markedstale: true).revalidate(url)runs (deduped by an internal lock).304 Not Modified(viaIf-None-MatchorIf-Modified-Since), extend TTL without replacing data.Persistence:
{data, meta}in IndexedDB with LRU index.Cross-tab:
BroadcastChannel('ghost-io')to share: cache set events, in-flight locks, and eviction notices.Revalidate on Focus:
Axios/Fetch integration:
registerAxioscan inject validators (If-None-Match, etc.) when meta exists.registerFetch(fetchImpl?: typeof fetch)for native Fetch users (optional).4) Example Usage
5) Storage & Eviction
QuotaExceededError; fallback to memory mode and log a warning hook.6) Edge Cases & Safety
ghost.prefetch(url, { persist: false }).cacheKey(url, init)to include query/body if needed.Performance/Success Criteria
Testing
Docs