feat(cacheable): tag-based invalidation via CacheTags#1655
Merged
Conversation
Implements tag-based invalidation for Cacheable (#1640) on top of the CacheTags service in @cacheable/utils: - set(key, value, { ttl, tags }) associates entries with tags (back-compat with ttl as the third argument) - setMany items accept per-item tags - invalidateTag / invalidateTags bump tag versions in constant time - stale entries are detected lazily on get / getMany, removed from both stores, and a delete is published via sync when enabled - tag metadata lives in the secondary store when configured so invalidations are shared across instances; tags: true option lets read-only instances honor invalidations - getTags(key), tags service getter, and tagsEnabled introspection - utils: add CacheTags.isKeyStale (safe for untagged keys) and CacheTags.getKeyTags https://claude.ai/code/session_01CEUJ2ZvfykEnuUfL43PobY
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1655 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 27 27
Lines 2939 3069 +130
Branches 657 678 +21
==========================================
+ Hits 2939 3069 +130 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces tag-based invalidation to the cacheable package, allowing cache entries to be associated with tags and invalidated in constant time. The feedback focuses on compatibility and performance optimizations, specifically recommending the use of Keyv's getMany and deleteMany APIs to batch operations (such as fetching tag versions and deleting stale keys or tag snapshots) rather than executing them individually or concurrently via Promise.all.
Addresses review feedback: - add CacheTags.removeKeys for batched snapshot deletion via deleteMany - removeKeyTags now uses a single batched delete instead of per-key deletes - getManyRaw collects stale keys and removes them with one deleteMany call https://claude.ai/code/session_01CEUJ2ZvfykEnuUfL43PobY
jaredwray
commented
Jun 12, 2026
- CacheTags now owns an enabled state: read methods no-op while disabled and tag writes (setKeyTags, invalidateTag, invalidateTags) auto-enable the service, so integrations are self-contained - add CacheTags.getStaleKeys for batch staleness checks (two store reads total) and use it in getManyRaw - move non-blocking (fire-and-forget) handling into CacheTags via a nonBlocking option on setKeyTags/removeKey/removeKeys with an onError callback for failures - rename getKeyTags to getTags - Cacheable creates the tag service by default in the constructor as _tags, exposes it via the tags getter, and drops the duplicated invalidateTag/invalidateTags/getTags/tagsEnabled members in favor of cacheable.tags.* https://claude.ai/code/session_01CEUJ2ZvfykEnuUfL43PobY
jaredwray
commented
Jun 12, 2026
Tag writes no longer auto-enable the CacheTags service. Auto-enable only flipped the local instance, which gives inconsistent behavior across distributed instances sharing a store. Tags must now be enabled explicitly via the tags: true option or tags.enabled = true on every instance; while disabled, all tag operations are no-ops. https://claude.ai/code/session_01CEUJ2ZvfykEnuUfL43PobY
jaredwray
commented
Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements tag-based invalidation for
Cacheable, closing out the integration step from #1640 on top of theCacheTagsservice merged in #1646.API
All tag functionality lives on the
tagsservice (aCacheTagsinstance from@cacheable/utils), created by default in the constructor. Tags must be explicitly enabled withtags: true(orcache.tags.enabled = true):setkeeps full back-compat withttlas the third argument; the options form also honors a per-callnonBlockingoverride (the existing-but-unusedSetOptionstype).How it works
invalidateTagbumps the tag's version counter; no scan-and-delete.get/getMany, tagged entries whose snapshot no longer matches the live versions are treated as misses and removed from both primary and secondary stores, with adeletepublished via sync when enabled — so other instances purge their L1 copies too.getManyusesCacheTags.getStaleKeys, which checks any number of keys with two batched store reads.CacheTagsservice is self-contained: it owns itsenabledstate and handles non-blocking fire-and-forget writes internally, reporting failures via anonErrorcallback thatCacheablewires to itserrorevent.tags: trueon every instance that shares the store.@cacheable/utilsadditions (CacheTags)enabledproperty — all methods no-op while disabled; must be explicitly enabled.isKeyStale(key)—trueonly when a snapshot exists and a tag version changed, so it is safe to call for every lookup including never-tagged keys (unlikeisKeyFresh, which reports unknown keys as not fresh).getStaleKeys(keys)— batch staleness check using two store reads total.getTags(key)— returns a key's tags orundefined.removeKeys(keys)— batched snapshot removal viadeleteMany.nonBlockingoption onsetKeyTags/removeKey/removeKeyswith anonErrorconstructor callback for fire-and-forget failures.Tests & docs
packages/cacheable/test/tags.test.ts(single store, shared secondary across instances, non-blocking mode, snapshot cleanup, TTL expiry, error events) and 24CacheTagstests inpackages/utils/test/cache-tags.test.ts. All new code paths are covered.Tag Based Invalidationin cacheable;enabled,isKeyStale,getStaleKeys,getTags,removeKeys, and non-blocking docs in utils), plus options/API listings.Note: tag version counters are intentionally stored without TTL (they are the source of truth); the lifecycle/cleanup concern raised late in #1640 is orthogonal to this integration and can be tracked separately.
Closes #1640
https://claude.ai/code/session_01CEUJ2ZvfykEnuUfL43PobY