@thi.ng/cache
Version:
In-memory cache implementations with ES6 Map-like API and different eviction strategies
116 lines (115 loc) • 3.02 kB
JavaScript
import { LRUCache } from "./lru.js";
class TLRUCache extends LRUCache {
constructor(pairs, opts) {
super(pairs, { ttl: 60 * 60 * 1e3, autoExtend: false, ...opts });
}
empty() {
return new TLRUCache(null, this.opts);
}
has(key) {
return this.get(key) !== void 0;
}
/**
* Attempts to retrieve & return cached value for `key`. If found, also
* resets the cache entry's configured TTL. If cache miss, returns
* `notFound`.
*
* @param key
* @param notFound
*/
get(key, notFound) {
const e = this.map.get(key);
if (e) {
if (e.value.t >= Date.now()) {
return this.resetEntry(e);
}
this.removeEntry(e);
}
return notFound;
}
/**
* Stores given `value` under `key` in the cache, optionally with custom
* `ttl` (in milliseconds). Returns `value`.
*
* @remarks
* Also see {@link TLRUCache.getSet} for alternative, and
* {@link CacheOpts.update} for user callback when updating an existing
* `key` in the cache.
*
* @param key
* @param value
* @param ttl
*/
set(key, value, ttl = this.opts.ttl) {
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, ttl);
} else {
this._size -= additionalSize;
}
return value;
}
async getSet(key, retrieve, ttl = this.opts.ttl) {
const e = this.get(key);
return e !== void 0 ? e : this.set(key, await retrieve(), ttl);
}
/**
* Scans all cached entries and evicts any which are expired by now (based
* on their TTL). Does **not** modify last-accessed time of remaining
* entries. Returns number of entries evicted.
*
* @remarks
* For very large caches, it's recommended to call this function in a
* cron-like manner...
*/
prune() {
const now = Date.now();
let cell = this.items.head;
let count = 0;
while (cell) {
if (cell.value.t < now) {
this.removeEntry(cell);
count++;
}
cell = cell.next;
}
return count;
}
ensureSize() {
const { maxlen, maxsize } = this.opts;
const now = Date.now();
let cell = this.items.head;
while (cell && (this._size > maxsize || this.length >= maxlen)) {
if (cell.value.t < now) {
this.removeEntry(cell);
}
cell = cell.next;
}
return super.ensureSize();
}
doSetEntry(e, k, v, s, ttl = this.opts.ttl) {
const t = Date.now() + ttl;
if (e) {
this.opts.update?.(k, e.value.v, v);
e.value.v = v;
e.value.s = s;
e.value.t = t;
this.items.asTail(e);
} else {
this.items.push({ k, v, s, t, ttl });
this.map.set(k, this.items.tail);
}
}
resetEntry(e) {
if (this.opts.autoExtend) {
e.value.t = Date.now() + e.value.ttl;
}
return super.resetEntry(e);
}
}
export {
TLRUCache
};