Cache Invalidation
UDIOT supports three invalidation strategies — exact key, glob pattern, and tag-based — through CacheInvalidator, TagRegistry, and PatternMatcher. Event-driven invalidation ties these strategies to domain events via CacheManager.setupEventInvalidation().
Key Concepts
TagRegistrymaintains two bidirectional maps:tag → Set<key>andkey → Set<tag>. Removing a key cleans up all tag mappings; removing a tag returns all associated keys.PatternMatcherconverts*wildcard patterns to regular expressions and filters an array of keys or delegates the sweep to a provider'sclear(pattern).CacheInvalidatororchestrates all three strategies and returns anInvalidationResultthat reports which keys were removed and how long it took.- Event-driven invalidation is configured on
CacheManagerusingsetupEventInvalidationand triggered by callinghandleEvent. This decouples business logic from cache management.
API Reference
TagRegistry
class TagRegistry {
register(key: string, tags: string[]): void
getKeysByTag(tag: string): string[]
getTagsForKey(key: string): string[]
removeKey(key: string): void
removeByTag(tag: string): string[] // returns removed keys
clear(): void
getAllTags(): string[]
getStats(): { totalKeys: number; totalTags: number; avgTagsPerKey: number }
}
PatternMatcher
class PatternMatcher {
static matches(key: string, pattern: string): boolean
static filter(keys: string[], pattern: string): string[]
static create(parts: (string | '*')[], separator?: string): string
static extractParts(key: string, separator?: string): string[]
static hasWildcard(pattern: string): boolean
static toRegex(pattern: string): RegExp
}
Pattern syntax: * matches any sequence of characters. Patterns are anchored at both ends (i.e., user:* matches user:123 but not prefix:user:123).
CacheInvalidator
class CacheInvalidator {
registerProvider(provider: ICacheProvider): void
registerTags(key: string, tags: string[]): void
invalidate(options: InvalidateOptions): Promise<InvalidationResult>
getTagRegistry(): TagRegistry
clear(): void
}
InvalidationResult:
| Field | Type | Description |
|---|---|---|
count |
number |
Number of keys invalidated |
keys |
string[] |
Keys that were removed |
duration |
number |
Time taken in milliseconds |
Event-driven invalidation on CacheManager
interface EventInvalidationConfig {
event: string;
keys?: string[] | ((payload: any) => string[]);
tags?: string[];
pattern?: string | ((payload: any) => string);
}
cacheManager.setupEventInvalidation(config: EventInvalidationConfig): void
cacheManager.handleEvent(eventName: string, payload: any): Promise<void>
When handleEvent is called, the manager resolves dynamic keys and pattern by invoking the function form with payload, then invalidates accordingly.
Basic Usage
Tag-based invalidation
import { CacheManager, setGlobalCacheManager } from '@tagna/udiot';
const cache = new CacheManager();
setGlobalCacheManager(cache);
// Store with tags
await cache.set('user:42', userData, { ttl: 300, tags: ['users', 'user:42'] });
// Invalidate all 'users' entries
await cache.invalidate({ tags: ['users'] });
Pattern-based invalidation
import { PatternMatcher } from '@tagna/udiot';
// Build a pattern for all keys of a given class/method
const pattern = PatternMatcher.create(['udiot', 'userservice', 'getuser', '*']);
// → 'udiot:userservice:getuser:*'
await cache.invalidate({ pattern });
Using CacheInvalidator directly
import { CacheInvalidator, MemoryCacheProvider } from '@tagna/udiot';
const provider = new MemoryCacheProvider();
const invalidator = new CacheInvalidator();
invalidator.registerProvider(provider);
invalidator.registerTags('product:7', ['products', 'featured']);
const result = await invalidator.invalidate({ tags: ['featured'] });
console.log(`Removed ${result.count} keys in ${result.duration.toFixed(2)} ms`);
Common Patterns
Event-driven invalidation when a domain event fires
import { CacheManager, setGlobalCacheManager } from '@tagna/udiot';
const cache = new CacheManager();
setGlobalCacheManager(cache);
// Register once at startup
cache.setupEventInvalidation({
event: 'user.updated',
keys: (payload: { userId: string }) => [`user:${payload.userId}`],
tags: ['users'],
});
cache.setupEventInvalidation({
event: 'product.price_changed',
pattern: (payload: { category: string }) =>
`udiot:productservice:getbycategory:${payload.category}:*`,
});
// Fire from your domain event handler:
async function onUserUpdated(event: { userId: string }) {
await cache.handleEvent('user.updated', event);
}
Inspecting the tag registry
import { CacheInvalidator } from '@tagna/udiot';
const invalidator = new CacheInvalidator();
// ... register providers and tags ...
const stats = invalidator.getTagRegistry().getStats();
console.log('Tags in use:', stats.totalTags);
console.log('Keys tracked:', stats.totalKeys);
console.log('Avg tags/key:', stats.avgTagsPerKey.toFixed(2));
Integration Notes
- The
CacheManagermaintains its own internaltagToKeysmap in addition to callinginvalidateByTagon each provider. This dual tracking ensures that cross-provider invalidation works even when a provider's own tag index differs. - Pattern-based clearing via
provider.clear(pattern)converts*to.*in a regular expression. The match is anchored at both ends, souser:*will not accidentally matchprefix:user:123. CacheInvalidatordoes not currently return the keys removed by pattern-based clearing becauseprovider.clear(pattern)does not expose a list of affected keys. Only tag-based and key-exact invalidations populateInvalidationResult.keys.
Pitfalls
- Registering the same event in multiple
setupEventInvalidationcalls is additive; all registered handlers fire. This is intentional but can lead to duplicatedeletecalls if keys are listed in both akeysarray and matched by a tag. TagRegistry.removeByTagremoves the tag entry and all its associated key entries. If a key had multiple tags, those other tags also lose the key from their sets, which prevents stale references.- Pattern matching uses
^and$anchors. Do not include regex metacharacters other than*in patterns; special characters are escaped before conversion.
From the canonical UDIOT public documentation.Try it in the playground ↗