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
JavaScript
"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;