UNPKG

@varia-bly/variably-sdk

Version:

Official JavaScript/TypeScript SDK for Variably feature flags, experimentation, and real-time dynamic configurations

306 lines 8.99 kB
"use strict"; /** * Cache management for Variably SDK */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BrowserCache = exports.MemoryCache = exports.CacheManager = void 0; class CacheManager { constructor(config, logger) { this.cleanupInterval = null; this.config = { ttl: config.ttl || 300000, // 5 minutes maxSize: config.maxSize || 1000, enabled: config.enabled !== false }; this.logger = logger; this.cache = new Map(); if (this.config.enabled) { this.startCleanupTimer(); } } /** * Get a value from cache */ get(key) { if (!this.config.enabled) { return null; } const entry = this.cache.get(key); if (!entry) { return null; } // Check if entry has expired if (this.isExpired(entry)) { this.cache.delete(key); this.logger.debug('Cache entry expired', { key }); return null; } this.logger.debug('Cache hit', { key }); return entry.value; } /** * Set a value in cache */ set(key, value, customTtl) { if (!this.config.enabled) { return; } // Enforce max size by removing oldest entries if (this.cache.size >= this.config.maxSize) { this.evictOldest(); } const ttl = customTtl || this.config.ttl; const entry = { value, timestamp: Date.now(), ttl }; this.cache.set(key, entry); this.logger.debug('Cache entry set', { key, ttl }); } /** * Remove a value from cache */ delete(key) { const deleted = this.cache.delete(key); if (deleted) { this.logger.debug('Cache entry deleted', { key }); } return deleted; } /** * Clear all cache entries */ clear() { const size = this.cache.size; this.cache.clear(); this.logger.debug('Cache cleared', { entriesRemoved: size }); } /** * Clear cache entries matching a pattern (supports * wildcard) */ clearByPattern(pattern) { if (!this.config.enabled) { return 0; } const keysToDelete = []; const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$'); for (const key of this.cache.keys()) { if (regex.test(key)) { keysToDelete.push(key); } } for (const key of keysToDelete) { this.cache.delete(key); } if (keysToDelete.length > 0) { this.logger.debug('Cache entries cleared by pattern', { pattern, entriesRemoved: keysToDelete.length }); } return keysToDelete.length; } /** * Get cache statistics */ getStats() { return { size: this.cache.size, maxSize: this.config.maxSize, hitRate: 0, // Would need to track hits/misses for this enabled: this.config.enabled }; } /** * Check if cache entry has expired */ isExpired(entry) { return Date.now() - entry.timestamp > entry.ttl; } /** * Remove oldest entries when cache is full */ evictOldest() { let oldestKey = null; let oldestTimestamp = Date.now(); for (const [key, entry] of this.cache.entries()) { if (entry.timestamp < oldestTimestamp) { oldestTimestamp = entry.timestamp; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); this.logger.debug('Cache entry evicted (oldest)', { key: oldestKey }); } } /** * Remove expired entries from cache */ cleanup() { const now = Date.now(); const keysToDelete = []; for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > entry.ttl) { keysToDelete.push(key); } } for (const key of keysToDelete) { this.cache.delete(key); } if (keysToDelete.length > 0) { this.logger.debug('Cache cleanup completed', { expiredEntries: keysToDelete.length }); } } /** * Start periodic cleanup timer */ startCleanupTimer() { // Run cleanup every minute this.cleanupInterval = setInterval(() => { this.cleanup(); }, 60000); } /** * Stop cleanup timer and clear cache */ destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } this.clear(); } } exports.CacheManager = CacheManager; /** * Simple in-memory cache for Node.js environments */ class MemoryCache extends CacheManager { constructor(config, logger) { super(config, logger); } } exports.MemoryCache = MemoryCache; /** * Browser-compatible cache using sessionStorage/localStorage */ class BrowserCache { constructor(config, logger, useSessionStorage = false) { this.config = { ttl: config.ttl || 300000, maxSize: config.maxSize || 1000, enabled: config.enabled !== false }; this.logger = logger; this.keyPrefix = 'variably_cache_'; if (typeof window !== 'undefined') { this.storage = useSessionStorage ? sessionStorage : localStorage; } else { // Fallback for non-browser environments this.storage = { getItem: () => null, setItem: () => { }, removeItem: () => { }, clear: () => { }, length: 0, key: () => null }; } } get(key) { if (!this.config.enabled) { return null; } try { const item = this.storage.getItem(this.keyPrefix + key); if (!item) { return null; } const entry = JSON.parse(item); // Check expiration if (Date.now() - entry.timestamp > entry.ttl) { this.delete(key); return null; } return entry.value; } catch (error) { this.logger.warn('Failed to read from browser cache', { key, error: error instanceof Error ? error.message : String(error) }); return null; } } set(key, value, customTtl) { if (!this.config.enabled) { return; } try { const ttl = customTtl || this.config.ttl; const entry = { value, timestamp: Date.now(), ttl }; this.storage.setItem(this.keyPrefix + key, JSON.stringify(entry)); } catch (error) { this.logger.warn('Failed to write to browser cache', { key, error: error instanceof Error ? error.message : String(error) }); } } delete(key) { try { const fullKey = this.keyPrefix + key; const existed = this.storage.getItem(fullKey) !== null; this.storage.removeItem(fullKey); return existed; } catch (error) { this.logger.warn('Failed to delete from browser cache', { key, error: error instanceof Error ? error.message : String(error) }); return false; } } clear() { try { const keys = []; for (let i = 0; i < this.storage.length; i++) { const key = this.storage.key(i); if (key && key.startsWith(this.keyPrefix)) { keys.push(key); } } for (const key of keys) { this.storage.removeItem(key); } } catch (error) { this.logger.warn('Failed to clear browser cache', { error: error instanceof Error ? error.message : String(error) }); } } getStats() { let size = 0; try { for (let i = 0; i < this.storage.length; i++) { const key = this.storage.key(i); if (key && key.startsWith(this.keyPrefix)) { size++; } } } catch (error) { this.logger.warn('Failed to get cache stats', { error: error instanceof Error ? error.message : String(error) }); } return { size, maxSize: this.config.maxSize, hitRate: 0, enabled: this.config.enabled }; } destroy() { this.clear(); } } exports.BrowserCache = BrowserCache; //# sourceMappingURL=cache.js.map