@thi.ng/cache
Version:
In-memory cache implementations with ES6 Map-like API and different eviction strategies
142 lines (141 loc) • 3.01 kB
JavaScript
import { DCons } from "@thi.ng/dcons/dcons";
class LRUCache {
map;
items;
opts;
_size;
constructor(pairs, opts) {
const _opts = {
maxlen: Infinity,
maxsize: Infinity,
map: () => /* @__PURE__ */ new Map(),
ksize: () => 0,
vsize: () => 0,
...opts
};
this.map = _opts.map();
this.items = new DCons();
this._size = 0;
this.opts = _opts;
if (pairs) {
this.into(pairs);
}
}
get length() {
return this.items.length;
}
get size() {
return this._size;
}
[Symbol.iterator]() {
return this.entries();
}
*entries() {
for (let e of this.items) yield [e.k, e];
}
*keys() {
for (let e of this.items) yield e.k;
}
*values() {
for (let e of this.items) yield e.v;
}
copy() {
const c = this.empty();
c.items = this.items.copy();
let cell = c.items.head;
while (cell) {
c.map.set(cell.value.k, cell);
cell = cell.next;
}
return c;
}
empty() {
return new LRUCache(null, this.opts);
}
release() {
this._size = 0;
this.map.clear();
const release = this.opts.release;
if (release) {
let e;
while (e = this.items.drop()) {
release(e.k, e.v);
}
return true;
}
return this.items.release();
}
has(key) {
return this.map.has(key);
}
get(key, notFound) {
const e = this.map.get(key);
return e ? this.resetEntry(e) : notFound;
}
set(key, value) {
const size = this.opts.ksize(key) + this.opts.vsize(value);
const e = this.map.get(key);
const additionalSize = Math.max(0, size - (e ? e.value.s : 0));
this._size += additionalSize;
if (this.ensureSize()) {
this.doSetEntry(e, key, value, size);
} else {
this._size -= additionalSize;
}
return value;
}
into(pairs) {
for (let p of pairs) {
this.set(p[0], p[1]);
}
return this;
}
async getSet(key, retrieve) {
const e = this.map.get(key);
return e ? this.resetEntry(e) : this.set(key, await retrieve());
}
delete(key) {
const e = this.map.get(key);
if (e) {
this.removeEntry(e);
return true;
}
return false;
}
resetEntry(e) {
this.items.asTail(e);
return e.value.v;
}
ensureSize() {
const { release, maxsize, maxlen } = this.opts;
while (this._size > maxsize || this.length >= maxlen) {
const e = this.items.drop();
if (!e) return false;
this.map.delete(e.k);
release?.(e.k, e.v);
this._size -= e.s;
}
return true;
}
removeEntry(e) {
const ee = e.value;
this.map.delete(ee.k);
this.items.remove(e);
this.opts.release?.(ee.k, ee.v);
this._size -= ee.s;
}
doSetEntry(e, k, v, s) {
if (e) {
this.opts.update?.(k, e.value.v, v);
e.value.v = v;
e.value.s = s;
this.items.asTail(e);
} else {
this.items.push({ k, v, s });
this.map.set(k, this.items.tail);
}
}
}
export {
LRUCache
};