/**
* 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;