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.

196 lines (195 loc) 6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ValidationUtils = exports.CacheUtils = void 0; /** * Utility class for cache-related operations */ class CacheUtils { /** * Check if a cache entry is expired */ static isExpired(entry) { return entry.expiresAt !== undefined && entry.expiresAt < Date.now(); } /** * Calculate expiration timestamp */ static calculateExpiresAt(ttl, defaultTtl) { if (ttl !== undefined) { return Date.now() + ttl; } if (defaultTtl !== undefined) { return Date.now() + defaultTtl; } return undefined; } /** * Create a new cache entry */ static createEntry(value, expiresAt) { const now = Date.now(); return { value, expiresAt, createdAt: now, lastAccessedAt: now }; } /** * Update access time for LRU tracking */ static updateAccessTime(entry) { entry.lastAccessedAt = Date.now(); } /** * Get the key to evict based on eviction policy */ static getKeyToEvict(store, evictionPolicy) { if (store.size === 0) return undefined; if (evictionPolicy === 'FIFO') { // Return the first key (oldest inserted) return store.keys().next().value; } else { // LRU: Find the least recently used entry let lruKey = undefined; let oldestAccessTime = Number.MAX_SAFE_INTEGER; for (const [key, entry] of store.entries()) { if (entry.lastAccessedAt < oldestAccessTime) { oldestAccessTime = entry.lastAccessedAt; lruKey = key; } } return lruKey; } } /** * Get the first key from a Map (for FIFO eviction) * @deprecated Use getKeyToEvict instead */ static getFirstKey(map) { const iterator = map.keys().next(); return iterator.done ? undefined : iterator.value; } /** * Remove expired entries from the cache */ static cleanupExpired(store) { let removedCount = 0; for (const [key, entry] of store.entries()) { if (CacheUtils.isExpired(entry)) { store.delete(key); removedCount++; } } return removedCount; } /** * Estimate memory usage of a cache entry */ static estimateEntrySize(key, entry) { const keySize = new Blob([key]).size; const valueSize = CacheUtils.estimateValueSize(entry.value); const metadataSize = 24; // Approximate size of timestamps and metadata return keySize + valueSize + metadataSize; } /** * Estimate memory size of a value */ static estimateValueSize(value) { if (value === null || value === undefined) return 0; const type = typeof value; switch (type) { case 'boolean': return 4; case 'number': return 8; case 'string': return new Blob([value]).size; case 'object': if (Array.isArray(value)) { return value.reduce((acc, item) => acc + CacheUtils.estimateValueSize(item), 0); } return new Blob([JSON.stringify(value)]).size; default: return new Blob([String(value)]).size; } } /** * Calculate total memory usage of cache */ static calculateMemoryUsage(store) { let totalBytes = 0; let entryCount = 0; for (const [key, entry] of store.entries()) { totalBytes += CacheUtils.estimateEntrySize(String(key), entry); entryCount++; } return { estimatedBytes: totalBytes, averageBytesPerEntry: entryCount > 0 ? Math.round(totalBytes / entryCount) : 0 }; } } exports.CacheUtils = CacheUtils; /** * Validation utilities for cache operations */ class ValidationUtils { /** * Validate cache key */ static validateKey(key) { if (typeof key !== 'string') { throw new TypeError('Cache key must be a string'); } if (key.length === 0) { throw new Error('Cache key cannot be empty'); } if (key.length > 250) { throw new Error('Cache key too long (maximum 250 characters)'); } } /** * Validate TTL value */ static validateTTL(ttl) { if (typeof ttl !== 'number' || Number.isNaN(ttl)) { throw new TypeError('TTL must be a number'); } if (!isFinite(ttl)) { throw new Error('TTL must be a finite number'); } if (ttl < 0) { throw new Error('TTL cannot be negative'); } } /** * Validate cache options */ static validateCacheOptions(options) { if (options.ttl !== undefined) { ValidationUtils.validateTTL(options.ttl); } if (options.maxSize !== undefined) { if (typeof options.maxSize !== 'number' || Number.isNaN(options.maxSize)) { throw new TypeError('maxSize must be a number'); } if (options.maxSize < 1) { throw new Error('maxSize must be at least 1'); } if (!Number.isInteger(options.maxSize)) { throw new Error('maxSize must be an integer'); } } if (options.evictionPolicy !== undefined) { const validPolicies = ['FIFO', 'LRU']; if (!validPolicies.includes(options.evictionPolicy)) { throw new Error(`evictionPolicy must be one of: ${validPolicies.join(', ')}`); } } } } exports.ValidationUtils = ValidationUtils;