fix(repo): fall back to Dexie when a network read fails (H2) (#1179)

Repos gated reads on raw navigator.onLine and the online branch had no
try/catch, so a captive portal or connected-but-no-internet (navigator.onLine
lying "true") threw a network error instead of serving the good cached copy —
blanking the trip even though Dexie held it.

- new onlineThenCache(onlineFn, cacheFn) helper: reads the cache when offline,
  and on a network-level failure (Axios error with no HTTP response). A genuine
  HTTP error (4xx/5xx — the server responded) is rethrown so callers still set
  error state / navigate, not masked by a stale cache.
- gates only on navigator.onLine, NOT the connectivity probe: the probe is a
  coarse global flag and one failed health check would otherwise divert every
  read to the (possibly empty) cache even when the request would succeed.
- every repo list/get read path routed through it (reads only — writes still
  go through the mutation queue so failures surface)
- tests: captive-portal fallback, HTTP-error rethrow, non-Axios rethrow
This commit is contained in:
jubnl
2026-06-15 09:25:11 +02:00
committed by GitHub
parent 5a9c14fc8e
commit bcd2c8c959
12 changed files with 267 additions and 100 deletions
+12 -10
View File
@@ -1,20 +1,22 @@
import { placesApi } from '../api/client'
import { offlineDb, upsertPlaces } from '../db/offlineDb'
import { mutationQueue, generateUUID, nextTempId } from '../sync/mutationQueue'
import { onlineThenCache } from './withOfflineFallback'
import type { Place } from '../types'
export const placeRepo = {
async list(tripId: number | string, params?: Record<string, unknown>): Promise<{ places: Place[] }> {
if (!navigator.onLine) {
const cached = await offlineDb.places
.where('trip_id')
.equals(Number(tripId))
.toArray()
return { places: cached }
}
const result = await placesApi.list(tripId, params)
upsertPlaces(result.places)
return result
return onlineThenCache(
async () => {
const result = await placesApi.list(tripId, params)
upsertPlaces(result.places)
return result
},
async () => ({
places: await offlineDb.places
.where('trip_id').equals(Number(tripId)).toArray(),
}),
)
},
async create(tripId: number | string, data: Record<string, unknown> & { name: string }): Promise<{ place: Place }> {