{"version":3,"sources":["../../src/cache/stores/MemoryStore.ts","../../src/cache/SmartCache.ts","../../src/cache/stores/RedisStore.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { CacheStore } from '../types.js';\n\ninterface MemoryEntry {\n  value: string;\n  expiresAt: number;\n}\n\n/**\n * In-memory cache store with automatic TTL expiry.\n * Uses a simple Map — no external dependencies required.\n */\nexport class MemoryStore implements CacheStore {\n  private readonly store = new Map<string, MemoryEntry>();\n  private sweepInterval: ReturnType<typeof setInterval> | null = null;\n\n  constructor(private readonly sweepIntervalMs = 30_000) {\n    // Periodic cleanup of expired entries to prevent memory leaks\n    this.sweepInterval = setInterval(() => this.sweep(), sweepIntervalMs).unref();\n  }\n\n  async get(key: string): Promise<string | null> {\n    const entry = this.store.get(key);\n    if (!entry) return null;\n    if (Date.now() > entry.expiresAt) {\n      this.store.delete(key);\n      return null;\n    }\n    return entry.value;\n  }\n\n  async set(key: string, value: string, ttlMs: number): Promise<void> {\n    if (ttlMs <= 0) throw new Error(`[rezilia/cache] TTL must be positive, got ${ttlMs}`);\n    this.store.set(key, { value, expiresAt: Date.now() + ttlMs });\n  }\n\n  async delete(key: string): Promise<void> {\n    this.store.delete(key);\n  }\n\n  async clear(): Promise<void> {\n    this.store.clear();\n  }\n\n  /** Number of entries currently in store (including possibly expired) */\n  get size(): number {\n    return this.store.size;\n  }\n\n  /** Stop the background sweep timer — call when you no longer need this store */\n  destroy(): void {\n    if (this.sweepInterval) {\n      clearInterval(this.sweepInterval);\n      this.sweepInterval = null;\n    }\n  }\n\n  private sweep(): void {\n    const now = Date.now();\n    for (const [key, entry] of this.store) {\n      if (now > entry.expiresAt) this.store.delete(key);\n    }\n  }\n}\n","import { MemoryStore } from './stores/MemoryStore.js';\nimport type { CacheStore, SmartCacheOptions, GetOptions, CacheEntry } from './types.js';\n\n/**\n * SmartCache — eliminates the two hardest caching problems in Node.js:\n *\n * 1. **Cache Stampede** (a.k.a. thundering herd): when a hot key expires,\n *    thousands of requests hit the database simultaneously. SmartCache uses\n *    the *singleflight* pattern — only ONE request fetches; the rest wait\n *    and share the result.\n *\n * 2. **Stale-While-Revalidate**: instead of blocking on a cache miss, serve\n *    the stale value instantly and refresh in the background.\n *\n * @example\n * const cache = new SmartCache({ defaultTtl: 60_000 });\n *\n * const user = await cache.get('user:42', () => db.findUser(42));\n * // Second call returns from cache instantly — no DB hit.\n */\nexport class SmartCache {\n  private readonly store: CacheStore;\n  private readonly defaultTtl: number;\n  private readonly jitterFactor: number;\n  private readonly staleWhileRevalidate: boolean;\n  private readonly staleTtl: number;\n\n  /** In-flight requests — key → pending Promise (singleflight map) */\n  private readonly inflight = new Map<string, Promise<unknown>>();\n\n  /** Local in-process cache — avoids JSON parse on every hit */\n  private readonly localCache = new Map<string, CacheEntry<unknown>>();\n\n  constructor(options: SmartCacheOptions = {}) {\n    this.store = options.store ?? new MemoryStore();\n    this.defaultTtl = options.defaultTtl ?? 60_000;\n    this.jitterFactor = this.validateJitter(options.jitterFactor ?? 0.1);\n    this.staleWhileRevalidate = options.staleWhileRevalidate ?? true;\n    this.staleTtl = options.staleTtl ?? 5_000;\n  }\n\n  /**\n   * Get a value from cache, or call `fetcher` to populate it.\n   *\n   * - Concurrent calls with the same key share ONE fetcher call (singleflight).\n   * - Stale values are served immediately while a background refresh runs.\n   *\n   * @param key   Cache key\n   * @param fetcher Function that returns the fresh value (called at most once per stampede window)\n   * @param options Per-key TTL / jitter overrides\n   */\n  async get<T>(key: string, fetcher: () => Promise<T>, options: GetOptions = {}): Promise<T> {\n    this.validateKey(key);\n\n    const ttl = options.ttl ?? this.defaultTtl;\n    const jitter = options.jitterFactor ?? this.jitterFactor;\n    const now = Date.now();\n\n    // 1. Check local in-process cache first (fast path — no async)\n    const local = this.localCache.get(key) as CacheEntry<T> | undefined;\n    if (local) {\n      if (now < local.expiresAt) return local.value;\n\n      // Stale-while-revalidate: serve stale immediately, refresh in background\n      if (this.staleWhileRevalidate && now < local.staleUntil) {\n        void this.revalidate(key, fetcher, ttl, jitter);\n        return local.value;\n      }\n    }\n\n    // 2. Check backing store (handles multi-instance / Redis)\n    const stored = await this.store.get(key);\n    if (stored !== null) {\n      const parsed = JSON.parse(stored) as CacheEntry<T>;\n      if (now < parsed.expiresAt) {\n        this.localCache.set(key, parsed); // warm local cache\n        return parsed.value;\n      }\n\n      if (this.staleWhileRevalidate && now < parsed.staleUntil) {\n        this.localCache.set(key, parsed);\n        void this.revalidate(key, fetcher, ttl, jitter);\n        return parsed.value;\n      }\n    }\n\n    // 3. Cache miss — use singleflight to prevent stampede\n    return this.singleflight(key, fetcher, ttl, jitter);\n  }\n\n  /**\n   * Explicitly set a value in cache.\n   */\n  async set<T>(key: string, value: T, options: GetOptions = {}): Promise<void> {\n    this.validateKey(key);\n    const ttl = options.ttl ?? this.defaultTtl;\n    const jitter = options.jitterFactor ?? this.jitterFactor;\n    await this.writeToStore(key, value, ttl, jitter);\n  }\n\n  /**\n   * Delete a key from cache.\n   */\n  async invalidate(key: string): Promise<void> {\n    this.validateKey(key);\n    this.localCache.delete(key);\n    await this.store.delete(key);\n  }\n\n  /**\n   * Clear all cached values.\n   */\n  async clear(): Promise<void> {\n    this.localCache.clear();\n    await this.store.clear();\n  }\n\n  /** Returns number of in-flight requests (useful for monitoring) */\n  get inflightCount(): number {\n    return this.inflight.size;\n  }\n\n  // ─── Private helpers ────────────────────────────────────────────────────────\n\n  /**\n   * Singleflight: if there's already a fetch in progress for this key,\n   * return that same promise. Otherwise start a new one.\n   */\n  private singleflight<T>(\n    key: string,\n    fetcher: () => Promise<T>,\n    ttl: number,\n    jitter: number,\n  ): Promise<T> {\n    const existing = this.inflight.get(key);\n    if (existing) return existing as Promise<T>;\n\n    const promise = (async () => {\n      try {\n        const value = await fetcher();\n        await this.writeToStore(key, value, ttl, jitter);\n        return value;\n      } finally {\n        this.inflight.delete(key);\n      }\n    })();\n\n    this.inflight.set(key, promise);\n    return promise;\n  }\n\n  /**\n   * Background revalidation for stale-while-revalidate.\n   * Errors are swallowed so they don't surface to the caller.\n   */\n  private async revalidate<T>(\n    key: string,\n    fetcher: () => Promise<T>,\n    ttl: number,\n    jitter: number,\n  ): Promise<void> {\n    // Avoid multiple concurrent revalidations for the same key\n    if (this.inflight.has(key)) return;\n\n    const promise = (async () => {\n      try {\n        const value = await fetcher();\n        await this.writeToStore(key, value, ttl, jitter);\n      } catch {\n        // Revalidation failure is non-fatal — stale value was already returned\n      } finally {\n        this.inflight.delete(key);\n      }\n    })();\n\n    this.inflight.set(key, promise);\n    await promise;\n  }\n\n  private async writeToStore<T>(\n    key: string,\n    value: T,\n    ttl: number,\n    jitter: number,\n  ): Promise<void> {\n    const jitteredTtl = this.applyJitter(ttl, jitter);\n    const now = Date.now();\n    const entry: CacheEntry<T> = {\n      value,\n      expiresAt: now + jitteredTtl,\n      staleUntil: now + jitteredTtl + this.staleTtl,\n    };\n    this.localCache.set(key, entry);\n    await this.store.set(key, JSON.stringify(entry), jitteredTtl + this.staleTtl);\n  }\n\n  /** Apply ±jitter% randomness to TTL to spread out expirations */\n  private applyJitter(ttl: number, jitter: number): number {\n    const delta = ttl * jitter;\n    return Math.round(ttl + (Math.random() * 2 - 1) * delta);\n  }\n\n  private validateJitter(jitter: number): number {\n    if (jitter < 0 || jitter > 1) {\n      throw new RangeError(`[rezilia/cache] jitterFactor must be between 0 and 1, got ${jitter}`);\n    }\n    return jitter;\n  }\n\n  private validateKey(key: string): void {\n    if (!key || typeof key !== 'string') {\n      throw new TypeError(`[rezilia/cache] Cache key must be a non-empty string`);\n    }\n    if (key.length > 512) {\n      throw new RangeError(`[rezilia/cache] Cache key too long (max 512 chars)`);\n    }\n  }\n}\n","import type { CacheStore } from '../types.js';\n\n/**\n * Minimal interface we need from ioredis — keeps the dependency optional.\n * If you use ioredis, pass the client directly; it satisfies this interface.\n */\nexport interface RedisClient {\n  get(key: string): Promise<string | null>;\n  set(key: string, value: string, ex: 'PX', ttl: number): Promise<unknown>;\n  del(key: string): Promise<unknown>;\n  flushdb(): Promise<unknown>;\n}\n\n/**\n * Redis-backed cache store using ioredis (peer dependency).\n *\n * @example\n * import Redis from 'ioredis';\n * import { SmartCache, RedisStore } from 'rezilia/cache';\n *\n * const client = new Redis({ host: 'localhost', port: 6379 });\n * const cache = new SmartCache({ store: new RedisStore(client) });\n */\nexport class RedisStore implements CacheStore {\n  constructor(private readonly client: RedisClient) {}\n\n  async get(key: string): Promise<string | null> {\n    return this.client.get(key);\n  }\n\n  async set(key: string, value: string, ttlMs: number): Promise<void> {\n    if (ttlMs <= 0) throw new Error(`[rezilia/cache] TTL must be positive, got ${ttlMs}`);\n    await this.client.set(key, value, 'PX', ttlMs);\n  }\n\n  async delete(key: string): Promise<void> {\n    await this.client.del(key);\n  }\n\n  async clear(): Promise<void> {\n    await this.client.flushdb();\n  }\n}\n"]}