UNPKG

hikma-engine

Version:

Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents

209 lines (208 loc) 6.4 kB
"use strict"; /** * @file In-memory cache service for API search results. * Provides LRU caching with TTL support without external dependencies. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultCacheService = exports.InMemoryCacheService = void 0; const logger_1 = require("../../utils/logger"); const logger = (0, logger_1.getLogger)('InMemoryCacheService'); /** * In-memory cache service with LRU eviction and TTL support. */ class InMemoryCacheService { constructor(config = {}) { this.cache = new Map(); this.accessOrder = []; // For LRU tracking this.config = { maxSize: config.maxSize || 1000, defaultTTL: config.defaultTTL || 5 * 60 * 1000, // 5 minutes default }; logger.info('Cache service initialized', { maxSize: this.config.maxSize, defaultTTL: `${this.config.defaultTTL / 1000}s`, }); } /** * Sets a value in the cache with optional TTL. */ set(key, value, ttl) { const now = Date.now(); const entryTTL = ttl || this.config.defaultTTL; // Remove existing entry if present if (this.cache.has(key)) { this.removeFromAccessOrder(key); } // Check if we need to evict entries if (this.cache.size >= this.config.maxSize) { this.evictLRU(); } // Add new entry this.cache.set(key, { value, timestamp: now, ttl: entryTTL, accessCount: 0, lastAccessed: now, }); // Update access order this.accessOrder.push(key); logger.debug('Cache entry set', { key: key.substring(0, 50), ttl: `${entryTTL / 1000}s`, cacheSize: this.cache.size, }); } /** * Gets a value from the cache. */ get(key) { const entry = this.cache.get(key); if (!entry) { logger.debug('Cache miss', { key: key.substring(0, 50) }); return null; } const now = Date.now(); // Check if entry has expired if (now - entry.timestamp > entry.ttl) { this.delete(key); logger.debug('Cache entry expired', { key: key.substring(0, 50), age: `${(now - entry.timestamp) / 1000}s`, }); return null; } // Update access tracking entry.accessCount++; entry.lastAccessed = now; this.updateAccessOrder(key); logger.debug('Cache hit', { key: key.substring(0, 50), accessCount: entry.accessCount, age: `${(now - entry.timestamp) / 1000}s`, }); return entry.value; } /** * Deletes a value from the cache. */ delete(key) { const existed = this.cache.delete(key); if (existed) { this.removeFromAccessOrder(key); logger.debug('Cache entry deleted', { key: key.substring(0, 50) }); } return existed; } /** * Clears all cache entries. */ clear() { const size = this.cache.size; this.cache.clear(); this.accessOrder = []; logger.info('Cache cleared', { entriesRemoved: size }); } /** * Gets cache statistics. */ getStats() { const now = Date.now(); let oldestEntry; let oldestTime = now; for (const [key, entry] of this.cache.entries()) { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestEntry = { key: key.substring(0, 30), age: Math.floor((now - entry.timestamp) / 1000), }; } } return { size: this.cache.size, maxSize: this.config.maxSize, oldestEntry, }; } /** * Generates a cache key for search operations. */ generateSearchKey(searchType, query, options = {}) { // Create a deterministic key from search parameters const optionsStr = JSON.stringify(options, Object.keys(options).sort()); const key = `search:${searchType}:${query}:${optionsStr}`; // Hash long keys to prevent memory issues if (key.length > 200) { return `search:${searchType}:${this.simpleHash(key)}`; } return key; } /** * Evicts least recently used entries. */ evictLRU() { if (this.accessOrder.length === 0) return; const lruKey = this.accessOrder[0]; this.cache.delete(lruKey); this.accessOrder.shift(); logger.debug('LRU eviction', { evictedKey: lruKey.substring(0, 50), cacheSize: this.cache.size, }); } /** * Updates access order for LRU tracking. */ updateAccessOrder(key) { this.removeFromAccessOrder(key); this.accessOrder.push(key); } /** * Removes key from access order array. */ removeFromAccessOrder(key) { const index = this.accessOrder.indexOf(key); if (index > -1) { this.accessOrder.splice(index, 1); } } /** * Simple hash function for long cache keys. */ simpleHash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(36); } /** * Cleanup expired entries (can be called periodically). */ cleanup() { const now = Date.now(); let removedCount = 0; for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > entry.ttl) { this.delete(key); removedCount++; } } if (removedCount > 0) { logger.info('Cache cleanup completed', { removedEntries: removedCount }); } return removedCount; } } exports.InMemoryCacheService = InMemoryCacheService; /** * Default cache instance for the API. */ exports.defaultCacheService = new InMemoryCacheService({ maxSize: 1000, defaultTTL: 5 * 60 * 1000, // 5 minutes });