UNPKG

runtime-memory-cache

Version:

A lightweight, high-performance in-memory cache for Node.js with TTL support, configurable eviction policies (FIFO/LRU), statistics tracking, and zero dependencies.

219 lines (218 loc) 6.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RuntimeMemoryCache = void 0; const utils_1 = require("./utils"); const stats_1 = require("./stats"); /** * A lightweight in-memory cache with TTL support, configurable eviction policies, * memory tracking, and comprehensive error handling */ class RuntimeMemoryCache { constructor(options = {}) { this.store = new Map(); // Validate options utils_1.ValidationUtils.validateCacheOptions(options); this.ttl = options.ttl; this.maxSize = options.maxSize || 1000; this.evictionPolicy = options.evictionPolicy || 'FIFO'; this.statsTracker = options.enableStats ? new stats_1.StatsTracker(this.maxSize) : undefined; } /** * Store a value in the cache with optional TTL override */ set(key, value, ttl) { try { // Validate inputs utils_1.ValidationUtils.validateKey(key); if (ttl !== undefined) { utils_1.ValidationUtils.validateTTL(ttl); } const existingEntry = this.store.get(key); const isUpdate = !!existingEntry && !utils_1.CacheUtils.isExpired(existingEntry); // Handle cache size limit with configurable eviction policy if (!isUpdate && this.store.size >= this.maxSize) { this.evictEntry(); } const expiresAt = utils_1.CacheUtils.calculateExpiresAt(ttl, this.ttl); let entry; if (isUpdate) { // Preserve createdAt, update lastAccessedAt for existing entries entry = { value, expiresAt, createdAt: existingEntry.createdAt, lastAccessedAt: Date.now() }; // For LRU: remove and re-add to move to end (most recent position) if (this.evictionPolicy === 'LRU') { this.store.delete(key); } } else { // Create new entry for new keys entry = utils_1.CacheUtils.createEntry(value, expiresAt); } this.store.set(key, entry); this.updateStats(); } catch (error) { // Re-throw validation errors throw error; } } /** * Retrieve a value from the cache */ get(key) { var _a, _b, _c; try { utils_1.ValidationUtils.validateKey(key); } catch (error) { // For get operations, return undefined for invalid keys instead of throwing return undefined; } const entry = this.store.get(key); if (!entry) { (_a = this.statsTracker) === null || _a === void 0 ? void 0 : _a.recordMiss(); return undefined; } if (utils_1.CacheUtils.isExpired(entry)) { this.store.delete(key); (_b = this.statsTracker) === null || _b === void 0 ? void 0 : _b.recordMiss(); this.updateStats(); return undefined; } utils_1.CacheUtils.updateAccessTime(entry); // LRU: move the key to the end by reinserting while preserving createdAt/expiresAt if (this.evictionPolicy === 'LRU') { this.store.delete(key); this.store.set(key, entry); } (_c = this.statsTracker) === null || _c === void 0 ? void 0 : _c.recordHit(); return entry.value; } /** * Check if a key exists and is not expired */ has(key) { try { utils_1.ValidationUtils.validateKey(key); } catch (error) { // For has operations, return false for invalid keys return false; } const entry = this.store.get(key); if (!entry) { return false; } if (utils_1.CacheUtils.isExpired(entry)) { this.store.delete(key); this.updateStats(); return false; } // TODO: Add logic to update the access time for a cache entry only if the user has configured the cache to update access time on access ("touch"). // Update the access time for the entry. utils_1.CacheUtils.updateAccessTime(entry); // LRU: move the key to the end by reinserting while preserving createdAt/expiresAt if (this.evictionPolicy === 'LRU') { this.store.delete(key); this.store.set(key, entry); } return true; } /** * Delete a specific key from the cache */ del(key) { try { utils_1.ValidationUtils.validateKey(key); } catch (error) { // For delete operations, return false for invalid keys return false; } const deleted = this.store.delete(key); if (deleted) { this.updateStats(); } return deleted; } /** * Get current cache size */ size() { return this.store.size; } /** * Clear all entries from the cache */ clear() { this.store.clear(); this.updateStats(); } /** * Get all keys in the cache */ keys() { return Array.from(this.store.keys()); } /** * Manually cleanup expired entries */ cleanup() { const removedCount = utils_1.CacheUtils.cleanupExpired(this.store); if (removedCount > 0) { this.updateStats(); } return removedCount; } /** * Get cache statistics (if enabled) */ getStats() { var _a; return ((_a = this.statsTracker) === null || _a === void 0 ? void 0 : _a.getStats()) || null; } /** * Reset statistics (if enabled) */ resetStats() { var _a; (_a = this.statsTracker) === null || _a === void 0 ? void 0 : _a.reset(); } /** * Get current eviction policy */ getEvictionPolicy() { return this.evictionPolicy; } /** * Get estimated memory usage */ getMemoryUsage() { return utils_1.CacheUtils.calculateMemoryUsage(this.store); } /** * Evict an entry based on the configured eviction policy */ evictEntry() { var _a; const keyToEvict = utils_1.CacheUtils.getKeyToEvict(this.store, this.evictionPolicy); if (keyToEvict) { this.store.delete(keyToEvict); (_a = this.statsTracker) === null || _a === void 0 ? void 0 : _a.recordEviction(); } } /** * Update internal statistics */ updateStats() { if (this.statsTracker) { this.statsTracker.updateSize(this.store.size); } } } exports.RuntimeMemoryCache = RuntimeMemoryCache; exports.default = RuntimeMemoryCache;