UNPKG

@voilajsx/appkit

Version:

Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development

203 lines 7.23 kB
/** * Ultra-simple caching that just works with automatic Redis/Memory strategy selection * @module @voilajsx/appkit/cache * @file src/cache/index.ts * * @llm-rule WHEN: Building apps that need caching with zero configuration * @llm-rule AVOID: Complex cache setups - this auto-detects Redis vs Memory from environment * @llm-rule NOTE: Uses cacheClass.get(namespace) pattern like auth - get() → cache.set() → done * @llm-rule NOTE: Common pattern - cacheClass.get('users') → cache.set('user:123', data) → cache.get('user:123') */ import { CacheClass } from './cache.js'; import { getSmartDefaults } from './defaults.js'; // Global cache instances for performance (like auth module) let globalConfig = null; const namedCaches = new Map(); /** * Get cache instance for specific namespace - the only function you need to learn * Strategy auto-detected from environment (REDIS_URL = Redis, no REDIS_URL = Memory) * @llm-rule WHEN: Need caching in any part of your app - this is your main entry point * @llm-rule AVOID: Creating CacheClass directly - always use this function * @llm-rule NOTE: Typical flow - get(namespace) → cache.set() → cache.get() → cached data */ function get(namespace = 'app') { // Validate namespace if (!namespace || typeof namespace !== 'string') { throw new Error('Cache namespace must be a non-empty string'); } if (!/^[a-zA-Z0-9_-]+$/.test(namespace)) { throw new Error('Cache namespace must contain only letters, numbers, underscores, and hyphens'); } // Lazy initialization - parse environment once (like auth) if (!globalConfig) { globalConfig = getSmartDefaults(); } // Return cached instance if exists if (namedCaches.has(namespace)) { return namedCaches.get(namespace); } // Create new cache instance for namespace const cacheInstance = new CacheClass(globalConfig, namespace); // Auto-connect on first use cacheInstance.connect().catch((error) => { console.error(`[AppKit] Cache auto-connect failed for namespace "${namespace}":`, error.message); }); namedCaches.set(namespace, cacheInstance); return cacheInstance; } /** * Clear all cache instances and disconnect - essential for testing * @llm-rule WHEN: Testing cache logic with different configurations or app shutdown * @llm-rule AVOID: Using in production except for graceful shutdown */ async function clear() { const disconnectPromises = []; for (const [namespace, cache] of namedCaches) { disconnectPromises.push(cache.disconnect().catch((error) => { console.error(`[AppKit] Cache disconnect failed for namespace "${namespace}":`, error.message); })); } await Promise.all(disconnectPromises); namedCaches.clear(); globalConfig = null; } /** * Reset cache configuration (useful for testing) * @llm-rule WHEN: Testing cache logic with different environment configurations * @llm-rule AVOID: Using in production - only for tests and development */ async function reset(newConfig) { // Clear existing instances await clear(); // Reset configuration if (newConfig) { const defaults = getSmartDefaults(); globalConfig = { ...defaults, ...newConfig }; } else { globalConfig = null; // Will reload from environment on next get() } } /** * Get active cache strategy for debugging * @llm-rule WHEN: Debugging or health checks to see which strategy is active (Redis vs Memory) * @llm-rule AVOID: Using for application logic - cache should be transparent */ function getStrategy() { if (!globalConfig) { globalConfig = getSmartDefaults(); } return globalConfig.strategy; } /** * Get all active cache namespaces * @llm-rule WHEN: Debugging or monitoring which cache namespaces are active * @llm-rule AVOID: Using for business logic - this is for observability only */ function getActiveNamespaces() { return Array.from(namedCaches.keys()); } /** * Get cache configuration summary for debugging * @llm-rule WHEN: Health checks or debugging cache configuration * @llm-rule AVOID: Exposing sensitive connection details - this only shows safe info */ function getConfig() { if (!globalConfig) { globalConfig = getSmartDefaults(); } return { strategy: globalConfig.strategy, keyPrefix: globalConfig.keyPrefix, defaultTTL: globalConfig.defaultTTL, activeNamespaces: getActiveNamespaces(), environment: globalConfig.environment.nodeEnv, }; } /** * Check if Redis is available and configured * @llm-rule WHEN: Conditional logic based on cache capabilities * @llm-rule AVOID: Complex cache detection - just use cache normally, it handles strategy */ function hasRedis() { return !!process.env.REDIS_URL; } /** * Flush all caches across all namespaces (dangerous) * @llm-rule WHEN: Testing or emergency cache clearing across all namespaces * @llm-rule AVOID: Using in production - this clears ALL cached data in ALL namespaces * @llm-rule NOTE: Only use for testing or emergency situations */ async function flushAll() { try { const clearPromises = []; for (const cache of namedCaches.values()) { clearPromises.push(cache.clear()); } const results = await Promise.all(clearPromises); return results.every(result => result === true); } catch (error) { console.error('[AppKit] Cache flushAll error:', error.message); return false; } } /** * Graceful shutdown for all cache instances * @llm-rule WHEN: App shutdown or process termination * @llm-rule AVOID: Abrupt process exit - graceful shutdown prevents data loss */ async function shutdown() { console.log('🔄 [AppKit] Cache graceful shutdown...'); try { await clear(); console.log('✅ [AppKit] Cache shutdown complete'); } catch (error) { console.error('❌ [AppKit] Cache shutdown error:', error.message); } } /** * Single caching export with minimal API (like auth module) */ export const cacheClass = { // Core method (like auth.get()) get, // Utility methods clear, reset, getStrategy, getActiveNamespaces, getConfig, hasRedis, flushAll, shutdown, }; export { CacheClass } from './cache.js'; // Default export export default cacheClass; // Auto-setup graceful shutdown handlers if (typeof process !== 'undefined') { // Handle graceful shutdown const shutdownHandler = () => { shutdown().finally(() => { process.exit(0); }); }; process.on('SIGTERM', shutdownHandler); process.on('SIGINT', shutdownHandler); // Handle uncaught errors process.on('uncaughtException', (error) => { console.error('[AppKit] Uncaught exception during cache operation:', error); shutdown().finally(() => { process.exit(1); }); }); process.on('unhandledRejection', (reason) => { console.error('[AppKit] Unhandled rejection during cache operation:', reason); shutdown().finally(() => { process.exit(1); }); }); } //# sourceMappingURL=index.js.map