import { CacheProvider } from '../interfaces/cache-provider'; import { keyMatchesPattern } from '../utils/key-generator'; import { getLogger } from '../utils/logger'; /** * Pattern-based cache invalidator */ export class PatternInvalidator { private provider: CacheProvider; private logger: any; /** * Creates a new PatternInvalidator * @param provider Cache provider to invalidate * @param loggerOptions Logger options */ constructor( provider: CacheProvider, loggerOptions: { enableLogging?: boolean; logLevel?: 'debug' | 'info' | 'warn' | 'error' } = {} ) { this.provider = provider; this.logger = getLogger('cache:invalidator:pattern', loggerOptions); } /** * Invalidate keys matching a pattern * @param pattern Pattern to match against (supports * and ? wildcards) * @param maxKeys Maximum number of keys to invalidate * @returns Promise resolving to the number of invalidated keys */ async invalidateByPattern(pattern: string, maxKeys: number = 1000): Promise { try { // Get all keys from the provider // Note: This operation could be expensive for large caches const stats = await this.provider.getStats(); this.logger.debug(`Starting pattern invalidation for pattern: ${pattern}`, { totalKeys: stats.size, maxKeys }); // Some providers may not support listing all keys // Redis can use SCAN with pattern matching directly if ('scanKeys' in this.provider && typeof this.provider.scanKeys === 'function') { // Use provider-specific method if available const deletedCount = await (this.provider as any).scanKeys(pattern, maxKeys, true); this.logger.info(`Invalidated ${deletedCount} keys matching pattern: ${pattern}`); return deletedCount; } else { // Fallback to manual matching (less efficient) const allKeys = await this.getAllKeys(maxKeys); const matchingKeys = allKeys.filter(key => keyMatchesPattern(key, pattern)); if (matchingKeys.length > 0) { await this.provider.deleteMany(matchingKeys); } this.logger.info(`Invalidated ${matchingKeys.length} keys matching pattern: ${pattern}`); return matchingKeys.length; } } catch (error) { this.logger.error(`Error invalidating by pattern: ${pattern}`, error); throw error; } } /** * Get all keys from the provider * @param maxKeys Maximum number of keys to return * @returns Promise resolving to an array of keys */ private async getAllKeys(maxKeys: number): Promise { // This is a simplified implementation // In a real implementation, we would use provider-specific methods to get keys efficiently if ('getAllKeys' in this.provider && typeof this.provider.getAllKeys === 'function') { return (this.provider as any).getAllKeys(maxKeys); } // Fallback implementation that may not work for all providers this.logger.warn('Using fallback getAllKeys method which may not be efficient'); return []; } }