208 lines
6.9 KiB
JavaScript
208 lines
6.9 KiB
JavaScript
const createLRU = (options) => {
|
|
let { max } = options;
|
|
if (!(Number.isInteger(max) && max > 0))
|
|
throw new TypeError("`max` must be a positive integer");
|
|
let size = 0;
|
|
let head = 0;
|
|
let tail = 0;
|
|
let free = [];
|
|
const { onEviction } = options;
|
|
const keyMap = /* @__PURE__ */ new Map();
|
|
const keyList = new Array(max).fill(void 0);
|
|
const valList = new Array(max).fill(void 0);
|
|
const next = new Array(max).fill(0);
|
|
const prev = new Array(max).fill(0);
|
|
const setTail = (index, type) => {
|
|
if (index === tail) return;
|
|
const nextIndex = next[index];
|
|
const prevIndex = prev[index];
|
|
if (index === head) head = nextIndex;
|
|
else if (type === "get" || prevIndex !== 0) next[prevIndex] = nextIndex;
|
|
if (nextIndex !== 0) prev[nextIndex] = prevIndex;
|
|
next[tail] = index;
|
|
prev[index] = tail;
|
|
next[index] = 0;
|
|
tail = index;
|
|
};
|
|
const _evict = () => {
|
|
const evictHead = head;
|
|
const key = keyList[evictHead];
|
|
onEviction == null ? void 0 : onEviction(key, valList[evictHead]);
|
|
keyMap.delete(key);
|
|
keyList[evictHead] = void 0;
|
|
valList[evictHead] = void 0;
|
|
head = next[evictHead];
|
|
if (head !== 0) prev[head] = 0;
|
|
size--;
|
|
if (size === 0) head = tail = 0;
|
|
free.push(evictHead);
|
|
return evictHead;
|
|
};
|
|
return {
|
|
/** Adds a key-value pair to the cache. Updates the value if the key already exists. */
|
|
set(key, value) {
|
|
if (key === void 0) return;
|
|
let index = keyMap.get(key);
|
|
if (index === void 0) {
|
|
index = size === max ? _evict() : free.length > 0 ? free.pop() : size;
|
|
keyMap.set(key, index);
|
|
keyList[index] = key;
|
|
size++;
|
|
} else onEviction == null ? void 0 : onEviction(key, valList[index]);
|
|
valList[index] = value;
|
|
if (size === 1) head = tail = index;
|
|
else setTail(index, "set");
|
|
},
|
|
/** Retrieves the value for a given key and moves the key to the most recent position. */
|
|
get(key) {
|
|
const index = keyMap.get(key);
|
|
if (index === void 0) return;
|
|
if (index !== tail) setTail(index, "get");
|
|
return valList[index];
|
|
},
|
|
/** Retrieves the value for a given key without changing its position. */
|
|
peek: (key) => {
|
|
const index = keyMap.get(key);
|
|
return index !== void 0 ? valList[index] : void 0;
|
|
},
|
|
/** Checks if a key exists in the cache. */
|
|
has: (key) => keyMap.has(key),
|
|
/** Iterates over all keys in the cache, from most recent to least recent. */
|
|
*keys() {
|
|
let current = tail;
|
|
for (let i = 0; i < size; i++) {
|
|
yield keyList[current];
|
|
current = prev[current];
|
|
}
|
|
},
|
|
/** Iterates over all values in the cache, from most recent to least recent. */
|
|
*values() {
|
|
let current = tail;
|
|
for (let i = 0; i < size; i++) {
|
|
yield valList[current];
|
|
current = prev[current];
|
|
}
|
|
},
|
|
/** Iterates over `[key, value]` pairs in the cache, from most recent to least recent. */
|
|
*entries() {
|
|
let current = tail;
|
|
for (let i = 0; i < size; i++) {
|
|
yield [keyList[current], valList[current]];
|
|
current = prev[current];
|
|
}
|
|
},
|
|
/** Iterates over each value-key pair in the cache, from most recent to least recent. */
|
|
forEach: (callback) => {
|
|
let current = tail;
|
|
for (let i = 0; i < size; i++) {
|
|
const key = keyList[current];
|
|
const value = valList[current];
|
|
callback(value, key);
|
|
current = prev[current];
|
|
}
|
|
},
|
|
/** Deletes a key-value pair from the cache. */
|
|
delete(key) {
|
|
const index = keyMap.get(key);
|
|
if (index === void 0) return false;
|
|
onEviction == null ? void 0 : onEviction(key, valList[index]);
|
|
keyMap.delete(key);
|
|
free.push(index);
|
|
keyList[index] = void 0;
|
|
valList[index] = void 0;
|
|
const prevIndex = prev[index];
|
|
const nextIndex = next[index];
|
|
if (prevIndex !== 0) next[prevIndex] = nextIndex;
|
|
if (nextIndex !== 0) prev[nextIndex] = prevIndex;
|
|
if (index === head) head = nextIndex;
|
|
if (index === tail) tail = prevIndex;
|
|
size--;
|
|
return true;
|
|
},
|
|
/** Evicts the oldest item or the specified number of the oldest items from the cache. */
|
|
evict: (number) => {
|
|
let toPrune = Math.min(number, size);
|
|
while (toPrune > 0) {
|
|
_evict();
|
|
toPrune--;
|
|
}
|
|
},
|
|
/** Clears all key-value pairs from the cache. */
|
|
clear() {
|
|
if (typeof onEviction === "function") {
|
|
const iterator = keyMap.values();
|
|
for (let result = iterator.next(); !result.done; result = iterator.next())
|
|
onEviction(keyList[result.value], valList[result.value]);
|
|
}
|
|
keyMap.clear();
|
|
keyList.fill(void 0);
|
|
valList.fill(void 0);
|
|
free = [];
|
|
size = 0;
|
|
head = tail = 0;
|
|
},
|
|
/** Resizes the cache to a new maximum size, evicting items if necessary. */
|
|
resize: (newMax) => {
|
|
if (!(Number.isInteger(newMax) && newMax > 0))
|
|
throw new TypeError("`max` must be a positive integer");
|
|
if (newMax === max) return;
|
|
if (newMax < max) {
|
|
let current = tail;
|
|
const preserve = Math.min(size, newMax);
|
|
const remove = size - preserve;
|
|
const newKeyList = new Array(newMax);
|
|
const newValList = new Array(newMax);
|
|
const newNext = new Array(newMax);
|
|
const newPrev = new Array(newMax);
|
|
for (let i = 1; i <= remove; i++)
|
|
onEviction == null ? void 0 : onEviction(keyList[i], valList[i]);
|
|
for (let i = preserve - 1; i >= 0; i--) {
|
|
newKeyList[i] = keyList[current];
|
|
newValList[i] = valList[current];
|
|
newNext[i] = i + 1;
|
|
newPrev[i] = i - 1;
|
|
keyMap.set(newKeyList[i], i);
|
|
current = prev[current];
|
|
}
|
|
head = 0;
|
|
tail = preserve - 1;
|
|
size = preserve;
|
|
keyList.length = newMax;
|
|
valList.length = newMax;
|
|
next.length = newMax;
|
|
prev.length = newMax;
|
|
for (let i = 0; i < preserve; i++) {
|
|
keyList[i] = newKeyList[i];
|
|
valList[i] = newValList[i];
|
|
next[i] = newNext[i];
|
|
prev[i] = newPrev[i];
|
|
}
|
|
free = [];
|
|
for (let i = preserve; i < newMax; i++) free.push(i);
|
|
} else {
|
|
const fill = newMax - max;
|
|
keyList.push(...new Array(fill).fill(void 0));
|
|
valList.push(...new Array(fill).fill(void 0));
|
|
next.push(...new Array(fill).fill(0));
|
|
prev.push(...new Array(fill).fill(0));
|
|
}
|
|
max = newMax;
|
|
},
|
|
/** Returns the maximum number of items that can be stored in the cache. */
|
|
get max() {
|
|
return max;
|
|
},
|
|
/** Returns the number of items currently stored in the cache. */
|
|
get size() {
|
|
return size;
|
|
},
|
|
/** Returns the number of currently available slots in the cache before reaching the maximum size. */
|
|
get available() {
|
|
return max - size;
|
|
}
|
|
};
|
|
};
|
|
export {
|
|
createLRU
|
|
};
|