{"version":3,"file":"cache.mjs","sources":["../../../src/lib/services/cache.ts"],"sourcesContent":["/**\n * @file Request Cache\n * @description Request deduplication and caching utilities\n */\n\n/**\n * Cache entry\n */\ninterface CacheEntry<T> {\n  data: T;\n  timestamp: number;\n  expiresAt: number;\n}\n\n/**\n * Pending request entry\n */\ninterface PendingRequest<T> {\n  promise: Promise<T>;\n  timestamp: number;\n}\n\n/**\n * Cache configuration\n */\nexport interface CacheConfig {\n  /** Default TTL in milliseconds */\n  defaultTtl?: number;\n  \n  /** Maximum cache entries */\n  maxEntries?: number;\n  \n  /** Enable request deduplication */\n  deduplication?: boolean;\n  \n  /** Deduplication window (ms) */\n  deduplicationWindow?: number;\n}\n\n/**\n * Request cache class\n */\nexport class RequestCache {\n  private cache: Map<string, CacheEntry<unknown>> = new Map();\n  private pending: Map<string, PendingRequest<unknown>> = new Map();\n  private config: Required<CacheConfig>;\n  private gcIntervalId: ReturnType<typeof setInterval> | null = null;\n\n  constructor(config: CacheConfig = {}) {\n    this.config = {\n      defaultTtl: config.defaultTtl ?? 5 * 60 * 1000, // 5 minutes\n      maxEntries: config.maxEntries ?? 100,\n      deduplication: config.deduplication ?? true,\n      deduplicationWindow: config.deduplicationWindow ?? 100, // 100ms\n    };\n\n    // Start periodic garbage collection to prevent unbounded cache growth\n    this.gcIntervalId = setInterval(() => this.clearExpired(), 60000);\n  }\n\n  /**\n   * Dispose of the cache and clean up resources\n   */\n  dispose(): void {\n    if (this.gcIntervalId !== null) {\n      clearInterval(this.gcIntervalId);\n      this.gcIntervalId = null;\n    }\n    this.clear();\n  }\n  \n  /**\n   * Generate cache key\n   */\n  generateKey(\n    url: string,\n    params?: Record<string, unknown>,\n    body?: unknown\n  ): string {\n    const parts = [url];\n    \n    if (params !== undefined) {\n      parts.push(JSON.stringify(params));\n    }\n    \n    if (body !== undefined) {\n      parts.push(JSON.stringify(body));\n    }\n    \n    return parts.join('::');\n  }\n  \n  /**\n   * Get cached value\n   */\n  get<T>(key: string): T | undefined {\n    const entry = this.cache.get(key) as CacheEntry<T> | undefined;\n    \n    if (entry === undefined) {\n      return undefined;\n    }\n    \n    // Check expiration\n    if (Date.now() > entry.expiresAt) {\n      this.cache.delete(key);\n      return undefined;\n    }\n    \n    return entry.data;\n  }\n  \n  /**\n   * Set cached value\n   */\n  set<T>(key: string, data: T, ttl?: number): void {\n    // Enforce max entries\n    if (this.cache.size >= this.config.maxEntries) {\n      this.evictOldest();\n    }\n    \n    const timestamp = Date.now();\n    this.cache.set(key, {\n      data,\n      timestamp,\n      expiresAt: timestamp + (ttl ?? this.config.defaultTtl),\n    });\n  }\n  \n  /**\n   * Check if key exists and is valid\n   */\n  has(key: string): boolean {\n    return this.get(key) !== undefined;\n  }\n  \n  /**\n   * Delete cached value\n   */\n  delete(key: string): boolean {\n    return this.cache.delete(key);\n  }\n  \n  /**\n   * Clear all cached values\n   */\n  clear(): void {\n    this.cache.clear();\n    this.pending.clear();\n  }\n  \n  /**\n   * Clear expired entries\n   */\n  clearExpired(): void {\n    const now = Date.now();\n    for (const [key, entry] of this.cache.entries()) {\n      if (now > entry.expiresAt) {\n        this.cache.delete(key);\n      }\n    }\n  }\n  \n  /**\n   * Execute with deduplication\n   */\n  async dedupe<T>(\n    key: string,\n    fetcher: () => Promise<T>,\n    options?: { ttl?: number }\n  ): Promise<T> {\n    // Check cache first\n    const cached = this.get<T>(key);\n    if (cached !== undefined) {\n      return cached;\n    }\n\n    // Check for pending request\n    if (this.config.deduplication) {\n      const pending = this.pending.get(key) as PendingRequest<T> | undefined;\n\n      if (pending !== undefined) {\n        const timeSincePending = Date.now() - pending.timestamp;\n        if (timeSincePending < this.config.deduplicationWindow) {\n          return pending.promise;\n        }\n      }\n    }\n\n    // Execute fetcher\n    const promise = fetcher();\n\n    // Store pending request\n    if (this.config.deduplication) {\n      this.pending.set(key, {\n        promise,\n        timestamp: Date.now(),\n      });\n    }\n\n    try {\n      const data = await promise;\n\n      // Cache result\n      this.set(key, data, options?.ttl);\n\n      return data;\n    } finally {\n      // Clean up pending\n      this.pending.delete(key);\n    }\n  }\n  \n  /**\n   * Invalidate cache entries by pattern\n   */\n  invalidatePattern(pattern: string | RegExp): void {\n    const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n\n    for (const key of this.cache.keys()) {\n      if (regex.test(key)) {\n        this.cache.delete(key);\n      }\n    }\n  }\n  \n  /**\n   * Get cache statistics\n   */\n  getStats(): {\n    size: number;\n    maxEntries: number;\n    pendingRequests: number;\n  } {\n    return {\n      size: this.cache.size,\n      maxEntries: this.config.maxEntries,\n      pendingRequests: this.pending.size,\n    };\n  }\n  \n  /**\n   * Evict oldest entry\n   */\n  private evictOldest(): void {\n    let oldestKey: string | null = null;\n    let oldestTime = Infinity;\n\n    for (const [key, entry] of this.cache.entries()) {\n      if (entry.timestamp < oldestTime) {\n        oldestTime = entry.timestamp;\n        oldestKey = key;\n      }\n    }\n\n    if (oldestKey !== null && oldestKey !== '') {\n      this.cache.delete(oldestKey);\n    }\n  }\n}\n\n/**\n * Default request cache instance\n */\nexport const requestCache = new RequestCache();\n\n/**\n * Create a cached fetcher\n */\nexport function createCachedFetcher<T>(\n  fetcher: () => Promise<T>,\n  key: string,\n  options?: { ttl?: number; cache?: RequestCache }\n): () => Promise<T> {\n  const cache = options?.cache ?? requestCache;\n  \n  return async () => cache.dedupe(key, fetcher, options);\n}\n\n/**\n * HOF for caching function results\n */\nexport function withCache<Args extends unknown[], Result>(\n  fn: (...args: Args) => Promise<Result>,\n  keyGenerator: (...args: Args) => string,\n  options?: { ttl?: number; cache?: RequestCache }\n): (...args: Args) => Promise<Result> {\n  const cache = options?.cache ?? requestCache;\n\n  return async (...args: Args) => {\n    const key = keyGenerator(...args);\n    return cache.dedupe(key, async () => fn(...args), options);\n  };\n}\n\n// ============================================================================\n// HMR Support\n// ============================================================================\n\n/**\n * HMR disposal handler to prevent memory leaks during hot module replacement.\n *\n * During development, when this module is hot-reloaded, the previous instance's\n * GC interval would continue running, causing memory leaks and unexpected behavior.\n * This handler ensures proper cleanup.\n */\nif (import.meta.hot) {\n  import.meta.hot.dispose(() => {\n    // Clean up the default cache instance to prevent memory leaks\n    requestCache.dispose();\n  });\n}\n"],"names":["RequestCache","config","url","params","body","parts","key","entry","data","ttl","timestamp","now","fetcher","options","cached","pending","promise","pattern","regex","oldestKey","oldestTime","requestCache","createCachedFetcher","cache","withCache","fn","keyGenerator","args"],"mappings":"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"}