diff --git a/apps/lib/core/cache/cache_entry.dart b/apps/lib/core/cache/cache_entry.dart new file mode 100644 index 0000000..95051c1 --- /dev/null +++ b/apps/lib/core/cache/cache_entry.dart @@ -0,0 +1,6 @@ +class CacheEntry { + final T value; + final DateTime fetchedAt; + + const CacheEntry({required this.value, required this.fetchedAt}); +} diff --git a/apps/lib/core/cache/cache_key.dart b/apps/lib/core/cache/cache_key.dart new file mode 100644 index 0000000..b5f75b6 --- /dev/null +++ b/apps/lib/core/cache/cache_key.dart @@ -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; +} diff --git a/apps/lib/core/cache/cache_policy.dart b/apps/lib/core/cache/cache_policy.dart new file mode 100644 index 0000000..0ebd479 --- /dev/null +++ b/apps/lib/core/cache/cache_policy.dart @@ -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, + ); + } +} diff --git a/apps/test/core/cache/cache_policy_test.dart b/apps/test/core/cache/cache_policy_test.dart new file mode 100644 index 0000000..8f3db23 --- /dev/null +++ b/apps/test/core/cache/cache_policy_test.dart @@ -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); + }); +}