cacherine 2.4.0
cacherine: ^2.4.0 copied to clipboard
A Dart in-memory cache library with FIFO, LRU, MRU, LFU, TTL expiry, async-safe variants, and monitoring metrics.
cacherine #
cacherine is a simple and flexible memory cache library for Dart. It provides caching algorithms such as FIFO, LRU, MRU, LFU, and TTL-based expiry. Both synchronous and async-safe versions are available to handle different usage scenarios.
If you want to choose the best cache algorithm for your app, you can use MonitoredCache in your development environment. It helps you monitor performance metrics (such as hit/miss rates, latency, and eviction alerts) so you can make data-driven decisions and optimize the algorithm you use.
Why cacherine? #
Dart/Flutter does not have a built-in caching solution similar to NSCache in Swift.
cacherine was created to provide a lightweight and flexible in-memory cache with common caching strategies like FIFO, LRU, MRU, and LFU.
Whether you need a simple synchronous cache or an async-compatible solution that serializes concurrent async calls within the same isolate, cacherine offers an easy-to-use API.
Features #
- FIFO (First In, First Out) — Learn more
- EphemeralFIFO (FIFO-based cache where keys are removed after being accessed) — Learn more
- LRU (Least Recently Used) — Learn more
- MRU (Most Recently Used) — Learn more
- LFU (Least Frequently Used) — Learn more
- TTL (Time-To-Live — entries auto-expire after a configurable duration; optional per-entry TTL override and background sweep) — Learn more
- SimpleTTLCache (synchronous TTL cache for single-threaded usage)
- MonitoredCache (Includes performance monitoring with hit/miss rates, latency, and eviction alerts) — Learn more
- MonitoredTTLCache (TTL-based expiry with the same monitoring metrics and alerts as other monitored cache variants)
- CacheStatsDashboard (Wraps a MonitoredCache's metrics to provide point-in-time snapshots and periodic streams;
formatDashboard()renders a Unicode terminal panel) containsKey()support (distinguishes missing keys from storednullvalues without changing cache eviction state)peek()support (reads a value without changing cache eviction state or consuming EphemeralFIFO entries)size,isEmpty, andisNotEmptysupport (reads cache occupancy without recording monitored traffic metrics)purgeExpired()support for TTL caches (explicitly removes expired TTL entries and returns the number removed)- Conditional mutation helpers:
putIfAbsent(),update(), andremoveWhere()for cache-aside writes, targeted updates, and predicate-based cleanup - Bulk operations:
getAll(),setAll(), andremoveAll()for multi-key reads, writes, and invalidation - Simple versions (e.g., SimpleFIFOCache) for synchronous usage, and standard versions that serialize concurrent async calls within the same isolate
Installation #
Check the latest version on pub.dev and add it to your pubspec.yaml:
dependencies:
cacherine: ^2.4.0
Then, run the following command in your terminal:
dart pub get
Usage #
Basic Usage (Single-threaded) #
import 'package:cacherine/cacherine.dart';
void main() {
final cache = SimpleFIFOCache<String, String>(maxSize: 5);
cache.set('key1', 'value1');
print(cache.get('key1')); // 'value1'
}
For synchronous single-threaded usage, use SimpleTTLCache:
import 'package:cacherine/cacherine.dart';
void main() {
final cache = SimpleTTLCache<String, String>(
ttl: const Duration(minutes: 5),
maxSize: 100,
);
cache.set('token', 'abc123');
cache.set('rate', '42', ttl: const Duration(seconds: 30));
print(cache.get('token')); // 'abc123' (if within TTL)
}
Async Usage (Async support) #
import 'package:cacherine/cacherine.dart';
void main() async {
final cache = FIFOCache<String, String>(maxSize: 5);
await cache.set('key1', 'value1');
print(await cache.get('key1')); // 'value1'
}
Use getOrSet() on synchronous caches or getOrCompute() on async caches when
you want to populate a missing key from a callback:
final syncCache = SimpleLRUCache<String, String>(100);
final syncValue = syncCache.getOrSet('profile:42', () => 'computed value');
final ttlCache = TTLCache<String, String>(ttl: const Duration(minutes: 5));
final asyncValue = await ttlCache.getOrCompute(
'session:42',
() async {
return 'computed value';
},
ttl: const Duration(minutes: 1),
);
Use putIfAbsent(), update(), and removeWhere() when you need conditional
mutation without open-coding the same presence checks:
final cache = LRUCache<String, int>(100);
final count = await cache.putIfAbsent('requests', () => 0);
await cache.update('requests', (value) => value + 1);
await cache.removeWhere((key, value) => value == 0);
Use getAll(), setAll(), and removeAll() when you need to read, warm, or
invalidate multiple keys:
final cache = LRUCache<String, String>(100);
await cache.setAll({'user:1': 'Ada', 'user:2': 'Linus'});
final users = await cache.getAll(['user:1', 'missing', 'user:2']);
await cache.removeAll(users.keys);
TTL Usage #
Entries expire automatically once their TTL elapses. Use ttl: on individual set() calls to override the global default for a single entry.
import 'package:cacherine/cacherine.dart';
void main() async {
final cache = TTLCache<String, String>(
ttl: const Duration(minutes: 5), // global default
maxSize: 100, // optional capacity limit
sweepInterval: const Duration(minutes: 1), // optional background sweep
);
await cache.set('token', 'abc123'); // expires in 5 min
await cache.set('rate', '42', ttl: Duration(seconds: 30)); // expires in 30 s
print(await cache.get('token')); // 'abc123' (if within TTL)
print(await cache.purgeExpired()); // number of expired entries removed
cache.dispose(); // cancel background sweep timer when done
}
Monitoring Usage #
If you want to monitor the performance of your cache and optimize the algorithm, use a monitored cache variant such as MonitoredLRUCache or MonitoredTTLCache.
Learn more about MonitoredCache and performance monitoring.
Stats Dashboard Usage #
CacheStatsDashboard wraps any CacheMetrics instance (exposed by MonitoredCache variants) to give you typed snapshots and a periodic stream. Use formatDashboard() to render a human-readable terminal panel.
import 'package:cacherine/cacherine.dart';
void main() async {
final cache = MonitoredLRUCache<String, String>(maxSize: 100);
// warm up the cache
await cache.set('key', 'value');
await cache.get('key');
// one-shot snapshot over a 1-minute eviction window
final dashboard = CacheStatsDashboard(cache.metrics);
final snap = dashboard.snapshot(const Duration(minutes: 1));
print(formatDashboard(snap));
// ┌─── Cacherine Dashboard Snapshot ─────────────────────────────┐
// │ Captured at: 2026-05-15 12:00:00 │
// ├───────────────────────────────────────────────────────────────┤
// │ Traffic: 1 request │
// │ Hit Rate: 100.0% [████████████████████] │
// ├───────────────────────────────────────────────────────────────┤
// │ Latency: P50: 12µs / P95: 12µs / P99: 12µs │
// │ Evictions: 0 / min │
// └───────────────────────────────────────────────────────────────┘
// periodic stream — emits every 5 seconds
final sub = dashboard
.stream(const Duration(minutes: 1), const Duration(seconds: 5))
.listen((s) => print(formatDashboard(s)));
await Future<void>.delayed(const Duration(seconds: 15));
await sub.cancel();
cache.dispose();
}
API Contracts #
Async Safety #
The standard and monitored cache variants use Future APIs and an internal lock to serialize concurrent async calls on the same cache instance within the same isolate. They are not shared-memory synchronization primitives across Dart isolates.
Presence and Nullable Values #
get() returns null when a key is absent. Use containsKey() to distinguish a missing key from a stored null value, such as Cache<String, String?>. containsKey() does not update LRU/MRU/LFU access state, does not remove entries from EphemeralFIFO caches, and does not record monitored cache hit/miss metrics. For TTL caches, expired entries return false.
Non-Mutating Reads #
Use peek() when you need to read a value without changing cache policy state. It does not update LRU/MRU order, does not increment LFU frequency, and does not remove entries from EphemeralFIFO caches. For TTL caches, expired entries return null and are removed lazily. Monitored caches do not record hit/miss/latency metrics for peek().
Cache-Aside and Mutation Helpers #
getOrSet(), getOrCompute(), and putIfAbsent() use containsKey() semantics before reading, so stored null values are treated as present. Package async-safe cache implementations serialize the check/compute/store sequence on the same cache instance, so concurrent calls for the same missing key compute once and later callers observe the stored value. On monitored caches, getOrCompute() records one hit when the key already exists and one miss when the callback is used to populate the key.
update() changes an existing value or stores ifAbsent() when provided. It throws StateError for missing keys when ifAbsent is omitted. removeWhere() iterates a key snapshot and reads candidate values with peek(), so it does not update LRU/MRU order, increment LFU frequency, or consume EphemeralFIFO entries while deciding what to remove.
Bulk Operations #
getAll() returns a map containing only currently present keys. Stored null
values are included when V is nullable. Each present key is read with get(),
so eviction policy side effects match repeated single-key reads. setAll() and
removeAll() apply the same behavior as repeated set() and remove() calls.
TTL abstractions accept ttl: on setAll() to apply the same per-entry TTL to
all inserted entries.
Key Order and Occupancy #
getKeys() returns a snapshot. FIFO and TTL caches return insertion order for live entries. LRU and MRU caches return least-to-most recently used order. Ephemeral FIFO caches omit entries already consumed by get(). LFU cache key order is unspecified.
size, isEmpty, and isNotEmpty report the current cache occupancy. TTL caches count only live, non-expired entries. These APIs do not update cache eviction state and monitored caches do not record hit/miss/latency metrics for them.
TTL Expiry #
TTL caches expose purgeExpired() to remove all expired entries immediately and
return the number removed. MonitoredTTLCache records one eviction per entry
removed by purgeExpired().
TTL Interfaces #
Use SimpleTTLCacheInterface or ThreadSafeTTLCacheInterface when code needs an abstraction that still exposes per-entry TTL overrides:
final ThreadSafeTTLCacheInterface<String, String> cache =
TTLCache(ttl: const Duration(minutes: 5));
await cache.set('token', 'abc123', ttl: const Duration(seconds: 30));
await cache.purgeExpired();
Diagnostics and Lifecycle #
toString() is synchronous. It returns a point-in-time representation of the cache contents and should be treated as diagnostic output, not as a synchronized cache operation.
Caches that implement Disposable own a background timer for sweeping expired
entries, checking alert thresholds, or both. Call dispose() when the cache is
no longer needed. It is idempotent; after disposal, cache read/write operations
continue to work, but background sweep and alert monitoring stop.
Metrics Retention #
CacheMetrics keeps bounded in-memory samples: the most recent 1,000 latency
samples and 10,000 eviction timestamps. Hit, miss, and total request counters are
not capped. Latency percentiles and eviction rates are calculated from the
retained samples.
API Reference #
-
SimpleFIFOCache<K, V>: Synchronous FIFO-based cache
-
SimpleEphemeralFIFOCache<K, V>: Synchronous Ephemeral FIFO cache
-
SimpleLRUCache<K, V>: Synchronous LRU-based cache
-
SimpleMRUCache<K, V>: Synchronous MRU-based cache
-
SimpleLFUCache<K, V>: Synchronous LFU-based cache
-
SimpleTTLCache<K, V>: Synchronous TTL-based cache
-
FIFOCache<K, V>: FIFO-based cache
-
EphemeralFIFOCache<K, V>: FIFO-based cache where the key is removed after being accessed (One-Time Read Cache)
-
LRUCache<K, V>: Cache that removes the least recently used items
-
MRUCache<K, V>: Cache that removes the most recently used items
-
LFUCache<K, V>: Cache that removes the least frequently used items
-
TTLCache<K, V>: Cache with time-based expiry; global TTL with optional per-entry override, lazy eviction, optional background sweep, and optional capacity limit
-
MonitoredFIFOCache<K, V>: FIFO-based cache with monitoring
-
MonitoredEphemeralFIFOCache<K, V>: Ephemeral FIFO cache with monitoring
-
MonitoredLRUCache<K, V>: LRU-based cache with monitoring
-
MonitoredMRUCache<K, V>: MRU-based cache with monitoring
-
MonitoredLFUCache<K, V>: LFU-based cache with monitoring
-
MonitoredTTLCache<K, V>: TTL-based cache with monitoring
-
CacheStatsDashboard: Wraps
CacheMetricsto providesnapshot(Duration window)andstream(Duration window, Duration interval) -
CacheMetricsSnapshot: Typed point-in-time metrics snapshot returned by
CacheMetrics.snapshot(Duration window) -
DashboardSnapshot: Immutable point-in-time snapshot (hitRate, missRate, latency percentiles, evictionsPerMinute, totalRequests, capturedAt)
-
formatDashboard(): Renders a
DashboardSnapshotas a Unicode box-drawing terminal panel
Contributing #
Contributions are welcome! If you find a bug, have a feature request, or want to improve the code, feel free to open an issue or submit a pull request.
How to Contribute #
- Fork the repository and create a new branch.
- Make your changes and write tests if necessary.
- Ensure the code passes all checks (
dart analyze,dart test). - Open a pull request and describe your changes.
For major changes, please open an issue first to discuss your proposal.
We appreciate your support in making cacherine better! 🚀
Changelog #
All notable changes to this project will be documented in the CHANGELOG file.
See the full changelog here.
License #
This project is licensed under the MIT License - see the LICENSE file for details.