interface CacheEntry { data: T; expiresAt: number; } export class TtlCache { private store = new Map>(); private defaultTtlMs: number; private maxSize: number; constructor(ttlSeconds = 60, maxSize = 1000) { this.defaultTtlMs = ttlSeconds * 1000; this.maxSize = maxSize; } get(key: string): T | undefined { const entry = this.store.get(key) as CacheEntry | undefined; if (!entry) return undefined; if (Date.now() > entry.expiresAt) { this.store.delete(key); return undefined; } // LRU: move to end on access this.store.delete(key); this.store.set(key, entry); return entry.data; } set(key: string, data: T, ttlMs?: number): void { const effectiveTtl = ttlMs ?? this.defaultTtlMs; // If key already exists, delete first to refresh insertion order if (this.store.has(key)) { this.store.delete(key); } else if (this.store.size >= this.maxSize) { // Evict oldest entry (first in Map insertion order) const oldestKey = this.store.keys().next().value; if (oldestKey !== undefined) { this.store.delete(oldestKey); } } this.store.set(key, { data, expiresAt: Date.now() + effectiveTtl }); } /** Delete all keys that start with the given prefix */ invalidate(prefix: string): void { for (const key of Array.from(this.store.keys())) { if (key.startsWith(prefix)) { this.store.delete(key); } } } /** Alias for invalidate — delete all keys starting with prefix */ invalidatePrefix(prefix: string): void { this.invalidate(prefix); } /** Delete all keys that end with the given suffix */ invalidateSuffix(suffix: string): void { for (const key of Array.from(this.store.keys())) { if (key.endsWith(suffix)) { this.store.delete(key); } } } /** Remove a single key */ delete(key: string): void { this.store.delete(key); } /** Completely clear the cache */ clear(): void { this.store.clear(); } /** Current number of entries (including possibly expired ones) */ get size(): number { return this.store.size; } } export const formsCache = new TtlCache(60); export const tasksMinimalCache = new TtlCache(60); /** * Кэш множеств доступных задач (getAccessibleTaskIds). * Ключ: accessible::. * Значение: Set | null (null = admin/view_all, фильтрация не нужна). * TTL 45 сек + явная инвалидация при изменении ролей, делегирований, * правил доступа к формам и назначений задач. */ export const accessibleTasksCache = new TtlCache(45); export function accessibleTasksKey(userId: number, organizationId: number): string { return `accessible:${userId}:${organizationId}`; } /** Точечная инвалидация для одного пользователя (назначение, членство в роли и т.п.) */ export function invalidateAccessibleTasksForUser(userId: number, organizationId: number): void { accessibleTasksCache.delete(accessibleTasksKey(userId, organizationId)); } /** Массовая инвалидация по организации (роли, правила доступа к формам и т.п.) */ export function invalidateAccessibleTasksForOrg(organizationId: number): void { accessibleTasksCache.invalidateSuffix(`:${organizationId}`); }