tessl install tessl/npm-cache-manager@7.2.0Cache Manager for Node.js with support for multi-store caching, background refresh, and Keyv-compatible storage adapters
Standard cache operations for getting, setting, and deleting single or multiple keys, with TTL support and batch operations.
Retrieves a value from the cache by key. Returns undefined if the key is not found or has expired. In multi-store setups, searches stores in priority order (first to last) and returns the first value found.
/**
* Get a value from cache
* @param key - Cache key to retrieve
* @returns The cached value or undefined if not found/expired
* @throws Never throws on cache miss; throws only on critical store errors
*/
get<T>(key: string): Promise<T | undefined>;Behavioral Details:
undefined (not null) for cache misses or expired keysT ensures type-safe retrievalUsage Examples:
import { createCache } from 'cache-manager';
const cache = createCache();
// Set a value
await cache.set('user:123', { name: 'Alice', age: 30 });
// Get the value with type safety
const user = await cache.get<{ name: string; age: number }>('user:123');
console.log(user); // { name: 'Alice', age: 30 }
// Get non-existent key
const missing = await cache.get('user:999');
console.log(missing); // undefined
// Get expired key
await cache.set('temp', 'data', 1000); // 1 second TTL
await new Promise(resolve => setTimeout(resolve, 1100));
const expired = await cache.get('temp');
console.log(expired); // undefinedEdge Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Empty string key (valid)
await cache.set('', 'empty key value');
console.log(await cache.get('')); // 'empty key value'
// Numeric values
await cache.set('number', 0);
console.log(await cache.get('number')); // 0 (not undefined)
// Boolean false
await cache.set('flag', false);
console.log(await cache.get('flag')); // false (not undefined)
// Null value (serialized and stored)
await cache.set('nullable', null);
console.log(await cache.get('nullable')); // null
// Complex nested objects
await cache.set('nested', {
a: { b: { c: [1, 2, { d: 'deep' }] } }
});
const nested = await cache.get('nested');
console.log(nested?.a.b.c[2].d); // 'deep'
// Arrays
await cache.set('array', [1, 2, 3]);
console.log(await cache.get<number[]>('array')); // [1, 2, 3]
// Date objects (serialization depends on store)
await cache.set('date', new Date('2024-01-01'));
const date = await cache.get('date');
console.log(date); // May be Date object or ISO string, depends on serializerMulti-Store Behavior:
import { createCache } from 'cache-manager';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';
import { CacheableMemory } from 'cacheable';
const cache = createCache({
stores: [
new Keyv({ store: new CacheableMemory() }), // L1: Memory
new Keyv({ store: new KeyvRedis('redis://localhost:6379') }), // L2: Redis
],
});
// Set in both stores
await cache.set('key', 'value');
// Clear only memory store directly
await cache.stores[0].clear();
// Get still succeeds (finds in Redis, promotes to memory)
const value = await cache.get('key');
console.log(value); // 'value' (from Redis)
// Now available in memory too
const value2 = await cache.get('key');
// Retrieved from memory (faster)Error Handling:
import { createCache } from 'cache-manager';
const cache = createCache();
// Listen for errors
cache.on('get', ({ key, value, error }) => {
if (error) {
console.error(`Get failed for ${key}:`, error);
// May want to alert, retry, or use fallback
}
});
try {
const value = await cache.get('key');
// Handle cache miss vs error
if (value === undefined) {
console.log('Cache miss - fetch from source');
}
} catch (error) {
console.error('Critical cache error:', error);
// Fallback: fetch from source without cache
}Retrieves multiple values from the cache by keys. Returns an array where each element is the cached value or undefined if not found or expired. In multi-store setups, efficiently fetches missing keys from subsequent stores.
/**
* Get multiple values from cache
* @param keys - Array of cache keys to retrieve
* @returns Array of cached values (undefined for missing/expired keys)
* @throws Only on critical store errors (all keys fail)
*/
mget<T>(keys: string[]): Promise<Array<T | undefined>>;Behavioral Details:
get() callsT (use union types if keys have different types)Usage Examples:
import { createCache } from 'cache-manager';
const cache = createCache();
// Set multiple values
await cache.set('product:1', { name: 'Widget', price: 9.99 });
await cache.set('product:2', { name: 'Gadget', price: 19.99 });
// Get multiple values at once
const products = await cache.mget<{ name: string; price: number }>([
'product:1',
'product:2',
'product:3', // Doesn't exist
]);
console.log(products);
// [
// { name: 'Widget', price: 9.99 },
// { name: 'Gadget', price: 19.99 },
// undefined
// ]
// Filter out missing values
const existingProducts = products.filter((p): p is { name: string; price: number } => p !== undefined);
console.log(existingProducts.length); // 2Edge Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Empty array
const empty = await cache.mget([]);
console.log(empty); // []
// Single key (still returns array)
const single = await cache.mget(['key1']);
console.log(single); // [undefined] or [value]
// Duplicate keys
await cache.set('dup', 'value');
const dups = await cache.mget(['dup', 'dup', 'dup']);
console.log(dups); // ['value', 'value', 'value']
// Mixed types (use union types)
await cache.set('str', 'string');
await cache.set('num', 123);
const mixed = await cache.mget<string | number>(['str', 'num']);
console.log(mixed); // ['string', 123]
// Large batch (performance consideration)
const largeKeys = Array.from({ length: 1000 }, (_, i) => `key:${i}`);
const results = await cache.mget(largeKeys);
// Efficiently retrieved in single batch per storePerformance Pattern:
import { createCache } from 'cache-manager';
const cache = createCache();
// INEFFICIENT: Multiple individual gets
const inefficient = [];
for (const id of [1, 2, 3, 4, 5]) {
inefficient.push(await cache.get(`user:${id}`));
}
// EFFICIENT: Single mget
const efficient = await cache.mget([
'user:1', 'user:2', 'user:3', 'user:4', 'user:5'
]);
// With fallback for missing values
const userIds = [1, 2, 3, 4, 5];
const cached = await cache.mget(userIds.map(id => `user:${id}`));
const users = await Promise.all(
cached.map(async (user, idx) => {
if (user !== undefined) return user;
// Fetch missing from database
const userId = userIds[idx];
const freshUser = await db.users.findById(userId);
await cache.set(`user:${userId}`, freshUser, 60000);
return freshUser;
})
);Multi-Store Optimization:
import { createCache } from 'cache-manager';
import { Keyv } from 'keyv';
import { CacheableMemory } from 'cacheable';
import KeyvRedis from '@keyv/redis';
const cache = createCache({
stores: [
new Keyv({ store: new CacheableMemory() }),
new Keyv({ store: new KeyvRedis('redis://localhost:6379') }),
],
});
await cache.mset([
{ key: 'a', value: 1 },
{ key: 'b', value: 2 },
{ key: 'c', value: 3 },
]);
// Clear memory only
await cache.stores[0].clear();
// mget efficiently:
// 1. Checks memory (finds nothing)
// 2. Checks Redis (finds all)
// 3. Promotes to memory (backfill)
const values = await cache.mget(['a', 'b', 'c']);
console.log(values); // [1, 2, 3]Sets a key-value pair in the cache with an optional TTL (time-to-live) in milliseconds. If no TTL is provided, uses the default TTL from cache configuration. In multi-store setups, sets the value in all stores.
/**
* Set a key-value pair in cache
* @param key - Cache key
* @param value - Value to cache (must be serializable)
* @param ttl - Optional TTL in milliseconds (overrides default)
* @returns The value that was set
* @throws On serialization error or critical store failure
*/
set<T>(key: string, value: T, ttl?: number): Promise<T>;Behavioral Details:
nonBlocking mode and some stores are slow)Usage Examples:
import { createCache } from 'cache-manager';
const cache = createCache({ ttl: 60000 }); // Default 60 second TTL
// Set with default TTL (60 seconds)
await cache.set('session:abc', { userId: 123, role: 'admin' });
// Set with custom TTL (5 seconds)
await cache.set('temp:xyz', 'temporary data', 5000);
// Set without expiration (if store supports it)
await cache.set('permanent', 'data', Infinity);
// Chaining pattern
const result = await cache.set('config:theme', 'dark');
console.log(result); // 'dark'
console.log(await cache.get('config:theme')); // 'dark'Edge Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Zero TTL (immediate expiration)
await cache.set('instant', 'gone', 0);
console.log(await cache.get('instant')); // undefined (already expired)
// Negative TTL (treated as expired)
await cache.set('past', 'value', -1000);
console.log(await cache.get('past')); // undefined
// Very large TTL
await cache.set('longterm', 'value', 365 * 24 * 60 * 60 * 1000); // 1 year
// Overwriting with different type (type safety lost at runtime)
await cache.set('key', 'string');
await cache.set('key', 123); // Overwrites
console.log(await cache.get('key')); // 123
// Empty string value (valid)
await cache.set('empty', '');
console.log(await cache.get('empty')); // '' (not undefined)
// Undefined value (behavior depends on store)
await cache.set('undef', undefined);
console.log(await cache.get('undef')); // May be undefined (not distinguishable from miss)
// Objects with circular references (will throw serialization error)
const circular: any = { a: 1 };
circular.self = circular;
try {
await cache.set('circular', circular);
} catch (error) {
console.error('Serialization error:', error);
}
// Functions (not serializable)
try {
await cache.set('func', () => 'hello');
} catch (error) {
console.error('Cannot cache functions:', error);
}
// Large values (may exceed store limits)
const largeValue = 'x'.repeat(10 * 1024 * 1024); // 10MB
try {
await cache.set('large', largeValue);
} catch (error) {
console.error('Value too large:', error);
}TTL Patterns:
import { createCache } from 'cache-manager';
const cache = createCache({ ttl: 60000 });
// Short-lived: OTP codes
await cache.set('otp:user123', '123456', 5 * 60 * 1000); // 5 minutes
// Medium-lived: Session data
await cache.set('session:abc', { userId: 1 }, 30 * 60 * 1000); // 30 minutes
// Long-lived: Configuration
await cache.set('config:app', { theme: 'dark' }, 24 * 60 * 60 * 1000); // 24 hours
// Refreshed on access (use wrap for auto-refresh)
async function getConfig() {
return await cache.wrap('config', async () => {
return await db.config.get();
}, 60000, 10000); // 60s TTL, refresh below 10s
}Multi-Store Consistency:
import { createCache } from 'cache-manager';
import { Keyv } from 'keyv';
import { CacheableMemory } from 'cacheable';
import KeyvRedis from '@keyv/redis';
// Blocking mode (default): waits for all stores
const blockingCache = createCache({
stores: [
new Keyv({ store: new CacheableMemory() }),
new Keyv({ store: new KeyvRedis('redis://localhost:6379') }),
],
nonBlocking: false,
});
await blockingCache.set('key', 'value');
// Guaranteed to be in both stores when promise resolves
// Non-blocking mode: returns after first store
const nonBlockingCache = createCache({
stores: [
new Keyv({ store: new CacheableMemory() }),
new Keyv({ store: new KeyvRedis('redis://localhost:6379') }),
],
nonBlocking: true,
});
await nonBlockingCache.set('key', 'value');
// May only be in memory store immediately; Redis write continues in backgroundError Handling:
import { createCache } from 'cache-manager';
const cache = createCache();
cache.on('set', ({ key, value, error }) => {
if (error) {
console.error(`Failed to set ${key}:`, error);
// Options: retry, alert, fallback
}
});
try {
await cache.set('key', 'value');
} catch (error) {
console.error('Critical set error:', error);
// Decide: throw to caller or continue without cache
}Sets multiple key-value pairs in the cache. Each item can have an optional TTL in milliseconds. In multi-store setups, sets all values in all stores. Returns the list of items that were set.
/**
* Set multiple key-value pairs in cache
* @param list - Array of items to cache, each with key, value, and optional ttl
* @returns Array of items that were set (same as input)
* @throws On serialization error or critical store failure
*/
mset<T>(
list: Array<{ key: string; value: T; ttl?: number }>
): Promise<Array<{ key: string; value: T; ttl?: number }>>;Behavioral Details:
set() callsT (use union types if needed)Usage Examples:
import { createCache } from 'cache-manager';
const cache = createCache({ ttl: 60000 });
// Set multiple values with individual TTLs
await cache.mset([
{ key: 'user:1', value: { name: 'Alice' }, ttl: 30000 }, // 30 seconds
{ key: 'user:2', value: { name: 'Bob' } }, // Uses default TTL
{ key: 'user:3', value: { name: 'Charlie' }, ttl: 120000 }, // 2 minutes
]);
// Retrieve to verify
const users = await cache.mget(['user:1', 'user:2', 'user:3']);
console.log(users); // [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }]Edge Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Empty array
const empty = await cache.mset([]);
console.log(empty); // []
// Single item (still use array)
await cache.mset([{ key: 'single', value: 'value' }]);
// Duplicate keys (last one wins)
await cache.mset([
{ key: 'dup', value: 'first' },
{ key: 'dup', value: 'second' },
]);
console.log(await cache.get('dup')); // 'second'
// Mixed TTLs including zero
await cache.mset([
{ key: 'a', value: 1, ttl: 60000 },
{ key: 'b', value: 2, ttl: 0 }, // Immediately expired
{ key: 'c', value: 3 }, // Default TTL
]);
console.log(await cache.get('b')); // undefined (expired)
// Large batch
const largeBatch = Array.from({ length: 10000 }, (_, i) => ({
key: `key:${i}`,
value: { id: i, data: `value ${i}` },
ttl: 60000,
}));
await cache.mset(largeBatch);
// Efficiently sets all in batchBulk Loading Pattern:
import { createCache } from 'cache-manager';
const cache = createCache();
// Load data from database and cache it
async function warmUpCache() {
const users = await db.users.findAll();
await cache.mset(
users.map(user => ({
key: `user:${user.id}`,
value: user,
ttl: 300000, // 5 minutes
}))
);
console.log(`Cached ${users.length} users`);
}
// Periodic refresh
setInterval(warmUpCache, 60000); // Refresh every minuteType Safety with Mixed Types:
import { createCache } from 'cache-manager';
const cache = createCache();
// Union type for mixed value types
type CacheValue = string | number | { id: number; name: string };
await cache.mset<CacheValue>([
{ key: 'string', value: 'text' },
{ key: 'number', value: 42 },
{ key: 'object', value: { id: 1, name: 'Alice' } },
]);
// Type-safe retrieval requires type assertion or narrowing
const strValue = await cache.get<string>('string');
const numValue = await cache.get<number>('number');
const objValue = await cache.get<{ id: number; name: string }>('object');Performance Comparison:
import { createCache } from 'cache-manager';
const cache = createCache();
const items = Array.from({ length: 1000 }, (_, i) => ({
key: `item:${i}`,
value: { id: i },
}));
// SLOW: Individual sets
console.time('individual');
for (const item of items) {
await cache.set(item.key, item.value);
}
console.timeEnd('individual'); // ~1000ms (1ms per item)
// FAST: Batch mset
console.time('batch');
await cache.mset(items);
console.timeEnd('batch'); // ~50ms (0.05ms per item)Deletes a key from the cache. In multi-store setups, deletes from all stores. Returns true on success or throws an error on failure.
/**
* Delete a key from cache
* @param key - Cache key to delete
* @returns true on success (even if key didn't exist)
* @throws Only on critical store failure
*/
del(key: string): Promise<boolean>;Behavioral Details:
true even if key didn't existnonBlocking)del event regardless of whether key existedUsage Examples:
import { createCache } from 'cache-manager';
const cache = createCache();
await cache.set('temp:data', 'some value');
// Verify it exists
console.log(await cache.get('temp:data')); // 'some value'
// Delete it
const deleted = await cache.del('temp:data');
console.log(deleted); // true
// Verify it's gone
console.log(await cache.get('temp:data')); // undefined
// Delete non-existent key (still returns true)
const deleted2 = await cache.del('nonexistent');
console.log(deleted2); // trueEdge Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Delete empty string key
await cache.set('', 'value');
await cache.del('');
console.log(await cache.get('')); // undefined
// Delete immediately after set
await cache.set('quick', 'value');
await cache.del('quick');
console.log(await cache.get('quick')); // undefined
// Delete expired key (still returns true)
await cache.set('exp', 'value', 100);
await new Promise(resolve => setTimeout(resolve, 150));
await cache.del('exp'); // true (even though already expired)Invalidation Patterns:
import { createCache } from 'cache-manager';
const cache = createCache();
// Invalidate on update
async function updateUser(userId: number, data: any) {
await db.users.update(userId, data);
await cache.del(`user:${userId}`); // Invalidate cache
}
// Invalidate related caches
async function deleteUser(userId: number) {
await db.users.delete(userId);
// Invalidate all related caches
await cache.mdel([
`user:${userId}`,
`user:${userId}:profile`,
`user:${userId}:settings`,
`user:${userId}:posts`,
]);
}
// Pattern-based invalidation (requires store iteration support)
async function invalidateUserCaches(userId: number) {
const store = cache.stores[0];
if (store.iterator) {
const pattern = `user:${userId}:`;
const keysToDelete: string[] = [];
for await (const [key] of store.iterator()) {
if (key.startsWith(pattern)) {
keysToDelete.push(key);
}
}
if (keysToDelete.length > 0) {
await cache.mdel(keysToDelete);
}
}
}Deletes multiple keys from the cache. In multi-store setups, deletes from all stores. Returns true on success or throws an error on failure.
/**
* Delete multiple keys from cache
* @param keys - Array of cache keys to delete
* @returns true on success (even if keys didn't exist)
* @throws Only on critical store failure
*/
mdel(keys: string[]): Promise<boolean>;Behavioral Details:
true even if keys didn't existdel() callsUsage Examples:
import { createCache } from 'cache-manager';
const cache = createCache();
// Set multiple values
await cache.mset([
{ key: 'temp:1', value: 'data1' },
{ key: 'temp:2', value: 'data2' },
{ key: 'temp:3', value: 'data3' },
]);
// Delete multiple keys at once
const deleted = await cache.mdel(['temp:1', 'temp:2', 'temp:3']);
console.log(deleted); // true
// Verify they're all gone
const results = await cache.mget(['temp:1', 'temp:2', 'temp:3']);
console.log(results); // [undefined, undefined, undefined]Edge Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Empty array
const empty = await cache.mdel([]);
console.log(empty); // true
// Mix of existing and non-existing keys
await cache.set('exists', 'value');
await cache.mdel(['exists', 'not-exists', 'also-not-exists']);
// All "deletions" succeed, returns true
// Duplicate keys in array
await cache.set('dup', 'value');
await cache.mdel(['dup', 'dup', 'dup']);
// Deletes once, returns true
// Large batch deletion
const keysToDelete = Array.from({ length: 10000 }, (_, i) => `key:${i}`);
await cache.mdel(keysToDelete);
// Efficiently deletes in batchCleanup Patterns:
import { createCache } from 'cache-manager';
const cache = createCache();
// Session cleanup on logout
async function logoutUser(userId: number) {
const sessionKeys = await getSessionKeys(userId);
await cache.mdel(sessionKeys);
}
// Bulk cleanup with prefix (if store supports iteration)
async function cleanupPrefix(prefix: string) {
const store = cache.stores[0];
if (store.iterator) {
const keysToDelete: string[] = [];
for await (const [key] of store.iterator()) {
if (key.startsWith(prefix)) {
keysToDelete.push(key);
}
}
if (keysToDelete.length > 0) {
console.log(`Deleting ${keysToDelete.length} keys with prefix ${prefix}`);
await cache.mdel(keysToDelete);
}
}
}
// Time-based cleanup (for stores without TTL)
async function cleanupOldEntries(maxAge: number) {
const store = cache.stores[0];
const now = Date.now();
const keysToDelete: string[] = [];
if (store.iterator) {
for await (const [key, value] of store.iterator()) {
if (value && typeof value === 'object' && 'timestamp' in value) {
if (now - value.timestamp > maxAge) {
keysToDelete.push(key);
}
}
}
if (keysToDelete.length > 0) {
await cache.mdel(keysToDelete);
}
}
}Removes all entries from the cache. In multi-store setups, clears all stores. Returns true on success or throws an error on failure.
/**
* Clear all entries from cache
* @returns true on success
* @throws On critical store failure
*/
clear(): Promise<boolean>;Behavioral Details:
clear eventUsage Examples:
import { createCache } from 'cache-manager';
const cache = createCache();
// Add some data
await cache.set('key1', 'value1');
await cache.set('key2', 'value2');
await cache.set('key3', 'value3');
// Clear everything
const cleared = await cache.clear();
console.log(cleared); // true
// Verify cache is empty
console.log(await cache.get('key1')); // undefined
console.log(await cache.get('key2')); // undefined
console.log(await cache.get('key3')); // undefinedUse Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Development: Clear cache on code reload
if (process.env.NODE_ENV === 'development') {
await cache.clear();
}
// Testing: Clear cache between tests
afterEach(async () => {
await cache.clear();
});
// Admin operation: Manual cache flush
app.post('/admin/cache/clear', async (req, res) => {
try {
await cache.clear();
res.json({ success: true, message: 'Cache cleared' });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Deployment: Clear cache on new deployment
process.on('SIGUSR2', async () => {
console.log('Clearing cache on deployment...');
await cache.clear();
});
// Scheduled maintenance
const cron = require('node-cron');
cron.schedule('0 2 * * *', async () => {
// Clear cache daily at 2 AM
console.log('Running scheduled cache clear...');
await cache.clear();
});Safety Patterns:
import { createCache } from 'cache-manager';
const cache = createCache();
// Confirmation required
async function safeClear(confirmation: string) {
if (confirmation !== 'CLEAR_CACHE_CONFIRMED') {
throw new Error('Cache clear requires confirmation');
}
console.warn('Clearing entire cache...');
const result = await cache.clear();
console.log('Cache cleared');
return result;
}
// Selective clear (clear only certain stores)
async function clearMemoryOnly() {
// Only clear first store (memory)
await cache.stores[0].clear();
// Redis cache remains intact
}
// Backup before clear
async function clearWithBackup() {
const backup = new Map();
const store = cache.stores[0];
if (store.iterator) {
for await (const [key, value] of store.iterator()) {
backup.set(key, value);
}
}
await cache.clear();
// Restore if needed
if (needsRestore) {
for (const [key, value] of backup) {
await cache.set(key, value);
}
}
}Gets the expiration timestamp for a key. Returns an absolute timestamp (milliseconds since Unix epoch) or undefined if the key is not found or has no expiration. To calculate remaining time-to-live, subtract the current time: remainingMs = ttl - Date.now().
/**
* Get expiration timestamp for a key
* @param key - Cache key to check
* @returns Absolute expiration timestamp in milliseconds (Unix epoch) or undefined if key not found or no expiration
* @throws Only on critical store failure
*/
ttl(key: string): Promise<number | undefined>;Behavioral Details:
Usage Examples:
import { createCache } from 'cache-manager';
const cache = createCache();
// Set a value with 10 second TTL
await cache.set('expiring:key', 'value', 10000);
// Check TTL immediately - returns absolute timestamp
const ttl = await cache.ttl('expiring:key');
console.log(ttl); // e.g., 1769587812910 (timestamp in the future)
// Calculate remaining time
if (ttl !== undefined) {
const remainingMs = ttl - Date.now();
const remainingSec = Math.floor(remainingMs / 1000);
console.log(`Expires in ${remainingSec} seconds`);
}
// Wait 5 seconds
await new Promise(resolve => setTimeout(resolve, 5000));
// Check TTL again
const ttl2 = await cache.ttl('expiring:key');
if (ttl2 !== undefined) {
const remainingMs2 = ttl2 - Date.now();
console.log(`${remainingMs2}ms remaining`); // ~5000ms
}
// Check non-existent key
const noTtl = await cache.ttl('missing:key');
console.log(noTtl); // undefinedAdvanced Patterns:
import { createCache } from 'cache-manager';
const cache = createCache();
// Extend TTL if near expiration
async function ensureMinimumTtl(key: string, minTtl: number) {
const value = await cache.get(key);
if (value === undefined) return false;
const ttl = await cache.ttl(key);
if (ttl === undefined) return false;
const remaining = ttl - Date.now();
if (remaining < minTtl) {
// Refresh with minimum TTL
await cache.set(key, value, minTtl);
return true;
}
return false;
}
// Monitor TTLs
async function monitorExpiration(key: string) {
const interval = setInterval(async () => {
const ttl = await cache.ttl(key);
if (ttl === undefined) {
console.log(`${key} expired or doesn't exist`);
clearInterval(interval);
return;
}
const remaining = Math.floor((ttl - Date.now()) / 1000);
console.log(`${key} expires in ${remaining}s`);
if (remaining <= 0) {
clearInterval(interval);
}
}, 1000);
}
// Conditional refresh based on TTL
async function getWithRefresh(key: string, fetchFn: () => Promise<any>) {
const value = await cache.get(key);
const ttl = await cache.ttl(key);
if (value === undefined) {
// Cache miss - fetch and cache
const fresh = await fetchFn();
await cache.set(key, fresh, 60000);
return fresh;
}
if (ttl !== undefined) {
const remaining = ttl - Date.now();
if (remaining < 10000) {
// Less than 10s remaining - refresh in background
fetchFn().then(fresh => {
cache.set(key, fresh, 60000);
});
}
}
return value;
}
// TTL-based caching strategy
async function adaptiveTtl(key: string, value: any, importance: 'low' | 'medium' | 'high') {
const ttls = {
low: 60000, // 1 minute
medium: 300000, // 5 minutes
high: 3600000, // 1 hour
};
await cache.set(key, value, ttls[importance]);
}Edge Cases:
import { createCache } from 'cache-manager';
const cache = createCache();
// Key with no TTL (store-dependent)
await cache.set('noexpiry', 'permanent');
const noTtl = await cache.ttl('noexpiry');
console.log(noTtl); // undefined (no expiration)
// Expired key
await cache.set('expired', 'value', 100);
await new Promise(resolve => setTimeout(resolve, 150));
const expiredTtl = await cache.ttl('expired');
console.log(expiredTtl); // undefined (key expired)
// TTL in the past (immediate expiration)
await cache.set('past', 'value', -1000);
const pastTtl = await cache.ttl('past');
console.log(pastTtl); // undefined (immediately expired)Disconnects from all storage adapters. This is important when using storage adapters that maintain persistent connections (like Redis). Should be called when the cache is no longer needed.
/**
* Disconnect from all storage adapters
* @returns Promise that resolves to undefined when all stores disconnected
* @throws Only on critical disconnection failure
*/
disconnect(): Promise<undefined>;Behavioral Details:
Usage Examples:
import { createCache } from 'cache-manager';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';
const cache = createCache({
stores: [
new Keyv({
store: new KeyvRedis('redis://localhost:6379'),
}),
],
});
// Use the cache
await cache.set('key', 'value');
await cache.get('key');
// When done, disconnect to close connections
await cache.disconnect();
// Further operations will fail after disconnect
try {
await cache.get('key');
} catch (error) {
console.error('Cache disconnected:', error);
}Application Lifecycle Patterns:
import { createCache } from 'cache-manager';
const cache = createCache();
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('Shutting down gracefully...');
// Disconnect cache
await cache.disconnect();
// Close other resources
await db.disconnect();
process.exit(0);
});
process.on('SIGINT', async () => {
console.log('Interrupted, cleaning up...');
await cache.disconnect();
process.exit(0);
});
// Express.js integration
const app = express();
app.on('close', async () => {
await cache.disconnect();
});
// Test cleanup
afterAll(async () => {
await cache.disconnect();
});Connection Pool Management:
import { createCache } from 'cache-manager';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';
// Create with explicit connection management
const redisStore = new KeyvRedis('redis://localhost:6379', {
// Connection pool options
maxRetriesPerRequest: 3,
enableOfflineQueue: false,
});
const cache = createCache({
stores: [new Keyv({ store: redisStore })],
});
// Monitor connection events
redisStore.on('connect', () => {
console.log('Redis connected');
});
redisStore.on('error', (error) => {
console.error('Redis error:', error);
});
redisStore.on('close', () => {
console.log('Redis connection closed');
});
// Proper cleanup
async function cleanup() {
console.log('Disconnecting cache...');
await cache.disconnect();
console.log('Cache disconnected');
}
process.on('exit', () => {
console.log('Process exiting');
});Returns the unique identifier for this cache instance. Primarily used internally to prevent conflicts when using wrap() with multiple cache instances.
/**
* Get the cache instance ID
* @returns Unique cache instance identifier (string)
*/
cacheId(): string;Usage Examples:
import { createCache } from 'cache-manager';
// Create cache with custom ID
const cache1 = createCache({ cacheId: 'my-cache-1' });
console.log(cache1.cacheId()); // 'my-cache-1'
// Create cache with auto-generated ID
const cache2 = createCache();
console.log(cache2.cacheId()); // Random string like 'k7x9m2p4q'
// Multiple caches with distinct IDs
const userCache = createCache({ cacheId: 'users' });
const productCache = createCache({ cacheId: 'products' });
console.log(userCache.cacheId()); // 'users'
console.log(productCache.cacheId()); // 'products'Provides direct access to the underlying Keyv store instances. Useful for advanced operations like iteration over cache contents or accessing store-specific features.
/**
* Array of Keyv store instances
*/
stores: Keyv[];Usage Examples:
import { createCache } from 'cache-manager';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';
const keyv1 = new Keyv();
const keyv2 = new Keyv({
store: new KeyvRedis('redis://localhost:6379'),
});
const cache = createCache({
stores: [keyv1, keyv2],
});
// Access stores directly
console.log(cache.stores.length); // 2
// Get store reference
const memoryStore = cache.stores[0];
const redisStore = cache.stores[1];
// Use Keyv API directly on a store
if (redisStore.iterator) {
for await (const [key, value] of redisStore.iterator()) {
console.log(key, value);
}
}
// Clear specific store
await memoryStore.clear(); // Only clears memory, not Redis
// Check individual store
const inMemory = await memoryStore.get('key');
const inRedis = await redisStore.get('key');Advanced Store Operations:
import { createCache } from 'cache-manager';
const cache = createCache();
// Iterate over all keys (if store supports it)
async function listAllKeys() {
const store = cache.stores[0];
const keys: string[] = [];
if (store.iterator) {
for await (const [key] of store.iterator()) {
keys.push(key);
}
}
return keys;
}
// Get cache statistics
async function getCacheStats() {
const store = cache.stores[0];
let count = 0;
let totalSize = 0;
if (store.iterator) {
for await (const [key, value] of store.iterator()) {
count++;
totalSize += JSON.stringify(value).length;
}
}
return {
entryCount: count,
approximateSize: totalSize,
averageEntrySize: count > 0 ? totalSize / count : 0,
};
}
// Export cache contents
async function exportCache() {
const store = cache.stores[0];
const entries: Record<string, any> = {};
if (store.iterator) {
for await (const [key, value] of store.iterator()) {
entries[key] = value;
}
}
return entries;
}
// Import cache contents
async function importCache(entries: Record<string, any>) {
await cache.mset(
Object.entries(entries).map(([key, value]) => ({
key,
value,
}))
);
}In configurations with multiple stores:
nonBlocking)When nonBlocking: true is set in CreateCacheOptions:
Promise.race() to return the fastest responseThis improves performance but may result in eventual consistency across stores.
set() or mset(), the default TTL from CreateCacheOptions is usedttl() method returns the expiration timestamp, not the remaining timeInfinity TTL may be treated as "no expiration" by some storesAs of cache-manager v7, operations return undefined (not null) when keys are not found or have expired. This aligns with the Keyv API conventions.
Most operations throw errors on critical failures. Errors are also emitted via the event system for monitoring purposes. Non-critical errors (like cache misses) return undefined rather than throwing.
Generic type parameter T is preserved through operations but depends on serializer. Complex types with methods, symbols, or circular references may not serialize correctly.
undefined values may not be distinguishable from cache misses