ever_cache 1.0.0
ever_cache: ^1.0.0 copied to clipboard
Allows to cache a computed value for a specific duration.
🚀 EverCache
A simple dart package which extends the functionality of Dart's built-in `late` keyword to provide a more robust and flexible way to handle lazy initialization. It closesly resembles the `Lazy
✨ Key Features #
- 🚀 Lazy Initialization: Compute the cache entry only when it is accessed for the first time. (or trigger the compute manually!)
- ⏳ TTL Support: Automatically purge cache entries after a set duration.
- 📡 Events: Monitor the state of the cache based on delegates invoked from the instance.
- 🔧 Placeholder: Provide placeholder data to be returned when cache is being computed.
- 🔍 Access Locking: Control acess to the computed value by using
lockfunctionality. - 🛡️ Error Caching (optional): Choose whether to cache the first exception until invalidation (akin to C# Lazy
- 📌 isValueCreated & snapshot: Inspect without forcing computation.
- 🪄 valueOrNull(): Safe read that never throws; returns placeholder or null.
- ⚡ ensure(): Await until a value exists; starts compute if needed.
- 📦 Precomputed value constructor:
EverCache.value(T)to wrap an existing value with TTL/events behavior. - 🧊 Sliding TTL (configurable): Absolute or sliding TTL; optionally disable sliding on read via
slideOnAccess. - 🔁 Publication-only refresh: Keep serving the last good value during background refresh errors/nulls; publish only on success.
- 🧭 Diagnostics: Inspect
computeCount,lastComputedAt,lastError,lastErrorStackTrace, and upcoming expirations.
🚀 Getting Started #
Integrate ever_cache into your project effortlessly. Just sprinkle this into your pubspec.yaml:
dependencies:
ever_cache: ^0.0.8
then run pub get or flutter pub get.
🌟 Usage #
import 'package:ever_cache/ever_cache.dart';
final cache = EverCache<String>(
() async {
// Your computation
return 'Hello, World!';
},
// set a placeholder if you wish to return a default value when the computation is in progress.
placeholder: () => 'placeholder',
// set a TTL (Time To Live) for the cache entry.
ttl: EverTTL.seconds(5),
// if you want to monitor different events emitted from the cache.
events: EverEvents(
onComputing: () => print('Conjuring...'),
onComputed: () => print('Voila!'),
onInvalidated: () => print('Poof! Gone!'),
onError: (e, stackTrace) => print('Oops! Computation failed: $e'),
),
// control exception semantics: do not cache by default; or cache until invalidated
errorCaching: EverErrorCachingMode.cacheUntilInvalidate,
// keep last good value during refresh failures, publish only on success
publishMode: EverPublishMode.publicationOnly,
// if you want the cache to be computed as soon as this constructor is called in the background
earlyCompute: true,
);
// access the computed value
// cache.value
// non-throwing access: cache.valueOrNull
// inspect state without computing: cache.snapshot
// wait for a value (starts compute if needed): await cache.ensure()
// has it been created? cache.isValueCreated
// diagnostics: computeCount/lastComputedAt/lastError/lastErrorStackTrace
// and nextExpiryAt/nextPrefetchAt/prefetchScheduled
📚 Additional Methods #
compute(): Manually compute the cache entryin async.computeSync(): Manually compute the cache entry in sync.lock(): Lock the cache entry to prevent further access till the provided callback is executed.invalidate(): Invalidate the cache entry.dispose(): Dispose of the cache entry.ensure(): Returns a FuturevalueOrNull: Returns T? without throwing; may return placeholder.isValueCreated: Indicates whether a value has been computed.
⏳ TTL modes and sliding control #
Use absolute or sliding TTL via EverTTL:
// Absolute TTL, no sliding
ttl: EverTTL.seconds(30),
// Sliding TTL restarted on access
ttl: EverTTL.seconds(30, mode: EverTTLMode.sliding),
// Sliding TTL but keep window fixed on reads (only refresh slides on write)
ttl: EverTTL.seconds(30, mode: EverTTLMode.sliding, slideOnAccess: false),
To prefetch before expiry (SWR), set prefetchBeforeExpiry:
ttl: EverTTL.minutes(5, prefetchBeforeExpiry: const Duration(seconds: 30)),
🔁 Publication-only refresh #
If a value already exists and you refresh in the background, you can keep the last good value visible even if the refresh fails or returns null:
final cache = EverCache<MyType>(
() async => fetchLatest(),
publishMode: EverPublishMode.publicationOnly,
ttl: EverTTL.minutes(5, prefetchBeforeExpiry: const Duration(seconds: 30)),
);
🧭 Diagnostics #
These read-only fields help with observability:
computeCount,lastComputedAtlastError,lastErrorStackTracenextExpiryAt— scheduled expiry timenextPrefetchAt— scheduled prefetch timeprefetchScheduled— whether a prefetch timer is active
Note #
EverCache is an open-source project and contributions are welcome! If you encounter any issues or have feature requests, please file them on the project's issue tracker.
For more detailed documentation, please refer to the source code and comments within the lib/ directory.
🧭 Roadmap / Ideas #
Drawing inspiration from C# Lazy<T>:
- Thread-safety modes (ExecutionAndPublication/PublicationOnly) — Not applicable to single-threaded Dart by default; consider isolates or re-entrant guards if needed.
- Reset semantics — today use
invalidate(); may addreset()alias. - Exception caching — implemented via
EverErrorCachingMode. - Diagnostics — counters for compute attempts, last error, last compute time.
Contributions and discussions are welcome!