cachemesh 2.0.0
cachemesh: ^2.0.0 copied to clipboard
Result-first caching and data orchestration layer for Dart and Flutter apps. Memory-first cache with policies, single-flight dedup, and reactive watch streams.
Changelog #
2.0.0 #
Unified Data Engine — a major version, but fully backward compatible. Every 1.x call site continues to work; v2.0.0 layers a higher-level pipeline, reactive composition, and dev tooling on top.
Unified data pipeline #
-
CacheEndpoint<P, T>— bundle a key-builder, fetcher, and policy once; reuse everywhere. Replaces the repeatedcache.get(key: ..., fetch: ...)ceremony at call sites:final users = cache.endpoint<int, User>( key: (id) => 'user:$id', fetch: (id) => api.fetchUser(id), policy: CachePolicy.staleWhileRevalidate, ); await users(42); users.watch(42); users.invalidateAll(); -
AuthenticatedCacheEndpoint<P, T>— likeCacheEndpointbut routes every fetch through the configuredTokenKeeperAdapter(defaults toCacheScope.user). Wire yourresilifycalls +token_keeperonce and the rest of the app just seesawait me(id). -
Endpoints track
ownedKeyssoinvalidateAll()only drops their entries, not the whole cache.
Reactive data graph #
cache.combineLatest<R>(keys, combine)— single-subscription stream that emits whenever any underlying key updates. Failures propagate; missing values delay the first emission until every key has data. Combine-thrown exceptions are wrapped asFailure.cache.peekCombined<R>(keys, combine)— synchronous combined read for seeding UI before subscribing.
Dev tooling & introspection #
cache.snapshot()—Map<String, CacheState>of every entry, no fetch.cache.debugDump()— pretty-printed multi-line summary including metrics, active user, persistence flag, and per-entry freshness/scope/age.cache.size,cache.allKeys,cache.contains(key)— zero-cost introspection.cache.touch(key, {ttl})— refreshcreatedAt(and optionally swap TTL) without re-fetching, for when out-of-band evidence confirms freshness.
Bug fixes #
Cache.dispose()now awaitsflush()first so persistence writes in flight at shutdown actually land on disk. Previously, callingdispose()immediately after a write could abandon the pending op.clearScope(CacheScope.global)documented as a no-op: global entries aren't tracked in the scope index (absence is global). Useclear()to drop globals too.
Notes #
- Listed as a major version because of the architectural additions
(endpoint as the new primary abstraction). The 1.x surface —
get,refresh,watch,invalidate,setActiveUser, etc. — is untouched. - Memory cost of v2.0.0: each endpoint keeps a
Set<String>of its owned keys forinvalidateAll. Skip the endpoint API and the cost is zero.
1.2.0 #
Persistence & offline support — no breaking changes.
- Pluggable disk persistence: new
PersistentStoreasync interface plus two implementations —InMemoryPersistentStore(tests/demos) andJsonFileStore(file-backed,dart:io).JsonFileStorelives in a separate entry point,package:cachemesh/cachemesh_io.dart, so the core library stays web-compatible. Bring your own backend (Hive, sqflite, shared_preferences) by implementingPersistentStore. - Typed serialization: register a
CacheCodec<T>per key-prefix viaCache.registerCodec. Only keys with a matching codec are persisted; the longest matching prefix wins. Write-through is fire-and-forget and never blocks the read path — callCache.flush()for a durability checkpoint. - Hydration:
Cache.hydrate()restores persisted entries into memory at startup, skipping (and pruning) expired records. User-scoped records remember their owner, so a latersetActiveUsercorrectly clears another account's restored data. - Offline-first: persistence +
CachePolicy.networkFirst(fall back to cache on failure) +cacheFailuresgives restart-safe, network-tolerant reads. Seeexample/persistence_offline_example.dart. - Cache metrics:
Cache.metricsexposes aCacheMetricssnapshot (hits, misses, writes, refreshes, errors, evictions, pluslookupsandhitRate).Cache.resetMetrics()zeroes the counters. Cache.invalidate/clearnow propagate to the persistent store, andCache.isPersistentreports whether a store is configured.- Behaviour refinement:
setActiveUsernow clears user-scoped entries that don't belong to the incoming user (previously only the immediately-preceding user). This is strictly safer and makes multi-account hydration correct.
1.1.0 #
Ecosystem integration — no breaking changes.
ResilifySource<T>typedef: alias forFetcher<T>so resilify pipelines read naturally at call sites. Any function returningFuture<Result<T>>plugs straight intoCache.get— no manual try/catch wrapping required.TokenKeeperAdapterinterface: bridgestoken_keeper'swithValidTokenflow into the cache. Pass an adapter viaCache(tokenKeeper: ...)and use the newCache.getAuthenticatedmethod — fetchers receive a valid token and the adapter is responsible for refreshing on unauthorized once.- Cache scopes: new
CacheScopeenum (global/session/user).Cache.get,refresh, andprefetchaccept an optionalscope:.Cache.scopeOf(key)reports the recorded scope. User-scoped reads requireCache.setActiveUser(id)to be called first. - Auto invalidation hooks:
Cache.setActiveUser(userId)— clears entries belonging to the previous user when the active user changes; no-op if unchanged.Cache.endSession()— drops both session- and user-scoped entries and unsets the active user. Call from your logout flow.Cache.clearScope(scope)— fine-grained, drop a single scope.
CacheLogger.onScopeCleared: new lifecycle event with the reason (setActiveUser,endSession,clearScope:<name>) and the list of removed keys. Only fires when there is at least one key to report.Cache.invalidateandCache.clearnow also clean up scope bookkeeping.
1.0.2 #
Smarter Result integration — no breaking changes.
- Failure-aware caching: pass
cacheFailures: truetoCacheor to individualget/refreshcalls to store failures in the cache (with TTL). The cached failure is returned on the next lookup instead of hitting the network. A successful re-fetch clears the stored failure automatically.Cache.hasCachedFailure(key)lets you check the state without fetching. - Retry hooks: new
RetryOptionstype (maxAttempts,retryWhen,delay). Set a cache-wide default viaCache(retryOptions: ...)and override per call.RetryOptions.noRetry(single attempt) is the default, so existing code is unaffected. Built-inretryWhenpredicate pattern makes it easy to skip retries on specific error types (e.g. auth errors). - Smarter SWR revalidation:
staleWhileRevalidatenow only kicks off a background refresh when the cached entry is actually stale. PassalwaysRevalidate: trueto restore the pre-1.0.2 behaviour. - Cleaner failure propagation: returned
Failureand thrown exceptions both flow through the same retry loop andCacheLogger.onErrorcall; the original error type and stack trace are preserved throughout.
1.0.1 #
Stability & observability — no breaking changes.
- Cache logger: pluggable
CacheLogger(hits, misses, writes, refreshes, invalidations, clears, errors) withRefreshSourcefor filtering. IncludesPrintCacheLoggerfor quick wiring. - Cache state insights: new
Cache.inspect<T>(key)returns aCacheState<T>snapshot —isPresent,isFresh,isStale,age,timeToExpiry,expiresAt. - Safer expiry: SWR no longer kicks off a redundant background refresh while one is already in flight. Tightens single-flight semantics.
- Better error propagation: fetcher failures (returned
Failureor thrown exceptions) are routed throughCacheLogger.onErrorwith their original stack trace.
1.0.0 #
Initial release.
Result<T>sealed type (Success<T>/Failure<T>) withfoldandmap.Cachewith five policies:cacheFirst,networkFirst,staleWhileRevalidate,networkOnly,cacheOnly.- In-memory
MemoryCacheStorewith per-entry TTL. - Single-flight deduplication of concurrent fetches per key.
- Reactive
watch(key)broadcast streams. - Manual control:
refresh,prefetch,invalidate,clear,peek. - Pluggable
CacheStoreinterface (disk adapters land in 1.2.0).