Event System
RunCache provides a comprehensive event system that allows you to monitor and respond to various cache lifecycle events. This guide explains how to use the event system effectively in your applications.
Understanding the Event System
The event system allows you to:
Monitor cache operations like expiration, refetching, and invalidation
Execute custom logic when specific events occur
Implement logging, analytics, and debugging
Build reactive systems that respond to cache changes
RunCache supports both global event listeners (for all keys) and key-specific listeners (for specific keys or patterns).
Available Event Types
RunCache provides several event types you can subscribe to:
EXPIRE
Triggered when a cache entry expires
key
, ttl
, updatedAt
REFETCH
Triggered when a cache entry is refreshed
key
, metadata
REFETCH_FAILURE
Triggered when a cache refresh fails
key
, error
, metadata
TAG_INVALIDATION
Triggered when a cache entry is invalidated by tag
key
, tag
DEPENDENCY_INVALIDATION
Triggered when a cache entry is invalidated by dependency
key
, dependencyKey
Global Event Listeners
Global event listeners are triggered for all cache entries that match the specified event type:
Expiry Events
// Listen for all expiry events
RunCache.onExpiry((event) => {
console.log(`Cache key ${event.key} expired at ${new Date(event.updatedAt + event.ttl).toISOString()}`);
});
Refetch Events
// Listen for all successful refetch events
RunCache.onRefetch((event) => {
console.log(`Cache key ${event.key} was refreshed at ${new Date().toISOString()}`);
});
// Listen for all refetch failure events
RunCache.onRefetchFailure((event) => {
console.error(`Failed to refresh cache key ${event.key}:`, event.error.message);
});
Tag Invalidation Events
// Listen for all tag invalidation events
RunCache.onTagInvalidation((event) => {
console.log(`Cache key ${event.key} was invalidated by tag: ${event.tag}`);
});
Dependency Invalidation Events
// Listen for all dependency invalidation events
RunCache.onDependencyInvalidation((event) => {
console.log(`Cache key ${event.key} was invalidated due to dependency on: ${event.dependencyKey}`);
});
Key-Specific Event Listeners
Key-specific event listeners are triggered only for cache entries with keys that match the specified key or pattern:
Key-Specific Expiry Events
// Listen for expiry of a specific key
RunCache.onKeyExpiry("api-data", (event) => {
console.log(`API data cache expired`);
});
// Listen for expiry of keys matching a pattern
RunCache.onKeyExpiry("user:*:profile", (event) => {
console.log(`User profile ${event.key} expired`);
});
Key-Specific Refetch Events
// Listen for refetch of a specific key
RunCache.onKeyRefetch("weather-data", (event) => {
console.log(`Weather data was refreshed`);
});
// Listen for refetch failures of keys matching a pattern
RunCache.onKeyRefetchFailure("api:*", (event) => {
console.error(`API data ${event.key} refresh failed:`, event.error.message);
});
Key-Specific Tag Invalidation Events
// Listen for tag invalidation of keys matching a pattern
RunCache.onKeyTagInvalidation("user:*:profile", (event) => {
console.log(`User profile ${event.key} was invalidated by tag: ${event.tag}`);
});
Key-Specific Dependency Invalidation Events
// Listen for dependency invalidation of a specific key
RunCache.onKeyDependencyInvalidation("dashboard:main", (event) => {
console.log(`Main dashboard was invalidated due to dependency on: ${event.dependencyKey}`);
});
Managing Event Listeners
Removing All Event Listeners
To remove all event listeners:
// Remove all event listeners
RunCache.clearEventListeners();
Removing Specific Event Listeners
To remove listeners for a specific event type:
// Remove all expiry event listeners
RunCache.clearEventListeners({
event: EVENT.EXPIRE
});
Removing Key-Specific Listeners
To remove listeners for a specific key or pattern:
// Remove refetch listeners for a specific key
RunCache.clearEventListeners({
event: EVENT.REFETCH,
key: "api-data"
});
// Remove refetch failure listeners for keys matching a pattern
RunCache.clearEventListeners({
event: EVENT.REFETCH_FAILURE,
key: "api:*"
});
Practical Event System Use Cases
1. Logging and Monitoring
Use events to implement comprehensive logging:
// Set up logging for all major events
RunCache.onExpiry((event) => {
logger.info(`Cache expired: ${event.key}`);
});
RunCache.onRefetch((event) => {
logger.info(`Cache refreshed: ${event.key}`);
});
RunCache.onRefetchFailure((event) => {
logger.error(`Cache refresh failed: ${event.key}`, event.error);
});
RunCache.onTagInvalidation((event) => {
logger.info(`Cache invalidated by tag: ${event.key} (tag: ${event.tag})`);
});
RunCache.onDependencyInvalidation((event) => {
logger.info(`Cache invalidated by dependency: ${event.key} (depends on: ${event.dependencyKey})`);
});
2. Implementing Retry Logic
Use events to implement retry logic for failed refetches:
// Set up retry logic with exponential backoff
RunCache.onRefetchFailure((event) => {
const retryCount = (event.metadata?.retryCount || 0) + 1;
if (retryCount <= 3) {
const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff
console.log(`Retrying ${event.key} in ${delay}ms (attempt ${retryCount})`);
setTimeout(() => {
RunCache.refetch(event.key, {
metadata: { retryCount }
});
}, delay);
} else {
console.error(`Failed to refresh ${event.key} after ${retryCount} attempts`);
}
});
3. UI Updates
Use events to trigger UI updates when cache data changes:
// React component example
function UserProfile({ userId }) {
const [profile, setProfile] = useState(null);
useEffect(() => {
// Set up event listener for cache refreshes
RunCache.onKeyRefetch(`user:${userId}:profile`, () => {
// Update UI when profile is refreshed
RunCache.get(`user:${userId}:profile`)
.then(data => data && setProfile(JSON.parse(data)));
});
// Initial data fetch
RunCache.get(`user:${userId}:profile`)
.then(data => data && setProfile(JSON.parse(data)));
// Clean up listener on unmount
return () => {
RunCache.clearEventListeners({
event: EVENT.REFETCH,
key: `user:${userId}:profile`
});
};
}, [userId]);
// Render profile...
}
4. Cache Analytics
Use events to collect analytics about cache usage:
// Set up analytics tracking
let metrics = {
expiryCount: 0,
refetchCount: 0,
refetchFailureCount: 0,
tagInvalidationCount: 0,
dependencyInvalidationCount: 0
};
RunCache.onExpiry(() => metrics.expiryCount++);
RunCache.onRefetch(() => metrics.refetchCount++);
RunCache.onRefetchFailure(() => metrics.refetchFailureCount++);
RunCache.onTagInvalidation(() => metrics.tagInvalidationCount++);
RunCache.onDependencyInvalidation(() => metrics.dependencyInvalidationCount++);
// Report metrics periodically
setInterval(() => {
console.log('Cache metrics:', metrics);
// Send metrics to monitoring system
sendMetricsToMonitoring(metrics);
// Reset counters
metrics = {
expiryCount: 0,
refetchCount: 0,
refetchFailureCount: 0,
tagInvalidationCount: 0,
dependencyInvalidationCount: 0
};
}, 60000); // Every minute
5. Cascading Updates
Use events to implement custom cascading update logic:
// When user data is refreshed, also refresh related data
RunCache.onKeyRefetch('user:*:profile', async (event) => {
const userId = event.key.split(':')[1];
// Refresh related data
await RunCache.refetch(`user:${userId}:recommendations`);
await RunCache.refetch(`user:${userId}:activity`);
});
Event Metadata
You can attach custom metadata to cache entries, which will be included in event objects:
// Set cache entry with metadata
await RunCache.set({
key: 'api-data',
value: JSON.stringify({ results: [...] }),
metadata: {
source: 'external-api',
version: '1.0',
timestamp: Date.now()
}
});
// Access metadata in event handlers
RunCache.onKeyExpiry('api-data', (event) => {
console.log(`API data expired. Source: ${event.metadata.source}, Version: ${event.metadata.version}`);
});
Best Practices for Event Handling
1. Keep Event Handlers Lightweight
Event handlers should be fast and non-blocking:
// Good - lightweight handler
RunCache.onExpiry((event) => {
console.log(`Cache expired: ${event.key}`);
});
// Avoid - heavy processing in handler
RunCache.onExpiry(async (event) => {
// DON'T do this - blocks the event loop
await heavyProcessing();
await networkRequest();
});
If you need to perform heavy work, delegate it to a separate process or queue.
2. Handle Errors in Event Handlers
Always handle errors in event handlers to prevent unhandled exceptions:
// Good - error handling
RunCache.onRefetch((event) => {
try {
processRefetchEvent(event);
} catch (error) {
console.error('Error in refetch handler:', error);
}
});
3. Use Specific Key Patterns
Use specific key patterns to minimize unnecessary event processing:
// Too broad - triggers for all keys
RunCache.onKeyExpiry('*', (event) => {
// This will run for EVERY expiry
});
// Better - more specific pattern
RunCache.onKeyExpiry('api:*', (event) => {
// This will only run for API-related expirations
});
4. Clean Up Event Listeners
Remove event listeners when they're no longer needed:
// In a component or module initialization
function initializeModule() {
// Set up event listeners
RunCache.onKeyRefetch('module-data', handleRefetch);
// Return cleanup function
return () => {
RunCache.clearEventListeners({
event: EVENT.REFETCH,
key: 'module-data'
});
};
}
// Later, when shutting down
const cleanup = initializeModule();
// When module is unloaded
cleanup();
5. Avoid Circular Event Handling
Be careful not to create circular event handling patterns:
// Dangerous - potential circular handling
RunCache.onKeyRefetch('data-a', () => {
RunCache.refetch('data-b');
});
RunCache.onKeyRefetch('data-b', () => {
RunCache.refetch('data-a'); // Creates a loop!
});
Next Steps
Now that you understand the event system, explore these related topics:
Tag-based Invalidation - Learn about invalidating groups of related cache entries
Dependency Tracking - Understand how to establish relationships between cache entries
Middleware - Explore how to intercept and transform cache operations
Resource Management - Learn about memory management and cleanup
Last updated