feat: add unified cache policy primitives

This commit is contained in:
qzl
2026-03-20 15:21:52 +08:00
parent cbbed29a75
commit 035ca46bd0
4 changed files with 91 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
class CacheEntry<T> {
final T value;
final DateTime fetchedAt;
const CacheEntry({required this.value, required this.fetchedAt});
}
+17
View File
@@ -0,0 +1,17 @@
class CacheKey {
final String value;
const CacheKey(this.value);
@override
String toString() => value;
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other is CacheKey && other.value == value);
}
@override
int get hashCode => value.hashCode;
}
+49
View File
@@ -0,0 +1,49 @@
class CacheDecision {
final bool canUseCached;
final bool shouldRefreshInBackground;
final bool mustBlockForNetwork;
const CacheDecision({
required this.canUseCached,
required this.shouldRefreshInBackground,
required this.mustBlockForNetwork,
});
}
class CachePolicy {
final Duration softTtl;
final Duration hardTtl;
final Duration minRefreshInterval;
const CachePolicy({
required this.softTtl,
required this.hardTtl,
this.minRefreshInterval = Duration.zero,
});
CacheDecision evaluate({required DateTime now, required DateTime fetchedAt}) {
final age = now.difference(fetchedAt);
if (age >= hardTtl) {
return const CacheDecision(
canUseCached: false,
shouldRefreshInBackground: false,
mustBlockForNetwork: true,
);
}
if (age >= softTtl) {
final shouldRefresh = age >= minRefreshInterval;
return CacheDecision(
canUseCached: true,
shouldRefreshInBackground: shouldRefresh,
mustBlockForNetwork: false,
);
}
return const CacheDecision(
canUseCached: true,
shouldRefreshInBackground: false,
mustBlockForNetwork: false,
);
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:social_app/core/cache/cache_policy.dart';
void main() {
test('soft expired should allow stale read with background refresh', () {
final now = DateTime(2026, 3, 20, 12);
final policy = CachePolicy(
softTtl: const Duration(minutes: 2),
hardTtl: const Duration(minutes: 30),
minRefreshInterval: const Duration(minutes: 1),
);
final fetchedAt = now.subtract(const Duration(minutes: 3));
final decision = policy.evaluate(now: now, fetchedAt: fetchedAt);
expect(decision.canUseCached, true);
expect(decision.shouldRefreshInBackground, true);
expect(decision.mustBlockForNetwork, false);
});
}