feat: implement multi-layer offline-first cache with IndexedDB, background sync, and conflict resolution - #762
Merged
Chucks1093 merged 1 commit intoAug 24, 2026
Conversation
…round sync, and conflict resolution Closes accesslayerorg#754
Member
|
The non-throwing fallback on IndexedDB operations is a good defensive choice since IndexedDB can fail in private browsing or when storage quota is exceeded and silently degrading to the in-memory cache means the app stays functional. The optimisticFields preserve on conflict resolution is also thoughtful because without it a sync drain could wipe out local position changes the user made while offline. |
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #754
Summary
IndexedDBCacheclass (src/lib/indexedDBCache.ts) wrapping the browser IndexedDB API with typedget,set,delete,clear,getAll, andevictStalemethods. All operations are async and non-throwing — any failure falls back silently to the in-memory React Query cache.src/lib/queryPersistence.tswithrestorePersistedQueries(loads all IndexedDB entries younger than 24 hours into the QueryClient on startup so the UI renders immediately with stale data) andsubscribeToQueryCache(subscribes to QueryClient cache events and persists every data update to IndexedDB).src/lib/conflictResolution.tswithresolveConflictimplementing last-write-wins merge: server data wins when it is newer, but fields listed inoptimisticFieldsare preserved from the local IndexedDB entry (e.g.positionson portfolio queries made while offline).public/service-worker.jsthat intercepts failed GET requests to/api/creators/and/api/wallet/during offline periods, queues them in async-queueIndexedDB store, and drains the queue via the Background Sync API when connectivity is restored. Queued requests older than 1 hour are discarded without replay.useOfflineStatushook andOfflineBannercomponent that show a persistent alert banner whennavigator.onLineis false and dismiss it automatically when connectivity is restored.StaleBadgecomponent that renders a 'Data may be outdated' badge on any data card whosedataUpdatedAttimestamp is older than 5 minutes.src/main.tsx(restore cache, subscribe to updates, register service worker) and mountsOfflineBannerinApp.tsx.IndexedDBCache(get, set, delete, clear, getAll, evictStale, and silent error fallback) and 10 forresolveConflict(server newer, local newer, equal timestamps, multiple optimistic fields, no mutation of inputs).Test plan