import NodeCache from 'node-cache';
import crypto from 'crypto';
import { Loggers } from './logger.js';
const logger = Loggers.cache;
/**
* Cache configuration
*/
interface CacheConfig {
stdTTL: number; // Standard time to live in seconds
checkperiod: number; // Check period for expired keys
useClones: boolean; // Whether to clone values
maxKeys: number; // Maximum number of keys
}
/**
* Default cache configuration
*/
const DEFAULT_CONFIG: CacheConfig = {
stdTTL: 3600, // 1 hour default TTL
checkperiod: 600, // Check every 10 minutes
useClones: true, // Clone values for safety
maxKeys: 1000 // Maximum 1000 cached items
};
/**
* Cache statistics
*/
interface CacheStats {
hits: number;
misses: number;
keys: number;
hitRate: number;
}
/**
* Logic operation cache for improved performance
*/
export class LogicCache {
private cache: NodeCache;
private hits: number = 0;
private misses: number = 0;
constructor(config: Partial<CacheConfig> = {}) {
const finalConfig = { ...DEFAULT_CONFIG, ...config };
this.cache = new NodeCache({
stdTTL: finalConfig.stdTTL,
checkperiod: finalConfig.checkperiod,
useClones: finalConfig.useClones,
maxKeys: finalConfig.maxKeys
});
// Set up event listeners
this.cache.on('expired', (key, value) => {
logger.debug(`Cache key expired: ${key}`);
});
this.cache.on('flush', () => {
logger.info('Cache flushed');
});
logger.info('Logic cache initialized', { config: finalConfig });
}
/**
* Generate a cache key from operation parameters
* @param system The logical system
* @param operation The operation
* @param input The input
* @param format The format
* @returns A unique cache key
*/
generateCacheKey(
system: string,
operation: string,
input: string,
format: string = 'natural'
): string {
const keyData = `${system}:${operation}:${input}:${format}`;
return crypto
.createHash('md5')
.update(keyData)
.digest('hex');
}
/**
* Get a value from cache
* @param key The cache key
* @returns The cached value or undefined
*/
get<T>(key: string): T | undefined {
try {
const value = this.cache.get<T>(key);
if (value !== undefined) {
this.hits++;
logger.debug(`Cache hit: ${key}`);
return value;
} else {
this.misses++;
logger.debug(`Cache miss: ${key}`);
return undefined;
}
} catch (error) {
logger.error('Cache get error', { key, error });
return undefined;
}
}
/**
* Set a value in cache
* @param key The cache key
* @param value The value to cache
* @param ttl Optional TTL in seconds
*/
set<T>(key: string, value: T, ttl?: number): void {
try {
const success = ttl
? this.cache.set(key, value, ttl)
: this.cache.set(key, value);
if (success) {
logger.debug(`Cache set: ${key}`, { ttl });
} else {
logger.warn(`Failed to set cache: ${key}`);
}
} catch (error) {
logger.error('Cache set error', { key, error });
}
}
/**
* Delete a value from cache
* @param key The cache key
*/
delete(key: string): void {
try {
const deleted = this.cache.del(key);
if (deleted > 0) {
logger.debug(`Cache deleted: ${key}`);
}
} catch (error) {
logger.error('Cache delete error', { key, error });
}
}
/**
* Clear all cached values
*/
flush(): void {
this.cache.flushAll();
this.hits = 0;
this.misses = 0;
logger.info('Cache flushed');
}
/**
* Get cache statistics
* @returns Cache statistics
*/
getStats(): CacheStats {
const keys = this.cache.keys().length;
const hitRate = this.hits + this.misses > 0
? this.hits / (this.hits + this.misses)
: 0;
return {
hits: this.hits,
misses: this.misses,
keys,
hitRate
};
}
/**
* Get detailed cache information
* @returns Detailed cache info
*/
getInfo(): {
stats: CacheStats;
keys: string[];
memoryUsage: number;
} {
const stats = this.getStats();
const keys = this.cache.keys();
// Estimate memory usage
let memoryUsage = 0;
keys.forEach(key => {
const value = this.cache.get(key);
if (value) {
memoryUsage += JSON.stringify(value).length;
}
});
return {
stats,
keys,
memoryUsage
};
}
/**
* Check if a key exists in cache
* @param key The cache key
* @returns True if key exists
*/
has(key: string): boolean {
return this.cache.has(key);
}
/**
* Get TTL for a key
* @param key The cache key
* @returns TTL in milliseconds or undefined
*/
getTtl(key: string): number | undefined {
const ttl = this.cache.getTtl(key);
return ttl || undefined;
}
/**
* Set TTL for a key
* @param key The cache key
* @param ttl TTL in seconds
*/
setTtl(key: string, ttl: number): boolean {
return this.cache.ttl(key, ttl);
}
}
/**
* Singleton cache instance
*/
export const logicCache = new LogicCache();
/**
* Cache decorator for methods
* @param ttl Optional TTL in seconds
*/
export function Cacheable(ttl?: number) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// Generate cache key from method name and arguments
const cacheKey = crypto
.createHash('md5')
.update(`${target.constructor.name}:${propertyKey}:${JSON.stringify(args)}`)
.digest('hex');
// Check cache
const cached = logicCache.get(cacheKey);
if (cached !== undefined) {
return cached;
}
// Call original method
const result = originalMethod.apply(this, args);
// Cache result
logicCache.set(cacheKey, result, ttl);
return result;
};
return descriptor;
};
}