CacheInterceptor кэширует каждый успешный GET по путь+параметры в drift (sqlite нативно, wasm+OPFS в браузере) и подменяет им сетевую ошибку; провайдер отдаёт Cached<T>, экран показывает баннер «данные на …». Контракт для остальных экранов — docs/ai/offline-cache.md. flake.nix: libsecret/pkg-config для линуксовой сборки, jq/curl для just app-web-assets (сборка sqlite3.wasm + drift_worker.js).
32 lines
1.1 KiB
Dart
32 lines
1.1 KiB
Dart
import 'package:dio/dio.dart';
|
|
|
|
/// A value from the API, or the last one [CacheInterceptor] had on file for the
|
|
/// same endpoint+params when the live request failed. `fetchedAt` is null for
|
|
/// a live answer — that is the one bit a screen needs to decide whether to
|
|
/// show a `StaleBanner`. See `docs/ai/offline-cache.md`.
|
|
class Cached<T> {
|
|
const Cached(this.data, {this.fetchedAt});
|
|
|
|
final T data;
|
|
final DateTime? fetchedAt;
|
|
}
|
|
|
|
extension CachedResponseX<T> on Response<T> {
|
|
/// Wraps this response's data with the staleness [CacheInterceptor] recorded
|
|
/// in `extra['fetchedAt']`. A provider that wants offline support returns
|
|
/// `r.cached` instead of `r.data!`.
|
|
Cached<T> get cached => Cached(data as T, fetchedAt: extra['fetchedAt'] as DateTime?);
|
|
}
|
|
|
|
/// The oldest of several fetch times, or null if none of them is stale.
|
|
/// Screens with several cached providers show one banner for the lot rather
|
|
/// than one per tile.
|
|
DateTime? oldestFetch(Iterable<DateTime?> fetchedAt) {
|
|
DateTime? oldest;
|
|
for (final at in fetchedAt) {
|
|
if (at == null) continue;
|
|
if (oldest == null || at.isBefore(oldest)) oldest = at;
|
|
}
|
|
return oldest;
|
|
}
|