/**
* @file generational-cache.js
* A generational pseudo-LRU cache with strict maximum size limits.
*/
/**
* @template K, V
*/
export class GenerationalCache {
#max;
#boundary;
#current = new Map();
#old = new Map();
/**
* Initializes a new instance of the GenerationalCache class.
* @param {number} max - The maximum number of items the cache can hold.
*/
constructor(max) {
this.max = max;
}
/**
* Returns the total number of `entries` currently in the cache.
* @note To optimize for write speed, this library allows temporary key
* duplication between generations. Therefore, this value may not always
* reflect the exact count of unique `keys`.
* @returns {number} The total entry count.
*/
get size() {
return this.#current.size + this.#old.size;
}
/**
* Returns the maximum capacity of the cache.
* @returns {number} The maximum size limit.
*/
get max() {
return this.#max;
}
/**
* Sets the maximum capacity of the cache and recalculates the boundary.
* Clears the cache when updated.
* @param {number} value - The new maximum capacity to set.
*/
set max(value) {
if (Number.isFinite(value) && value > 4) {
this.#max = value;
this.#boundary = Math.ceil(value / 2);
} else {
this.#max = 4;
this.#boundary = 2;
}
this.clear();
}
/**
* Retrieves an item from the cache.
* If the item is in the older generation, it gets promoted to the current
* generation.
* @param {K} key - The key of the element to return.
* @returns {V | undefined} The element associated with the specified key, or
* undefined if the key cannot be found.
*/
get(key) {
let value = this.#current.get(key);
if (value !== undefined) {
return value;
}
value = this.#old.get(key);
if (value !== undefined) {
this.set(key, value);
return value;
}
return undefined;
}
/**
* Adds or updates an element with a specified key and a value to the cache.
* @param {K} key - The key of the element to add.
* @param {V} value - The value of the element to add.
* @returns {GenerationalCache} The cache object itself.
*/
set(key, value) {
this.#current.set(key, value);
// Swap generations if the current map reaches the boundary
if (this.#current.size >= this.#boundary) {
this.#old = this.#current;
this.#current = new Map();
}
return this;
}
/**
* Returns a boolean indicating whether an element with the specified key
* exists or not.
* @param {K} key - The key of the element to test for presence.
* @returns {boolean} true if an element with the specified key exists in the
* cache; otherwise false.
*/
has(key) {
return this.#current.has(key) || this.#old.has(key);
}
/**
* Removes the specified element from the cache.
* @param {K} key - The key of the element to remove.
* @returns {boolean} true if an element in the cache existed and has been
* removed, or false if the element does not exist.
*/
delete(key) {
const deletedFromCurrent = this.#current.delete(key);
const deletedFromOld = this.#old.delete(key);
return deletedFromCurrent || deletedFromOld;
}
/**
* Removes all elements from the cache.
*/
clear() {
this.#current.clear();
this.#old.clear();
}
}