fix: persist circuit breaker cache to IndexedDB across page reloads#279
fix: persist circuit breaker cache to IndexedDB across page reloads#279elzalem wants to merge 6 commits into
Conversation
On page reload, all 28+ circuit breaker in-memory caches are lost, triggering 20-30 simultaneous POST requests to Vercel edge functions. Wire the existing persistent-cache.ts (IndexedDB + localStorage + Tauri fallback) into CircuitBreaker so every breaker automatically: - Hydrates from IndexedDB on first execute() call (~1-5ms read) - Writes to IndexedDB fire-and-forget on every recordSuccess() - Falls back to stale persistent data on network failure - Auto-disables for breakers with cacheTtlMs=0 (live pricing) Zero consumer code changes -- all 28+ breaker call sites untouched. Reloads within the cache TTL (default 10min) serve instantly from IndexedDB with zero network calls. Also adds deletePersistentCache() to persistent-cache.ts for clean cache invalidation via clearCache(). Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@elzalem is attempting to deploy a commit to the Elie Team on Vercel. A member of the Team first needs to authorize it. |
7 tests covering: IndexedDB persistence on success, hydration on new instance, TTL expiry forcing fresh fetch, 24h stale ceiling rejection, clearCache cleanup, cacheTtlMs=0 auto-disable, and network failure fallback to stale persistent data. Co-Authored-By: Claude Opus 4.6 <[email protected]>
P1: deletePersistentCache sent empty string to write_cache_entry, which fails Rust's serde_json::from_str (not valid JSON). Add dedicated delete_cache_entry Tauri command that removes the key from the in-memory HashMap and flushes to disk. P2: clearCache() set persistentLoaded=false, allowing a concurrent execute() to re-hydrate stale data from IndexedDB before the async delete completed. Remove the reset — after explicit clear there is no reason to re-hydrate from persistent storage.
Fixes for P1 + P2Pushed to P1: Desktop cache deletion broken (High)Problem: Fix: Added a dedicated P2: clearCache() race condition (Medium)Problem: Fix: Removed Files changed: |
P1b: 6 breakers store Date objects (weather, aviation, ACLED, military-flights, military-vessels, GDACS) which become strings after JSON round-trip. Callers like MapPopup.getTimeUntil() call date.getTime() on hydrated strings → TypeError. Change default to false (opt-in) so persistence requires explicit confirmation that the payload is JSON-safe. P2: `if (!entry?.data) return` drops valid falsy payloads (0, false, empty string). Use explicit null/undefined check instead.
Additional fixes: P1b + P2P1b: Date type loss on hydration (High)6 breakers store Fix: Changed P2: Falsy data guard drops valid payloads (Low)
Fix: Changed to |
- clearCache() nulls persistentLoadPromise to orphan in-flight hydration - delete_cache_entry defers disk flush to exit handler (avoids 14MB sync write) - hydratePersistentCache checks TTL before setting lastDataState to 'cached' - deletePersistentCache resets cacheDbPromise on IDB error + logs warning - hydration catch logs warning instead of silently swallowing - deletePersistentCache respects isStorageQuotaExceeded() for localStorage
|
The 2 commits were added to another PR that closes this |
Summary
On page reload, all 28+ circuit breaker in-memory caches are lost, triggering 20-30 simultaneous POST requests to Vercel edge functions. This wires the existing
persistent-cache.tsinfrastructure (IndexedDB/localStorage/Tauri fallback) into the circuit breaker class so every service automatically persists its cache across reloads.execute()call, reads from IndexedDB (~1-5ms). If fresh (withincacheTtlMs), serves immediately — zero network callsrecordSuccess(), fire-and-forget write to IndexedDB. No blocking.cacheTtlMs: 0(e.g. live market quotes) skip persistence entirelyImpact
At 500K monthly users, assuming ~30% of sessions involve at least one reload: ~150K fewer edge function invocations/month.
Changes
src/utils/circuit-breaker.ts: AddpersistCacheoption,hydratePersistentCache(), fire-and-forget writes inrecordSuccess(), invalidation inclearCache()src/services/persistent-cache.ts: AdddeletePersistentCache()for clean cache invalidationZero consumer code changes — all 28+ breaker call sites are untouched.
Design
Uses dynamic
import('../services/persistent-cache')to avoid a hard dependency from utils to services. The import is resolved once by the module system and cached. Hydration is deduped via a promise guard so concurrentexecute()calls share one IndexedDB read.Persistent entries older than 24 hours are discarded entirely (stale ceiling). Entries between
cacheTtlMsand 24h are only used as fallback when the network call fails.Test plan
worldmonitor_persistent_cache— verifybreaker:*entries appear/api/POST requestsbreaker:Market Quotes)🤖 Generated with Claude Code