UNPKG

@thi.ng/cache

Version:

In-memory cache implementations with ES6 Map-like API and different eviction strategies

116 lines (115 loc) 3.02 kB
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 };