/** * Cache store interface — implement this to use any backend (Redis, Memcached, etc.) */ interface CacheStore { get(key: string): Promise; set(key: string, value: string, ttlMs: number): Promise; delete(key: string): Promise; clear(): Promise; } /** * Options for SmartCache */ interface SmartCacheOptions { /** Default TTL in milliseconds (default: 60_000 = 1 minute) */ defaultTtl?: number; /** * TTL jitter factor 0–1 (default: 0.1 = ±10%) * Prevents cache stampede by randomising expiry times */ jitterFactor?: number; /** Enable stale-while-revalidate pattern (default: true) */ staleWhileRevalidate?: boolean; /** * How long to serve stale data while revalidating (ms, default: 5_000) * Only used when staleWhileRevalidate is true */ staleTtl?: number; /** Backing store — defaults to in-memory LRU */ store?: CacheStore; } /** * Per-key get options */ interface GetOptions { /** Override default TTL for this key (ms) */ ttl?: number; /** Override jitter factor for this key */ jitterFactor?: number; } /** * Cache entry stored internally */ interface CacheEntry { value: T; expiresAt: number; staleUntil: number; } /** * SmartCache — eliminates the two hardest caching problems in Node.js: * * 1. **Cache Stampede** (a.k.a. thundering herd): when a hot key expires, * thousands of requests hit the database simultaneously. SmartCache uses * the *singleflight* pattern — only ONE request fetches; the rest wait * and share the result. * * 2. **Stale-While-Revalidate**: instead of blocking on a cache miss, serve * the stale value instantly and refresh in the background. * * @example * const cache = new SmartCache({ defaultTtl: 60_000 }); * * const user = await cache.get('user:42', () => db.findUser(42)); * // Second call returns from cache instantly — no DB hit. */ declare class SmartCache { private readonly store; private readonly defaultTtl; private readonly jitterFactor; private readonly staleWhileRevalidate; private readonly staleTtl; /** In-flight requests — key → pending Promise (singleflight map) */ private readonly inflight; /** Local in-process cache — avoids JSON parse on every hit */ private readonly localCache; constructor(options?: SmartCacheOptions); /** * Get a value from cache, or call `fetcher` to populate it. * * - Concurrent calls with the same key share ONE fetcher call (singleflight). * - Stale values are served immediately while a background refresh runs. * * @param key Cache key * @param fetcher Function that returns the fresh value (called at most once per stampede window) * @param options Per-key TTL / jitter overrides */ get(key: string, fetcher: () => Promise, options?: GetOptions): Promise; /** * Explicitly set a value in cache. */ set(key: string, value: T, options?: GetOptions): Promise; /** * Delete a key from cache. */ invalidate(key: string): Promise; /** * Clear all cached values. */ clear(): Promise; /** Returns number of in-flight requests (useful for monitoring) */ get inflightCount(): number; /** * Singleflight: if there's already a fetch in progress for this key, * return that same promise. Otherwise start a new one. */ private singleflight; /** * Background revalidation for stale-while-revalidate. * Errors are swallowed so they don't surface to the caller. */ private revalidate; private writeToStore; /** Apply ±jitter% randomness to TTL to spread out expirations */ private applyJitter; private validateJitter; private validateKey; } /** * In-memory cache store with automatic TTL expiry. * Uses a simple Map — no external dependencies required. */ declare class MemoryStore implements CacheStore { private readonly sweepIntervalMs; private readonly store; private sweepInterval; constructor(sweepIntervalMs?: number); get(key: string): Promise; set(key: string, value: string, ttlMs: number): Promise; delete(key: string): Promise; clear(): Promise; /** Number of entries currently in store (including possibly expired) */ get size(): number; /** Stop the background sweep timer — call when you no longer need this store */ destroy(): void; private sweep; } /** * Minimal interface we need from ioredis — keeps the dependency optional. * If you use ioredis, pass the client directly; it satisfies this interface. */ interface RedisClient { get(key: string): Promise; set(key: string, value: string, ex: 'PX', ttl: number): Promise; del(key: string): Promise; flushdb(): Promise; } /** * Redis-backed cache store using ioredis (peer dependency). * * @example * import Redis from 'ioredis'; * import { SmartCache, RedisStore } from 'rezilia/cache'; * * const client = new Redis({ host: 'localhost', port: 6379 }); * const cache = new SmartCache({ store: new RedisStore(client) }); */ declare class RedisStore implements CacheStore { private readonly client; constructor(client: RedisClient); get(key: string): Promise; set(key: string, value: string, ttlMs: number): Promise; delete(key: string): Promise; clear(): Promise; } export { type CacheEntry, type CacheStore, type GetOptions, MemoryStore, RedisStore, SmartCache, type SmartCacheOptions };