memoru
Version:
A hash-based LRU cache that evicts entries based on memory usage rather than time or item count.
142 lines (141 loc) • 4.46 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Memoru = void 0;
const memory_stats_1 = require("./memory-stats");
/**
* Memoru is a high-performance LRU cache using a two-map hashlru algorithm.
* Supports any key type and can be rotated by size or memory threshold.
* @public
*/
class Memoru {
/**
* Create a new Memoru instance.
* @param options - Configuration options for the cache
* @public
*/
constructor(options) {
if (options.max !== undefined &&
(typeof options.max !== 'number' || options.max <= 0)) {
throw new Error('Memoru max value, if provided, must be a number greater than 0');
}
this.max = options.max;
this.size = 0;
this.cache = new Map();
this._cache = new Map();
if (options.memoryStats) {
// If respectGC is enabled, ensure monitorGC is also enabled
if (options.respectGC && !options.memoryStats.monitorGC) {
options.memoryStats.monitorGC = true;
}
this.memoryMonitor = new memory_stats_1.MemoryStatsMonitor(options.memoryStats);
this.memoryMonitor.on('threshold', (event) => {
// Skip rotation if GC is active and respectGC option is enabled
if (options.respectGC && event.gcActive) {
return;
}
this.rotate();
});
// Listen for GC events if enabled
if (options.respectGC) {
this.memoryMonitor.on('gc:start', () => {
// Do nothing when GC starts - threshold events will be skipped
});
this.memoryMonitor.on('gc:end', () => {
// Optional: could trigger a rotation here after GC completes
// if that becomes a desirable feature
});
}
}
}
/**
* Rotate the cache, moving current cache to shadow cache and clearing the main cache.
* Called internally on size or memory threshold.
* @internal
*/
rotate() {
var _a;
// Don't rotate if GC is in progress and we're configured to respect it
if ((_a = this.memoryMonitor) === null || _a === void 0 ? void 0 : _a.isGCActive()) {
return;
}
this.size = 0;
this._cache = this.cache;
this.cache = new Map();
}
/**
* Internal update method for inserting a new item and handling rotation.
* @param key - The key to insert
* @param value - The value to insert
* @internal
*/
update(key, value) {
var _a;
this.cache.set(key, value);
this.size++;
// Check if size-based rotation is needed and no GC is in progress
if (this.max !== undefined &&
this.size >= this.max &&
!((_a = this.memoryMonitor) === null || _a === void 0 ? void 0 : _a.isGCActive())) {
this.rotate();
}
}
/**
* Check if the cache contains a key.
* @param key - The key to check
* @public
*/
has(key) {
return this.cache.has(key) || this._cache.has(key);
}
/**
* Remove a key from the cache and shadow cache.
* @param key - The key to remove
* @public
*/
remove(key) {
this.cache.delete(key);
this._cache.delete(key);
}
/**
* Get a value from the cache. If found in the shadow cache, it is promoted to the main cache.
* @param key - The key to retrieve
* @public
*/
get(key) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
if (this._cache.has(key)) {
const v = this._cache.get(key);
if (v !== undefined) {
this.update(key, v);
return v;
}
}
return undefined;
}
/**
* Set a value in the cache.
* @param key - The key to set
* @param value - The value to set
* @public
*/
set(key, value) {
if (this.cache.has(key)) {
this.cache.set(key, value);
}
else {
this.update(key, value);
}
}
/**
* Clear the cache and shadow cache.
* @public
*/
clear() {
this.cache = new Map();
this._cache = new Map();
this.size = 0;
}
}
exports.Memoru = Memoru;