Skip to content
Warlock.js v4

Event System

The cache event system provides comprehensive observability into every cache operation. Listen to hit, miss, set, removed, and other events to monitor cache behavior.

EventWhen FiredEvent Data
hitValue retrieved from cache{ key, value, driver }
missValue not found or expired{ key, driver }
setValue stored in cache{ key, value, ttl, driver }
removedKey removed from cache{ key, driver }
flushedEntire cache cleared{ driver }
expiredKey expired (TTL reached){ key, driver }
connectedDriver connected{ driver }
disconnectedDriver disconnected{ driver }
errorError occurred{ error, driver, key? }

For aggregate observability — hit rate, latency percentiles, per-driver breakdowns — use the built-in collector instead of wiring everything by hand: see Built-in Metrics. Use the raw event listeners below when you need per-event reactions (alerts, audit logs) that the collector doesn’t cover.

Listen to hit and miss events to track cache performance:

import { cache } from "@warlock.js/cache";
let hitCount = 0;
let missCount = 0;
cache.on("hit", ({ key }) => {
hitCount++;
console.log(`Cache hit: ${key}`);
});
cache.on("miss", ({ key }) => {
missCount++;
console.log(`Cache miss: ${key}`);
});
// Log metrics periodically
setInterval(() => {
const total = hitCount + missCount;
const hitRate = total > 0 ? (hitCount / total * 100).toFixed(2) : 0;
console.log(`Hit rate: ${hitRate}% (${hitCount} hits, ${missCount} misses)`);
}, 60000);

For external metrics services:

cache.on("hit", ({ key }) => {
metricsService.increment("cache.hits", { key });
});
cache.on("miss", ({ key }) => {
metricsService.increment("cache.misses", { key });
});
// Global listeners (listen to all drivers)
cache.on("hit", () => {
console.log("Hit on any driver");
});
// Driver-specific listeners
const redisDriver = await cache.driver("redis");
redisDriver.on("hit", () => {
console.log("Hit only on Redis driver");
});

Global listeners are automatically attached to all drivers when they’re loaded or switched.

import { cache } from "@warlock.js/cache";
const metrics = {
productHits: 0,
productMisses: 0,
cartHits: 0,
cartMisses: 0
};
cache.on("hit", ({ key }) => {
if (key.startsWith("products.")) metrics.productHits++;
if (key.startsWith("cart.")) metrics.cartHits++;
});
cache.on("miss", ({ key }) => {
if (key.startsWith("products.")) metrics.productMisses++;
if (key.startsWith("cart.")) metrics.cartMisses++;
});
// Report metrics every minute
setInterval(() => {
console.log("Product Cache:", {
hits: metrics.productHits,
misses: metrics.productMisses,
hitRate: (metrics.productHits / (metrics.productHits + metrics.productMisses) * 100).toFixed(2) + "%"
});
}, 60000);
import { cache } from "@warlock.js/cache";
cache.on("error", ({ error, driver, key }) => {
console.error(`Cache error on ${driver}:`, {
key,
error: error.message
});
// Send to error tracking service
errorTracker.report(error, { driver, key });
});
if (process.env.NODE_ENV === "development") {
cache.on("hit", ({ key }) => {
console.log(`[Cache] HIT: ${key}`);
});
cache.on("miss", ({ key }) => {
console.log(`[Cache] MISS: ${key}`);
});
cache.on("set", ({ key, ttl }) => {
console.log(`[Cache] SET: ${key} (expires in ${ttl}s)`);
});
}

Listen to an event once, then automatically remove the listener:

import { cache } from "@warlock.js/cache";
// Wait for cache to connect
cache.once("connected", () => {
console.log("Cache connected! Ready to use.");
// This listener is automatically removed after firing
});

Remove specific event listeners:

import { cache } from "@warlock.js/cache";
const handler = ({ key }) => {
console.log(`Key accessed: ${key}`);
};
// Register
cache.on("hit", handler);
cache.on("miss", handler);
// Later, remove
cache.off("hit", handler);
cache.off("miss", handler);

Event handlers can be async - the cache waits for them to complete:

import { cache } from "@warlock.js/cache";
cache.on("set", async ({ key, value, ttl }) => {
// Async operation in event handler
await analytics.trackAsync("cache_write", { key, ttl });
await updateCacheMetrics({ key, ttl });
});
import { cache } from "@warlock.js/cache";
class CacheMonitor {
private stats = {
hits: 0,
misses: 0,
sets: 0,
errors: 0
};
constructor() {
cache.on("hit", () => this.stats.hits++);
cache.on("miss", () => this.stats.misses++);
cache.on("set", () => this.stats.sets++);
cache.on("error", () => this.stats.errors++);
}
getStats() {
const total = this.stats.hits + this.stats.misses;
return {
hits: this.stats.hits,
misses: this.stats.misses,
sets: this.stats.sets,
errors: this.stats.errors,
hitRate: total > 0 ? (this.stats.hits / total * 100).toFixed(2) + "%" : "0%",
totalRequests: total
};
}
reset() {
this.stats = { hits: 0, misses: 0, sets: 0, errors: 0 };
}
}
const monitor = new CacheMonitor();
setInterval(() => {
console.log("Cache Stats:", monitor.getStats());
}, 60000);

All events provide a driver field indicating which driver emitted the event:

import type { CacheEventData } from "@warlock.js/cache";
cache.on("hit", (data: CacheEventData) => {
console.log(`Driver: ${data.driver}`);
console.log(`Key: ${data.key}`);
console.log(`Value: ${data.value}`);
});
cache.on("error", (data: CacheEventData) => {
console.error(`Error on ${data.driver}:`, data.error);
});
  • Event handlers are non-blocking: They run asynchronously and don’t block cache operations
  • Multiple handlers: You can register multiple handlers for the same event
  • Error handling: Errors in event handlers are caught and logged, but don’t affect cache operations
  • Overhead: Minimal - events are emitted efficiently with minimal performance impact
  1. Register listeners early: Set up event listeners before cache operations
  2. Keep handlers fast: Event handlers should complete quickly — offload heavy work to background jobs
  3. Handle errors: Always handle errors in event handlers gracefully
  4. Use once() for one-time operations: Use once() instead of manually removing listeners
  5. Filter by key pattern: Listen to specific key patterns to reduce noise in production
  • Events not firing? Ensure listeners are registered before cache operations occur
  • Too many events? Filter events in your handlers or register listeners only in development
  • Performance issues? Ensure event handlers are fast — consider using a message queue for heavy operations

See Best Practices for more monitoring patterns.