enhancment complete with phase 10

This commit is contained in:
dimitar committed 2026-03-10 05:37:22 +01:00
1 parent 2c4927478a
commit 6adb541967
44 files changed
+7820 -845

No files matched your search

+253
View File
@@ -0,0 +1,253 @@
/**
* Simple in-memory cache with TTL support
*
* Usage:
* ```typescript
* const cache = new Cache<User[]>(5 * 60 * 1000); // 5 minutes TTL
*
* const users = await cache.getOrSet('all-users', async () => {
* return await db.getAllUsers();
* });
* ```
*/
import log from "../logger";
export interface CacheEntry<T> {
value: T;
expiresAt: number;
createdAt: number;
}
export interface CacheStats {
hits: number;
misses: number;
size: number;
hitRate: number;
}
/**
* Simple in-memory cache with TTL (Time To Live)
*/
export class Cache<T = any> {
private store: Map<string, CacheEntry<T>> = new Map();
private hits = 0;
private misses = 0;
private defaultTtl: number;
/**
* Create a new cache instance
*
* @param defaultTtl - Default time-to-live in milliseconds (default: 5 minutes)
*/
constructor(defaultTtl: number = 5 * 60 * 1000) {
this.defaultTtl = defaultTtl;
}
/**
* Get a value from cache
*
* @param key - Cache key
* @returns Cached value or null if expired/not found
*/
get(key: string): T | null {
const entry = this.store.get(key);
if (!entry) {
this.misses++;
return null;
}
// Check if expired
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
this.misses++;
log.debug("Cache expired", { key });
return null;
}
this.hits++;
log.debug("Cache hit", { key });
return entry.value;
}
/**
* Set a value in cache
*
* @param key - Cache key
* @param value - Value to cache
* @param ttl - Time-to-live in milliseconds (optional, uses default if not provided)
*/
set(key: string, value: T, ttl?: number): void {
const expiresAt = Date.now() + (ttl ?? this.defaultTtl);
this.store.set(key, {
value,
expiresAt,
createdAt: Date.now(),
});
log.debug("Cache set", { key, ttl: ttl ?? this.defaultTtl });
}
/**
* Get a value from cache, or compute and cache it if not found
*
* @param key - Cache key
* @param fn - Function to compute value if not in cache
* @param ttl - Time-to-live in milliseconds (optional)
* @returns Cached or computed value
*/
async getOrSet(key: string, fn: () => Promise<T>, ttl?: number): Promise<T> {
const cached = this.get(key);
if (cached !== null) {
return cached;
}
log.debug("Cache miss, computing value", { key });
const value = await fn();
this.set(key, value, ttl);
return value;
}
/**
* Delete a value from cache
*
* @param key - Cache key
* @returns True if deleted, false if not found
*/
delete(key: string): boolean {
const deleted = this.store.delete(key);
log.debug("Cache delete", { key, deleted });
return deleted;
}
/**
* Clear all entries from cache
*/
clear(): void {
const size = this.store.size;
this.store.clear();
this.hits = 0;
this.misses = 0;
log.info("Cache cleared", { entriesCleared: size });
}
/**
* Get cache statistics
*/
getStats(): CacheStats {
const total = this.hits + this.misses;
return {
hits: this.hits,
misses: this.misses,
size: this.store.size,
hitRate: total > 0 ? this.hits / total : 0,
};
}
/**
* Remove expired entries from cache
*/
cleanup(): number {
const now = Date.now();
let removed = 0;
for (const [key, entry] of Array.from(this.store.entries())) {
if (now > entry.expiresAt) {
this.store.delete(key);
removed++;
}
}
if (removed > 0) {
log.info("Cache cleanup completed", { entriesRemoved: removed });
}
return removed;
}
/**
* Check if a key exists in cache (and is not expired)
*/
has(key: string): boolean {
const entry = this.store.get(key);
if (!entry) return false;
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
return false;
}
return true;
}
/**
* Get all keys in cache
*/
keys(): string[] {
return Array.from(this.store.keys());
}
/**
* Get cache size (number of entries)
*/
size(): number {
return this.store.size;
}
}
/**
* Global cache instances for common use cases
*/
// User data cache (5 minutes)
export const userCache = new Cache<any>(5 * 60 * 1000);
// Fitness goals cache (2 minutes)
export const fitnessGoalsCache = new Cache<any>(2 * 60 * 1000);
// Recommendations cache (3 minutes)
export const recommendationsCache = new Cache<any>(3 * 60 * 1000);
// Dashboard stats cache (1 minute - frequently changing data)
export const dashboardStatsCache = new Cache<any>(1 * 60 * 1000);
/**
* Setup automatic cache cleanup (run every 5 minutes)
*/
export function setupCacheCleanup(): NodeJS.Timeout {
const interval = setInterval(
() => {
log.debug("Running scheduled cache cleanup");
userCache.cleanup();
fitnessGoalsCache.cleanup();
recommendationsCache.cleanup();
dashboardStatsCache.cleanup();
},
5 * 60 * 1000,
); // Every 5 minutes
return interval;
}
/**
* Clear all caches
*/
export function clearAllCaches(): void {
userCache.clear();
fitnessGoalsCache.clear();
recommendationsCache.clear();
dashboardStatsCache.clear();
log.info("All caches cleared");
}
/**
* Get stats for all caches
*/
export function getAllCacheStats(): Record<string, CacheStats> {
return {
users: userCache.getStats(),
fitnessGoals: fitnessGoalsCache.getStats(),
recommendations: recommendationsCache.getStats(),
dashboardStats: dashboardStatsCache.getStats(),
};
}