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
JavaScript
"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
});