import { createHash } from 'crypto'; /** * Options for key generation */ export interface KeyGeneratorOptions { /** * Prefix to add to the key */ prefix?: string; /** * Namespace for the key (used for grouping related keys) */ namespace?: string; /** * Whether to include the type in the key */ includeType?: boolean; /** * Hash algorithm to use (default: md5) */ hashAlgorithm?: 'md5' | 'sha1' | 'sha256'; /** * Maximum key length */ maxLength?: number; /** * Whether to hash the key */ hash?: boolean; } /** * Generate a cache key * @param key Key or object to generate key from * @param options Key generation options * @returns Generated cache key */ export function generateKey(key: string | object, options: KeyGeneratorOptions = {}): string { const { prefix = '', namespace = '', includeType = true, hashAlgorithm = 'md5', maxLength = 250, hash = true, } = options; let baseKey: string; let type: string = ''; // Convert object to string if necessary if (typeof key === 'object' && key !== null) { try { if (includeType) { type = key.constructor.name; } // Handle special objects if (key instanceof Date) { baseKey = key.toISOString(); } else if (key instanceof RegExp) { baseKey = key.toString(); } else if (key instanceof URL) { baseKey = key.href; } else if (Array.isArray(key)) { // Sort arrays to ensure consistent keys baseKey = JSON.stringify(key.sort()); } else if (key instanceof Set) { // Convert to sorted array for consistency baseKey = JSON.stringify(Array.from(key).sort()); } else if (key instanceof Map) { // Convert to sorted entries for consistency baseKey = JSON.stringify(Array.from(key.entries()).sort()); } else { // Regular object - sort keys for consistency baseKey = JSON.stringify( Object.fromEntries( Object.entries(key).sort(([a], [b]) => a.localeCompare(b)) ) ); } } catch (error) { // Fallback to string representation baseKey = String(key); } } else { // Simple string key baseKey = String(key); } // Combine parts let combinedKey = ''; if (prefix) { combinedKey += `${prefix}:`; } if (namespace) { combinedKey += `${namespace}:`; } if (type && includeType) { combinedKey += `${type}:`; } // Hash the base key if requested or if it's too long if (hash || baseKey.length > maxLength) { combinedKey += createHash(hashAlgorithm).update(baseKey).digest('hex'); } else { combinedKey += baseKey; } // Truncate if necessary if (maxLength > 0 && combinedKey.length > maxLength) { combinedKey = combinedKey.substring(0, maxLength); } return combinedKey; } /** * Generate a pattern key for pattern matching * @param pattern Pattern to match * @returns Pattern key */ export function generatePatternKey(pattern: string): string { // Replace wildcards with regex equivalents return pattern .replace(/\*/g, '.*') .replace(/\?/g, '.'); } /** * Check if a key matches a pattern * @param key Key to check * @param pattern Pattern to match against * @returns True if the key matches the pattern */ export function keyMatchesPattern(key: string, pattern: string): boolean { const regexPattern = generatePatternKey(pattern); const regex = new RegExp(`^${regexPattern}$`); return regex.test(key); }