NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #5566 most downloaded on pub.dev
A Dart in-memory cache library with FIFO, LRU, MRU, LFU, TTL expiry, async-safe variants, and monitoring metrics.
Last release 1 months ago
02 Sep 2026
Release timing varies
gaps range from 3 weeks to 8 months
Nearly every release is documented
notes for 14 of 14 stable releases
Nothing withdrawn
no release was ever pulled
2 years old
14 releases · first in 2025
One column per month.
…build artifact and failing the job if it finds a breaking change without a matching major version bump (--version-check-mode onlyBreakingChanges). Del…
getOrCompute/update control flow that every legacy facade composing (not extending) an engine had hand-rolled identically — this PR's own history is direct evidence of the resulting cost: the "write through this class's own set instead of the engine directly" fix had to be independently discovered and applied three separate times at three separate generality tiers. LRUCache/MRUCache/FIFOCache/LFUCache/EphemeralFIFOCache/TTLCache now call shared top-level functions (composedGetOrCompute/composedUpdate in the new lib/src/caches/_composed_engine_ops.dart, plus sync counterparts syncComposedGetOrSet/syncComposedUpdate used by SimpleTTLCache); their Monitored* counterparts (MonitoredLRUCache/MonitoredMRUCache/MonitoredFIFOCache/MonitoredLFUCache/MonitoredEphemeralFIFOCache/MonitoredTTLCache) call new monitoredGetOrCompute/monitoredUpdate methods added to the CacheMonitoring mixin. No public API changed; each facade keeps its own narrow signature and dispatch-through-set behavior — only the previously-duplicated method bodies moved.AsyncCache/MonitoredCache/MonitoredTTLCache/MonitoredEphemeralFIFOCache's getAll()/removeWhere() acquiring the instance lock once per key instead of once for the whole batch — setAll() already did this; these now match it, cutting a 10,000-key getAll() from 10,000-20,000 lock acquisitions down to 1. Cache.getAll()/removeWhere() (the sync engine) similarly now read the clock once for the whole batch instead of once per key, which is still one consistent point-in-time view (this method's own contract) at a fraction of the cost on a TTL-enabled instance.Cache's two internal call sites of checkWeightRejection() (trySet() and _storeOrThrow()) down to one: _storeOrThrow() now delegates to trySet() instead of duplicating its "check then write through set()" logic. checkWeightRejection()'s doc comment now spells out the correct-vs-incorrect usage explicitly, since a future caller of this public method (the docs already invite subclassing Cache/AsyncCache/MonitoredCache directly) could otherwise silently reopen the exact double-weigher-invocation issue this method exists to prevent.getAll()/removeWhere() lock-batching fix above: a slow (realistically async) removeWhere() predicate now provably blocks a concurrent set() on an unrelated key for the whole call, not just between individual key checks — confirmed by verifying this same test fails against the prior per-key-lock-acquisition implementation.api-diff CI job (.github/workflows/ci.yaml) that runs dart_apitool on every PR to diff this branch's public API against the latest version published on pub.dev, publishing the full report as a build artifact and failing the job if it finds a breaking change without a matching major version bump (--version-check-mode onlyBreakingChanges). Deliberately not the stricter fully mode, which would also require every additive change to already carry its eventual minor bump — this project bumps pubspec.yaml's version as a release-time step, not per-PR (true of several additions in this very release, still sitting on the currently-published version), so fully mode would false-positive on ordinary feature work. onlyBreakingChanges instead automates exactly what this project already polices by hand (this file's own "Breaking Changes" sections); validated by both confirming it passes cleanly against this release's substantial-but-non-breaking additions and by injecting a real breaking rename and observing an immediate, correctly-classified failure.getOrCompute()/update() on the five non-TTL legacy facades (LRUCache/MRUCache/FIFOCache/LFUCache/EphemeralFIFOCache and their Monitored* counterparts) checking presence and reading by calling the composed engine directly, instead of this class's own (overridable) containsKey()/get() — a downstream subclass's override of either was silently bypassed, unlike the pre-existing ThreadSafeCache.getOrCompute()/update() defaults these facades stand in for (which always dispatched through containsKey()/get()). For the Monitored* variants this also meant a hit was not recorded at all if the caller's update/valueFactory callback later threw, since the whole check-compute-store sequence was wrapped in one monitoredGet() call instead of recording the hit as soon as the read resolved. Both now dispatch through containsKey()/get() — safe for these five facades specifically because none of them configure a ttl, so (unlike TTLCache/MonitoredTTLCache, which keep their existing atomic-snapshot implementation) there's no check-then-fetch race for two separate calls to reintroduce; the whole sequence still runs under one continuously-held, reentrant lock acquisition.EphemeralFIFOCache/MonitoredEphemeralFIFOCache's getAll()/removeWhere(), which were left to ThreadSafeCache's default implementations (correct for every other legacy facade, since their get/peek are non-destructive) — but get() for this store is destructive (an entry is removed on retrieval), so the default's separate containsKey()-then-get()/peek() calls, each independently acquiring the lock, left a gap where a concurrent caller's get() could consume the entry first: getAll() would silently omit a key that was confirmed present a moment earlier, and removeWhere() would throw a TypeError casting the resulting null to a non-nullable V. Both now read (and, per get()'s documented behavior, consume) each key via a single atomic snapshot instead, matching the fix already applied elsewhere in this release for TTL/weight check-then-fetch races.CacheMetrics.getLatencyPercentile(50) (and CacheMetricsSnapshot.p50Latency) discarding all sub-millisecond precision on an even sample count — the median branch truncated both middle samples to whole milliseconds via .inMilliseconds before averaging, so two latencies like 400µs/800µs (realistic for an in-memory cache) incorrectly reported a median of 0, disagreeing with averageLatency's correct 600µs for the same data. Now averages in microseconds throughout.CacheAlertManager firing a spurious "Low hit rate detected" alert on a freshly-constructed cache that hasn't served any traffic yet: CacheMetrics.hitRate documented-returns 0 when totalRequests is 0, and 0 is below almost any positive hitRateThreshold (the default is 0.5), so an idle cache looked identical to one with a genuine 0% hit rate. _checkAlerts() now skips both the hit-rate and miss-rate checks entirely when totalRequests == 0 — neither rate means anything without at least one request to compute it from. Found via an edge-case coverage audit, confirmed by reproducing the spurious alert against the unfixed code before applying the fix.Cache's capacity/weight eviction loop silently giving up on a cache holding a literal null key (K nullable): FIFOStore/LRUStore/EphemeralFIFOStore/TTLFifoStore's evictOne() delegated to selectVictim() and checked victim == null to mean "nothing evictable" — indistinguishable from a legitimately-selected victim whose key is null. Cache._write()'s while (exceedsCount() || exceedsWeight()) loop then broke out immediately instead of evicting, so e.g. SimpleFIFOCache<int?, String>(1) followed by set(null, 'a') then set(1, 'b') left both entries — over the declared maxSize. selectVictim()/evictOne() on CacheStore now return a single-field record ((K,)?/(K, V)?) rather than a bare K?, so "found, and the key is null" and "found nothing" are distinguishable regardless of K; every store's evictOne() was audited and, where needed, rewritten to stop relying on the ambiguous bare-K? check internally. See PR #69 review discussion for the original report.Cache.update()'s callback-throws-mid-computation path now asserts the cache/weight ledger is left untouched and the instance stays usable afterward; AsyncCache/MonitoredCache's getOrCompute()/update() now assert the instance's lock is actually released (not just that the exception propagates) when the caller's valueFactory/update callback throws, guarding against a silent deadlock on every later call; EvictionReason attribution is now tested with maxSize and maxWeight configured together (previously each was only tested in isolation), pinning down that a write exceeding both at once is always attributed .weight, never .capacity; and TTLFifoStore was added to the shared CacheStore conformance suite (it was the only store implementation not covered by it) plus a dedicated policy test for its "update refreshes FIFO position" behavior, the one place it deliberately diverges from FIFOStore.getOrSet/update/trySet/getOrCompute is now pinned down explicitly (see the checkWeightRejection fix below); a slow getOrCompute() on one key is confirmed to actually block a concurrent set() on an unrelated key, per AsyncCache's documented single-instance-lock tradeoff; a set() override that reentrantly calls a different public method (clear()) mid-write is confirmed not to deadlock or corrupt state; a fully unbounded Cache (no maxSize/maxWeight/ttl) is confirmed to hold 50,000 entries correctly; a temporarily backward-jumping injected clock is confirmed not to corrupt cache state and to resume normal purging once the clock catches back up; Cache._minExpiry's post-purge restoration is now verified to keep short-circuiting a subsequent write, not just the write that triggered the purge; a weight-bounded LFU cache's excluding-key eviction fallthrough (previously only tested against LFUStore in isolation) is now driven end-to-end through Cache; CacheMetrics.snapshot() is now smoke-tested for O(n)-not-worse cost at the full maxEvictionSamples retention cap, and for correct window-filtering under sustained churn well past that cap; CacheAlertConfig's strict-inequality thresholds are now tested at their exact boundary (hit rate and per-reason eviction rate) to catch an accidental <=/>= flip; LRUStore/FIFOStore/EphemeralFIFOStore gained the same selectVictim(excluding:) multi-candidate fallback test MRUStore/LFUStore already had; LFUCache.getKeys() — the only policy whose key order is documented as unspecified — was added to the canonical order-contract test file, asserting the correct key set rather than an order; and PeriodicSweeper gained a dedicated test file, including a direct test of its documented "an in-flight sweep still runs to completion after dispose()" guarantee.trySet()/getOrSet()/update()/AsyncCache.storeOrThrow() computed a write's weight twice — once as a pre-check (to decide whether to reject/throw), once again inside the delegated set()/_write() — so a non-deterministic weigher (unsupported per its documented "should be pure" contract, but not otherwise guarded against) could disagree with itself between the two calls, letting trySet() report success (or getOrSet()/update() return a value) for a write that was actually silently rejected for exceeding maxWeight. Cache.wouldRejectWrite() was replaced with checkWeightRejection(), which computes the weight once and threads it back into the delegated set() call as an explicit weight:, so the weigher is now invoked exactly once per logical write (down from up to twice) and the two call sites can no longer disagree.getOrCompute/update read-dispatch fix below: the bug it fixes previously existed identically in all five non-TTL legacy facades (and their Monitored* counterparts) but had only ever been exercised by a test for one of them (LRUCache) — the same "pattern applied uniformly across several call sites, verified against only one" shape as the bug itself. legacy_facade_subclass_compat_test.dart's subclass-dispatch coverage is now driven through a single parameterized check across every concrete non-TTL facade (LRUCache/MRUCache/FIFOCache/LFUCache/EphemeralFIFOCache and their Monitored* counterparts) instead of one hand-picked representative, so a future change that regresses just one sibling can no longer hide behind the others' tests staying green.test/caches/model_based_lru_cache_test.dart: a differential test that runs LRUCache against an independent, deliberately naive reference model (written without consulting LRUStore's implementation) over 50 random seeds × 200 operations each (set/get/peek/containsKey/remove/getOrCompute/update), asserting the two agree — including the full key set, not just the immediately-preceding return value — after every single step. Every other test in this suite is example-based (a hand-picked sequence exercising one specific behavior); this instead covers the much larger space of interaction sequences no one thought to hand-write, and was confirmed to have real detection power by deliberately injecting a wrong-eviction-victim bug into LRUStore and observing an immediate, specific failure.test/caches/model_based_mru_cache_test.dart, model_based_fifo_cache_test.dart, model_based_lfu_cache_test.dart, and model_based_ephemeral_fifo_cache_test.dart, each with its own from-scratch reference model rather than a copy of LRUCache's (MRU evicts the most-recently-used entry and, unlike LRU, must therefore evict before inserting a new key — a fresh key would otherwise instantly become its own victim; FIFO/EphemeralFIFO never reorder on read or on updating an existing key; LFU tracks frequency-then-recency and, per LFUStore.put()'s documented contract, must not bump frequency on a plain overwrite). Empirically probing the real classes first surfaced a genuine, non-obvious behavior worth pinning down explicitly: EphemeralFIFOCache.update()'s hit path reads via the destructive get() and then writes the result back through set() — since the key was just removed by the read, the write reinserts it as a brand-new entry at the newest position, unlike FIFOCache.update(), which leaves an existing key's position untouched. Each of the four gained the same bug-injection validation as the original LRUCache test (a policy-specific store bug — wrong eviction end, reordering on read, frequency not preserved on overwrite — confirmed to fail immediately and specifically), so all five cache-policy differential tests now carry the same evidence of real detection power.test/caches/concurrency_stress_test.dart: every prior concurrency test in this suite races a hand-picked pair of calls via Completers to pin down one exact interleaving — the right tool for proving a specific ordering is safe, but unable to catch a bug that only shows up under much wider fan-out (e.g. a dedup path that happens to hold for two racing callers but not for the twentieth). These tests instead fire a large, unstructured batch of calls via Future.wait and assert on the aggregate outcome, across LRUCache/MRUCache/FIFOCache/LFUCache/AsyncCache/MonitoredCache: 100 concurrent getOrCompute() calls on the same missing key collapse into exactly one valueFactory invocation; hundreds of mixed set/get/remove/getOrCompute/update calls against a capacity-bounded cache never push it over maxSize and leave it fully usable afterward; concurrent getAll()/removeWhere() batch calls interleaved with regular traffic on a MonitoredCache never deadlock (the lock-batching change earlier in this release is exactly the kind of change that could have reintroduced one) and settle at a consistent count; and a MonitoredLRUCache's hit/miss/total-request counters stay exactly consistent under 300 concurrent get() calls. Empirically probing EphemeralFIFOCache for this surfaced another genuine, non-obvious behavior: because its getOrCompute() reads a hit through the destructive get(), single-flight dedup cannot hold across more than one subsequent caller — a hit for caller N is a fresh miss for caller N+1 — so it's covered by its own dedicated test asserting exactly that pattern (half the calls recompute, the cache ends up empty) rather than folded into the generic sweep. Every wait in this file carries an explicit .timeout() naming what would have deadlocked, since a hung Future.wait would otherwise just run until the test runner's own timeout with a generic message. Validated the same way as the model-based tests: injecting a lock-removal bug into composedGetOrCompute produced immediate, specific failures across every affected target.WeightedLRUCache/SimpleWeightedLRUCache/MonitoredWeightedLRUCache family's update()/getOrCompute() StateError-on-oversized-write contract (previously only exercised via set()'s silent-rejection path, never through the non-void-return methods that actually throw) is now tested for all three classes; a weigher that throws is now tested for the same three classes, confirming the instance is left untouched and — for the async/monitored variants — that the lock is released rather than leaked; TTLCache now has a dedicated test for the exact instant an entry's TTL elapses (now == expiry), not just well-before/well-after, pinning down that equality counts as expired; getAll() with an empty keys iterable and with a repeated key are now both tested (the latter also for EphemeralFIFOCache specifically, where a repeated key's second occurrence is a same-call miss following the first occurrence's destructive read — the result still reflects the first, successful read). This same audit found and fixed the CacheAlertManager zero-traffic bug above; a dedicated regression test for it (and a fix to the one pre-existing test that had been unknowingly relying on that bug's behavior to observe the alert timer firing) were added to cache_alert_manager_test.dart.cache_store_conformance_test.dart's "documented limitation" group (which previously asserted the bug as expected behavior) was replaced with a group proving every store selects/evicts a null-keyed entry once it's a genuine candidate, plus a narrower group confirming the one remaining excluding-default limitation stays exactly that limitation, not something worse; cache_engine_test.dart gained end-to-end Cache-level tests driving a maxSize- and a maxWeight-bounded cache past capacity with a pre-existing null key; and simple_fifo_cache_test.dart/simple_weighted_lru_cache_test.dart each gained a facade-level test matching the exact reported repro shape.SimpleWeightedLRUCache, WeightedLRUCache, and MonitoredWeightedLRUCache — LRU caches bounded by a caller-supplied per-entry weight (e.g. estimated byte size) via a weigher callback and maxWeight, optionally alongside an entry-count maxSize. An explicit weight: argument can also be passed per set() call.Cache/AsyncCache/MonitoredCache classes and a CacheStore interface (with LRUStore/MRUStore/FIFOStore/EphemeralFIFOStore/LFUStore implementations). Every named cache class in this package (SimpleLRUCache, TTLCache, MonitoredWeightedLRUCache, ...) is now a thin facade over this engine — power users can configure combinations that don't have a dedicated name (e.g. a weight-and-TTL-bounded LRU cache) by constructing Cache/AsyncCache/MonitoredCache directly.EvictionReason (capacity, weight, expired, manual, unspecified) and a new CacheMetrics.recordEvictionReason(EvictionReason) method to record it, alongside an additive evictionsPerMinuteByReason field on CacheMetricsSnapshot/DashboardSnapshot. The existing zero-argument recordEviction() is unchanged (it now delegates to recordEvictionReason(EvictionReason.unspecified)) — see the "Maintenance" section further below for why EvictionReason isn't instead an optional parameter on recordEviction() itself. CacheAlertConfig gained an optional evictionsPerReasonThreshold so alerting can distinguish an expected expired rate from a capacity/weight rate that signals the cache is undersized.PeriodicSweeper mixin (implements Disposable) that centralizes the "owns a periodic timer" lifecycle previously hand-rolled per TTL/Monitored class.weigher — the recorded weight is never recomputed, so a value mutated in place after caching will silently drift from the ledger; re-set() (with an explicit weight: override if needed) to refresh it.getOrCompute/update hold the whole cache instance's lock across the awaited callback (not just the affected key), so slow work (e.g. a network request) should not run directly inside it.LFUStore's eviction-candidate fallthrough: when the min-frequency bucket's only occupant is the key currently being excluded from eviction, selection falls through to the next occupied frequency bucket rather than reporting nothing evictable.K limitation on CacheStore.selectVictim()/evictOne()'s excluding parameter: its own default (null, "exclude nothing") is indistinguishable from an explicit request to exclude the literal key null, so a store holding only a null-keyed entry, queried with excluding left at its default, still reports nothing evictable. This doesn't affect Cache's own eviction loop (which always passes the key it's currently writing as excluding, never the default) — only a caller driving CacheStore directly. See CacheStore.selectVictim()'s doc comment for the full analysis. (The broader version of this limitation — null also overloading the return value's "no victim" result — is fixed; see Fixes below.)Cache.getOrSet()/update(), AsyncCache.getOrCompute()/update(), and MonitoredCache.getOrCompute()/update() do not dispatch their presence check/read through this cache's own (possibly overridden) get()/containsKey() — unlike set(), which always does. This is deliberate, not an oversight, and structurally different from the same-shaped bug just fixed in the non-TTL legacy facades below: those facades never have a ttl, so dispatching through containsKey()/get() is safe; these three classes are the general engine itself, used both with and without a ttl/weigher depending on how a given instance was built, so there is no way to know at the call site whether a specific instance needs presentValue()'s single-clock-snapshot safety — dispatching through get()/containsKey() here would silently reintroduce the TTL check-then-fetch race for any ttl-configured instance. Previously undocumented despite the class docs explicitly inviting "power users" to subclass these engines directly.dart doc warnings from unresolved doc-reference syntax: CacheStatsDashboard.snapshot() referenced [totalRequests] unqualified (now [CacheMetrics.totalRequests]), and seven Monitored*Cache classes' doc comments used the grammatical shorthand follow[s] (e.g. "update() follow[s] getOrCompute()..."), which dartdoc parsed as a broken [s] cross-reference rather than prose (now spelled out as follows). dart doc . now reports 0 warnings.presentValue() above fixed for getOrSet/getOrCompute/update), where a separate presence check and read/peek could each read the clock independently and observe an entry expire in between: Cache.getAll()/removeWhere(), AsyncCache.getAll()/removeWhere() (previously falling back to the base interfaces' racy defaults), and SimpleTTLCache.getOrSet()/update()/getAll()/removeWhere(), TTLCache.update()/getAll()/removeWhere(), MonitoredTTLCache.update()/getAll()/removeWhere() (previously falling back to their parent interfaces' racy defaults instead of the composed engine's atomic helpers). Added Cache.presentPeek() — a peek-based (non-mutating) counterpart to presentValue() — so removeWhere() can test entries for removal without perturbing LRU/LFU eviction-policy state as a side effect.LFUStore's eviction-candidate fallthrough degrading to O(distinct frequencies) per victim (and repeating per victim across a multi-eviction write, i.e. up to O(n × distinct frequencies)) by chaining frequency buckets into their own linked list instead of rescanning a plain frequency map; eviction, promotion, and selection (including the excluded-key fallthrough) are now O(1) worst case.MonitoredCache.getAll()/update() and MonitoredTTLCache.getAll()/removeWhere()/update() silently dropping the hit/miss/latency/eviction metrics doc/monitored_cache.md documents, by delegating straight to an unmonitored inner engine call instead of routing through the monitored get()/remove() path (update()'s gap was a regression from the presentValue()-based TTL-race fix above, which bypassed the old default implementation's get() call that used to record these metrics via virtual dispatch).Cache.validateSetArgs() silently accepting a negative explicit weight: on getOrSet()/getOrCompute()/update() when the key was already present (only a miss reached the negative-weight check inside _write()); it's now rejected eagerly regardless of hit or miss, matching set()'s validate-first contract.getOrSet()/getOrCompute()/update() (Cache, AsyncCache, MonitoredCache) reporting a value as cached when its weight actually exceeded maxWeight and _write() silently rejected it as a no-op — the same "can never fit" case set() accepts silently, but these methods have a non-void return contract, so a caller previously got back a value (the newly-computed one, or the update callback's result) that was never actually stored, while any prior entry under that key was left unchanged with no signal anything went wrong. They now throw StateError instead when the write is rejected.Cache._write() no longer scans every stored key to purge expired entries on a capacity-triggered write when nothing has actually expired yet (tracked via a cheap lower-bound on the earliest expiry deadline), so inserting into a TTL-and-maxSize-bounded cache at steady-state capacity stays O(1) amortized instead of O(n) per insert. Added CacheStore.removesOnAccess so Cache.get() skips a redundant presence recheck for every policy (LRU/LFU/FIFO/MRU/TTL) except EphemeralFIFOStore, the only one where access() removes the entry.SimpleLRUCache/SimpleFIFOCache/SimpleLFUCache/SimpleMRUCache/SimpleEphemeralFIFOCache, their async equivalents, and the corresponding Monitored*Cache classes: extending Cache/AsyncCache/MonitoredCache directly (rather than composing them, as TTLCache/MonitoredTTLCache/SimpleTTLCache already correctly did) pulled the new engine's weight/ttl named parameters into these classes' inherited set/setAll/getOrSet/getOrCompute/update, so any pre-existing downstream subclass overriding one of those methods with the old, narrower signature would fail to compile (Dart doesn't allow an override to drop optional named parameters the overridden method declares). All 15 of these "legacy" facades now compose an internal engine instead, restoring their original method surface; the Monitored* ones additionally mix in CacheMonitoring/PeriodicSweeper directly (matching MonitoredTTLCache) so is CacheMonitoring<K, V>/is Disposable keep holding.setAll/getAll/getOrSet/getOrCompute/update/removeWhere) delegating straight to the internal engine, bypassing a downstream subclass's override of set/get/etc. entirely — before the composable-engine refactor, these were inherited from SimpleCache/ThreadSafeCache and dynamically called the (overridable) facade methods. The non-monitored facades now leave these to the interface defaults, which call this class's own methods; the Monitored* ones keep custom getOrCompute/update (for hit/miss metrics) but now write through their own set instead of the engine directly. AsyncCache's internal lock is now reentrant so that in-lock write can happen without deadlocking, preserving the existing no-duplicate-computation-for-a-racing-key guarantee for getOrCompute/update.CacheMetrics.recordEviction() gaining an optional EvictionReason parameter, which was source-breaking for the same reason as above (downstream override with the original zero-argument signature). Split it back into a genuine zero-argument recordEviction() and a new recordEvictionReason(EvictionReason).doc/weighted_lru_cache.md examples labeling a List<int>'s .length as a byte-size weight — a List<int> has substantial per-element overhead beyond one byte, so this understated real memory usage. Switched the examples to Uint8List/lengthInBytes, which is exactly the byte count.set-bypass issue in TTLCache/MonitoredTTLCache/SimpleTTLCache's getOrCompute/getOrSet/update, which wrote through the internal engine directly instead of this class's own set. Unlike the 15 facades above, these classes' getAll/removeWhere were deliberately left calling the engine directly (not fixed) — they need a single atomic clock snapshot per key to avoid a TTL check-then-fetch race already fixed earlier in this same effort, which routing through separate get/peek/containsKey calls would reintroduce; getOrCompute/update don't have that conflict since they already run under a single lock hold, so the same reentrant-lock technique applies.set-bypass issue at its root, in Cache/AsyncCache/MonitoredCache themselves: Cache.getOrSet()/update()/setAll() wrote via private _write/_writeOrThrow helpers, and AsyncCache/MonitoredCache's getOrCompute()/update()/setAll() wrote via storeOrThrow() calling the composed Cache engine's trySet() directly — both bypassing this.set() entirely. Since SimpleWeightedLRUCache/WeightedLRUCache/MonitoredWeightedLRUCache extend these classes directly and add no set() of their own, they (and any subclass overriding set() on any of these six classes) were silently affected. Cache.getOrSet()/update()/setAll()/(the now-set()-delegating) trySet() and AsyncCache.storeOrThrow()/setAll() now write through this.set(); a new Cache.wouldRejectWrite() lets each of these detect a weight-exceeds-maxWeight rejection before delegating to set() (which, like _write() before it, has no way to report back what it actually stored), so the reject-and-throw contract on getOrSet/update/getOrCompute is preserved without needing set() itself to return anything. Added test/caches/core_engine_subclass_compat_test.dart, covering all six classes, to guard against this regressing again.Added putIfAbsent(), update(), and removeWhere() default APIs to simple, async-safe, and TTL cache interfaces.
putIfAbsent(), update(), and removeWhere() default APIs to simple, async-safe, and TTL cache interfaces.putIfAbsent() and update() overloads so new or updated entries can receive per-entry TTL overrides through TTL abstractions.getAll(), setAll(), and removeAll() bulk operation APIs to simple, async-safe, and TTL cache interfaces.setAll() overloads so batches can receive a shared per-entry TTL override through TTL abstractions.getOrCompute() same-instance serialization semantics.CacheMetrics for deterministic eviction-window and dashboard tests.getOrCompute() concurrent computation behavior.Expanded the runnable example and README cache-aside snippets to cover both getOrSet() and TTL-aware getOrCompute().
getOrSet() and TTL-aware getOrCompute().peek() API across README and cache guides.size, isEmpty, and isNotEmpty) across README and cache guides.purgeExpired().peek() to simple, async-safe, monitored, and TTL cache variants so callers can read values without updating cache eviction state.size, isEmpty, and isNotEmpty to simple, async-safe, monitored, and TTL cache variants so callers can inspect cache occupancy without materializing keys directly.purgeExpired() to SimpleTTLCache, TTLCache, MonitoredTTLCache, and the TTL cache interfaces so callers can explicitly remove expired TTL entries and inspect how many were removed.synchronized and lints to the newest resolvable versions for the current SDK range.peek() nullable-value behavior, policy side effects, monitored traffic metrics, and TTL expiry.SimpleTTLCache: Added a synchronous TTL cache variant with global and per-entry TTL, lazy expiry, containsKey(), optional maxSize, and FIFO capacity e
containsKey(), optional maxSize, and FIFO capacity eviction.SimpleTTLCacheInterface and ThreadSafeTTLCacheInterface so abstract cache references can still expose per-entry TTL overrides.getOrSet() to simple caches and getOrCompute() to async-safe caches, including TTL per-entry override support through TTL-specific interfaces.SimpleTTLCache, TTLCache, and monitored cache dashboard snapshots.getKeys() ordering contracts, Disposable lifecycle behavior, and bounded CacheMetrics sample storage.SimpleTTLCache, TTLCache, and MonitoredTTLCache through the new TTL-specific interfaces.dispose() idempotency, post-dispose operations, and getKeys() ordering contracts.chore: prepare 2.1.0 release
chore: prepare 2.1.0 release
TTLCache.containsKey() to simple, async-safe, monitored, and TTL cache variants so callers can distinguish stored null values from missing keys without mutating eviction state.CacheMetrics.snapshot(Duration window) API with hit/miss rates, latency percentiles, eviction rate, total requests, and capture time.CacheAlertConfig optional for monitored cache variants by providing a default no-op alert callback and default thresholds.null values are recorded as hits when the key exists.sweepInterval and per-entry TTL values so zero or negative intervals fail fast.ThreadSafeCache implementations.containsKey() semantics, nullable value behavior, and TTL expiry behavior.toString() output as diagnostic point-in-time state.containsKey() policy side effects, monitored cache constructors, metrics snapshots, and MonitoredTTLCache expiry paths.Merge pull request #46 from mem-5514-tahara/chore/update-deps-and-ver…
Merge pull request #46 from mem-5514-tahara/chore/update-deps-and-ver…
_minFreq recomputation in remove(). The minimum-frequency pointer is now maintained incrementally.toString() now eagerly snapshots _keyMap before formatting, preventing a data-race window between the map read and string construction in async contexts.toString() is not covered by the lock-based thread-safety guarantee; result is a point-in-time snapshot.getKeys() and toString().set() on an existing key and the tiebreak semantics.>=3.8.0 (minimum required by synchronized ^3.4.0)synchronized at ^3.3.1 (resolves to 3.4.0+1 in practice, which requires SDK >=3.8.0)lints dev dependency from ^5.0.0 to ^5.1.1 to align with the revised SDK baselinetest dev dependency from ^1.25.8 to ^1.31.0 (resolved: 1.31.1)dart_code_metrics ^5.7.6 (incompatible with the analyzer versions required by modern test tooling).fvm/ and .fvmrc to .gitignorefeat: release v2.0.0 — CacheStatsDashboard, TTLCache, and Lifecycle Management
feat: release v2.0.0 — CacheStatsDashboard, TTLCache, and Lifecycle Management
CacheMetrics to produce typed DashboardSnapshot objects for terminal-ready metric display.Disposable interface to handle resource cleanup (timers, controllers) across monitored and TTL caches.hitRate, missRate, latency percentiles (p50, p95, p99), evictionsPerMinute, totalRequests, and capturedAt.DashboardSnapshot as a Unicode box-drawing terminal panel with adaptive unit formatting (µs, ms, s).Monitored* caches and TTLCache now implement Disposable. Callers managing these instances should call .dispose() to prevent timer leaks.CacheStatsDashboard.snapshot(Duration window) now throws ArgumentError for zero or negative window values (previously undefined behaviour propagated from CacheMetrics.getRecentStats).CacheStatsDashboard.stream(Duration window, Duration interval) now throws ArgumentError for zero or negative interval values.Fixed spurious eviction in FIFO set() when updating an existing key.
set() when updating an existing key.Future in LFU/MRU eviction causing silent async errors.set() incorrectly resetting usage count and spuriously evicting when updating an existing key.CacheMetrics by capping stored latency samples.CacheMetrics/CacheMonitoring.Added project logo to README header for improved visual branding.
Added documentation for Cache algorithms to the README.
pubspec.yaml.Added the following badges to the README:
Renamed docs directory to doc to comply with pub.dev package layout convention.
docs directory to doc to comply with pub.dev package layout convention.lints, synchronized, and js packages.Updated the description field in pubspec.yaml to meet pub.dev requirements.
description field in pubspec.yaml to meet pub.dev requirements.Updated the description field in pubspec.yaml to provide a more specific and accurate explanation of the package's functionality.
description field in pubspec.yaml to provide a more specific and accurate explanation of the package's functionality.Your coding agent can read these notes before it upgrades. Set up the MCP server →