Source: utils/cache.js

/**
 * In-memory cache utility
 * 
 * Provides caching capabilities for API responses
 */

import { config } from '../config/index.js';
import { logger } from './logger.js';

class Cache {
  constructor(ttl = 300, maxSize = 100) {
    this.cache = new Map();
    this.ttl = ttl; // Default TTL in seconds
    this.maxSize = maxSize; // Maximum number of items in cache
  }
  
  /**
   * Get item from cache
   * @param {string} key - Cache key
   * @returns {any|null} - Cached value or null if expired/not found
   */
  get(key) {
    // Check if key exists
    if (!this.cache.has(key)) {
      return null;
    }
    
    const cacheItem = this.cache.get(key);
    
    // Check if item has expired
    if (Date.now() > cacheItem.expiresAt) {
      this.delete(key);
      return null;
    }
    
    // Update access time
    cacheItem.lastAccessed = Date.now();
    this.cache.set(key, cacheItem);
    
    logger.debug(`Cache hit for key: ${key}`);
    return cacheItem.value;
  }
  
  /**
   * Set item in cache
   * @param {string} key - Cache key
   * @param {any} value - Value to cache
   * @param {number} ttl - Time to live in seconds (optional)
   */
  set(key, value, ttl = null) {
    // Enforce max size by removing least recently used items
    if (this.cache.size >= this.maxSize) {
      this._evictLRU();
    }
    
    const expiresAt = Date.now() + ((ttl || this.ttl) * 1000);
    
    this.cache.set(key, {
      value,
      expiresAt,
      lastAccessed: Date.now()
    });
    
    logger.debug(`Cache set for key: ${key}, expires in ${ttl || this.ttl}s`);
  }
  
  /**
   * Delete item from cache
   * @param {string} key - Cache key
   */
  delete(key) {
    this.cache.delete(key);
    logger.debug(`Cache deleted for key: ${key}`);
  }
  
  /**
   * Clear all items from cache
   */
  clear() {
    this.cache.clear();
    logger.debug('Cache cleared');
  }
  
  /**
   * Check if key exists in cache (without updating access time)
   * @param {string} key - Cache key
   * @returns {boolean} - Whether key exists and is not expired
   */
  has(key) {
    if (!this.cache.has(key)) {
      return false;
    }
    
    const cacheItem = this.cache.get(key);
    
    if (Date.now() > cacheItem.expiresAt) {
      this.delete(key);
      return false;
    }
    
    return true;
  }
  
  /**
   * Evict least recently used item
   * @private
   */
  _evictLRU() {
    let oldestKey = null;
    let oldestTime = Infinity;
    
    // Find least recently accessed item
    for (const [key, item] of this.cache.entries()) {
      if (item.lastAccessed < oldestTime) {
        oldestTime = item.lastAccessed;
        oldestKey = key;
      }
    }
    
    // Remove if found
    if (oldestKey) {
      this.delete(oldestKey);
      logger.debug(`Cache evicted LRU key: ${oldestKey}`);
    }
  }
  
  /**
   * Get current cache size
   * @returns {number} - Number of items in cache
   */
  size() {
    return this.cache.size;
  }
  
  /**
   * Get cache statistics
   * @returns {Object} - Cache statistics
   */
  stats() {
    let expiredCount = 0;
    const now = Date.now();
    
    // Count expired items
    for (const item of this.cache.values()) {
      if (now > item.expiresAt) {
        expiredCount++;
      }
    }
    
    return {
      size: this.cache.size,
      maxSize: this.maxSize,
      expired: expiredCount,
      ttl: this.ttl
    };
  }
}

// Create cache instance with configuration
export const cache = new Cache(
  config.CACHE.TTL || 300,
  config.CACHE.MAX_SIZE || 100
);

export default cache;