Initial commit of massive v2 rewrite
This commit is contained in:
parent
1025f3b523
commit
dc120fe78a
55 changed files with 21733 additions and 0 deletions
278
src/utils/cache-utils.ts
Normal file
278
src/utils/cache-utils.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// =============================================================================
|
||||
// CENTRALIZED CACHE CLEANUP UTILITY
|
||||
// =============================================================================
|
||||
// Consolidates all cache cleanup logic to prevent duplication
|
||||
|
||||
import { parseDuration } from './time.js';
|
||||
|
||||
export interface CacheEntry<T = unknown> {
|
||||
readonly value: T;
|
||||
readonly timestamp: number;
|
||||
readonly ttl?: number;
|
||||
}
|
||||
|
||||
export interface CacheOptions {
|
||||
readonly maxSize?: number;
|
||||
readonly defaultTTL?: number;
|
||||
readonly cleanupRatio?: number; // What percentage to clean when over limit (0.0-1.0)
|
||||
}
|
||||
|
||||
export interface CacheCleanupResult {
|
||||
readonly expired: number;
|
||||
readonly overflow: number;
|
||||
readonly total: number;
|
||||
}
|
||||
|
||||
export interface TTLCacheEntry {
|
||||
readonly data: unknown;
|
||||
readonly expires: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic TTL-based cache cleaner
|
||||
*/
|
||||
export class TTLCacheCleaner {
|
||||
/**
|
||||
* Cleans expired entries from a Map-based cache with TTL entries
|
||||
* @param cache - Map cache to clean
|
||||
* @param now - Current timestamp (defaults to Date.now())
|
||||
* @returns Number of entries removed
|
||||
*/
|
||||
static cleanExpired<K>(
|
||||
cache: Map<K, TTLCacheEntry>,
|
||||
now: number = Date.now()
|
||||
): number {
|
||||
let cleaned = 0;
|
||||
|
||||
for (const [key, entry] of cache.entries()) {
|
||||
if (now >= entry.expires) {
|
||||
cache.delete(key);
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans cache by removing oldest entries when over size limit
|
||||
* @param cache - Map cache to clean
|
||||
* @param maxSize - Maximum allowed size
|
||||
* @param cleanupRatio - What percentage to remove (default 0.25 = 25%)
|
||||
* @returns Number of entries removed
|
||||
*/
|
||||
static cleanOverflow<K>(
|
||||
cache: Map<K, TTLCacheEntry>,
|
||||
maxSize: number,
|
||||
cleanupRatio: number = 0.25
|
||||
): number {
|
||||
if (cache.size <= maxSize) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const targetSize = Math.floor(maxSize * (1 - cleanupRatio));
|
||||
const toRemove = cache.size - targetSize;
|
||||
|
||||
// Remove oldest entries (based on Map insertion order)
|
||||
let removed = 0;
|
||||
for (const key of cache.keys()) {
|
||||
if (removed >= toRemove) break;
|
||||
cache.delete(key);
|
||||
removed++;
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprehensive cache cleanup (expired + overflow)
|
||||
*/
|
||||
static cleanup<K>(
|
||||
cache: Map<K, TTLCacheEntry>,
|
||||
options: CacheOptions = {}
|
||||
): CacheCleanupResult {
|
||||
const { maxSize = 10000, cleanupRatio = 0.25 } = options;
|
||||
const now = Date.now();
|
||||
|
||||
const expired = this.cleanExpired(cache, now);
|
||||
const overflow = this.cleanOverflow(cache, maxSize, cleanupRatio);
|
||||
|
||||
return {
|
||||
expired,
|
||||
overflow,
|
||||
total: expired + overflow
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic timestamped cache cleaner (for caches with timestamp fields)
|
||||
*/
|
||||
export class TimestampCacheCleaner {
|
||||
/**
|
||||
* Cleans expired entries from cache with custom timestamp/TTL logic
|
||||
*/
|
||||
static cleanExpired<K, T extends { timestamp?: number; lastReset?: number }>(
|
||||
cache: Map<K, T>,
|
||||
ttlMs: number,
|
||||
timestampField: 'timestamp' | 'lastReset' = 'timestamp',
|
||||
now: number = Date.now()
|
||||
): number {
|
||||
let cleaned = 0;
|
||||
|
||||
for (const [key, entry] of cache.entries()) {
|
||||
const entryTime = entry[timestampField];
|
||||
if (!entryTime || (now - entryTime) > ttlMs) {
|
||||
cache.delete(key);
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans cache entries with custom expiration logic
|
||||
*/
|
||||
static cleanWithCustomLogic<K, T>(
|
||||
cache: Map<K, T>,
|
||||
shouldExpire: (key: K, value: T, now: number) => boolean,
|
||||
now: number = Date.now()
|
||||
): number {
|
||||
let cleaned = 0;
|
||||
|
||||
for (const [key, entry] of cache.entries()) {
|
||||
if (shouldExpire(key, entry, now)) {
|
||||
cache.delete(key);
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized cleaner for rate limiting caches
|
||||
*/
|
||||
export class RateLimitCacheCleaner {
|
||||
static cleanExpiredRateLimits<K>(
|
||||
cache: Map<K, { count: number; lastReset: number }>,
|
||||
windowMs: number,
|
||||
now: number = Date.now()
|
||||
): number {
|
||||
return TimestampCacheCleaner.cleanExpired(cache, windowMs, 'lastReset', now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized cleaner for reputation caches
|
||||
*/
|
||||
export class ReputationCacheCleaner {
|
||||
static cleanExpiredReputation<K>(
|
||||
cache: Map<K, { reputation: unknown; timestamp: number }>,
|
||||
ttlMs: number,
|
||||
now: number = Date.now()
|
||||
): number {
|
||||
return TimestampCacheCleaner.cleanExpired(cache, ttlMs, 'timestamp', now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* High-level cache manager for common patterns
|
||||
*/
|
||||
export class CacheManager {
|
||||
private cleanupTimers: Map<string, NodeJS.Timeout> = new Map();
|
||||
|
||||
/**
|
||||
* Sets up automatic cleanup for a cache
|
||||
*/
|
||||
setupPeriodicCleanup<K>(
|
||||
cacheName: string,
|
||||
cache: Map<K, TTLCacheEntry>,
|
||||
options: CacheOptions & { interval?: string } = {}
|
||||
): void {
|
||||
const { interval = '5m', maxSize = 10000 } = options;
|
||||
const intervalMs = parseDuration(interval);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
const result = TTLCacheCleaner.cleanup(cache, { maxSize });
|
||||
if (result.total > 0) {
|
||||
console.log(`Cache ${cacheName}: cleaned ${result.expired} expired + ${result.overflow} overflow entries`);
|
||||
}
|
||||
}, intervalMs);
|
||||
|
||||
// Store timer so it can be cleared later
|
||||
this.cleanupTimers.set(cacheName, timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops periodic cleanup for a cache
|
||||
*/
|
||||
stopPeriodicCleanup(cacheName: string): void {
|
||||
const timer = this.cleanupTimers.get(cacheName);
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
this.cleanupTimers.delete(cacheName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all periodic cleanups
|
||||
*/
|
||||
stopAllCleanups(): void {
|
||||
for (const [_name, timer] of this.cleanupTimers.entries()) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
this.cleanupTimers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton cache manager
|
||||
export const cacheManager = new CacheManager();
|
||||
|
||||
/**
|
||||
* Utility functions for common cache operations
|
||||
*/
|
||||
export const CacheUtils = {
|
||||
/**
|
||||
* Creates a TTL cache entry
|
||||
*/
|
||||
createTTLEntry<T>(value: T, ttlMs: number): TTLCacheEntry {
|
||||
return {
|
||||
data: value,
|
||||
expires: Date.now() + ttlMs
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if TTL entry is expired
|
||||
*/
|
||||
isExpired(entry: TTLCacheEntry, now: number = Date.now()): boolean {
|
||||
return now >= entry.expires;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets remaining TTL for an entry
|
||||
*/
|
||||
getRemainingTTL(entry: TTLCacheEntry, now: number = Date.now()): number {
|
||||
return Math.max(0, entry.expires - now);
|
||||
},
|
||||
|
||||
/**
|
||||
* Safely gets cache entry, returning null if expired
|
||||
*/
|
||||
safeGet<T>(cache: Map<string, TTLCacheEntry>, key: string): T | null {
|
||||
const entry = cache.get(key);
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isExpired(entry)) {
|
||||
cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.data as T;
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue