123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542 |
- /**
- * @module LRUCache
- */
- const perf = typeof performance === 'object' &&
- performance &&
- typeof performance.now === 'function'
- ? performance
- : Date;
- const warned = new Set();
- /* c8 ignore start */
- const PROCESS = (typeof process === 'object' && !!process ? process : {});
- /* c8 ignore start */
- const emitWarning = (msg, type, code, fn) => {
- typeof PROCESS.emitWarning === 'function'
- ? PROCESS.emitWarning(msg, type, code, fn)
- : console.error(`[${code}] ${type}: ${msg}`);
- };
- let AC = globalThis.AbortController;
- let AS = globalThis.AbortSignal;
- /* c8 ignore start */
- if (typeof AC === 'undefined') {
- //@ts-ignore
- AS = class AbortSignal {
- onabort;
- _onabort = [];
- reason;
- aborted = false;
- addEventListener(_, fn) {
- this._onabort.push(fn);
- }
- };
- //@ts-ignore
- AC = class AbortController {
- constructor() {
- warnACPolyfill();
- }
- signal = new AS();
- abort(reason) {
- if (this.signal.aborted)
- return;
- //@ts-ignore
- this.signal.reason = reason;
- //@ts-ignore
- this.signal.aborted = true;
- //@ts-ignore
- for (const fn of this.signal._onabort) {
- fn(reason);
- }
- this.signal.onabort?.(reason);
- }
- };
- let printACPolyfillWarning = PROCESS.env?.LRU_CACHE_IGNORE_AC_WARNING !== '1';
- const warnACPolyfill = () => {
- if (!printACPolyfillWarning)
- return;
- printACPolyfillWarning = false;
- emitWarning('AbortController is not defined. If using lru-cache in ' +
- 'node 14, load an AbortController polyfill from the ' +
- '`node-abort-controller` package. A minimal polyfill is ' +
- 'provided for use by LRUCache.fetch(), but it should not be ' +
- 'relied upon in other contexts (eg, passing it to other APIs that ' +
- 'use AbortController/AbortSignal might have undesirable effects). ' +
- 'You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.', 'NO_ABORT_CONTROLLER', 'ENOTSUP', warnACPolyfill);
- };
- }
- /* c8 ignore stop */
- const shouldWarn = (code) => !warned.has(code);
- const TYPE = Symbol('type');
- const isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n);
- /* c8 ignore start */
- // This is a little bit ridiculous, tbh.
- // The maximum array length is 2^32-1 or thereabouts on most JS impls.
- // And well before that point, you're caching the entire world, I mean,
- // that's ~32GB of just integers for the next/prev links, plus whatever
- // else to hold that many keys and values. Just filling the memory with
- // zeroes at init time is brutal when you get that big.
- // But why not be complete?
- // Maybe in the future, these limits will have expanded.
- const getUintArray = (max) => !isPosInt(max)
- ? null
- : max <= Math.pow(2, 8)
- ? Uint8Array
- : max <= Math.pow(2, 16)
- ? Uint16Array
- : max <= Math.pow(2, 32)
- ? Uint32Array
- : max <= Number.MAX_SAFE_INTEGER
- ? ZeroArray
- : null;
- /* c8 ignore stop */
- class ZeroArray extends Array {
- constructor(size) {
- super(size);
- this.fill(0);
- }
- }
- class Stack {
- heap;
- length;
- // private constructor
- static #constructing = false;
- static create(max) {
- const HeapCls = getUintArray(max);
- if (!HeapCls)
- return [];
- Stack.#constructing = true;
- const s = new Stack(max, HeapCls);
- Stack.#constructing = false;
- return s;
- }
- constructor(max, HeapCls) {
- /* c8 ignore start */
- if (!Stack.#constructing) {
- throw new TypeError('instantiate Stack using Stack.create(n)');
- }
- /* c8 ignore stop */
- this.heap = new HeapCls(max);
- this.length = 0;
- }
- push(n) {
- this.heap[this.length++] = n;
- }
- pop() {
- return this.heap[--this.length];
- }
- }
- /**
- * Default export, the thing you're using this module to get.
- *
- * The `K` and `V` types define the key and value types, respectively. The
- * optional `FC` type defines the type of the `context` object passed to
- * `cache.fetch()` and `cache.memo()`.
- *
- * Keys and values **must not** be `null` or `undefined`.
- *
- * All properties from the options object (with the exception of `max`,
- * `maxSize`, `fetchMethod`, `memoMethod`, `dispose` and `disposeAfter`) are
- * added as normal public members. (The listed options are read-only getters.)
- *
- * Changing any of these will alter the defaults for subsequent method calls.
- */
- export class LRUCache {
- // options that cannot be changed without disaster
- #max;
- #maxSize;
- #dispose;
- #disposeAfter;
- #fetchMethod;
- #memoMethod;
- /**
- * {@link LRUCache.OptionsBase.ttl}
- */
- ttl;
- /**
- * {@link LRUCache.OptionsBase.ttlResolution}
- */
- ttlResolution;
- /**
- * {@link LRUCache.OptionsBase.ttlAutopurge}
- */
- ttlAutopurge;
- /**
- * {@link LRUCache.OptionsBase.updateAgeOnGet}
- */
- updateAgeOnGet;
- /**
- * {@link LRUCache.OptionsBase.updateAgeOnHas}
- */
- updateAgeOnHas;
- /**
- * {@link LRUCache.OptionsBase.allowStale}
- */
- allowStale;
- /**
- * {@link LRUCache.OptionsBase.noDisposeOnSet}
- */
- noDisposeOnSet;
- /**
- * {@link LRUCache.OptionsBase.noUpdateTTL}
- */
- noUpdateTTL;
- /**
- * {@link LRUCache.OptionsBase.maxEntrySize}
- */
- maxEntrySize;
- /**
- * {@link LRUCache.OptionsBase.sizeCalculation}
- */
- sizeCalculation;
- /**
- * {@link LRUCache.OptionsBase.noDeleteOnFetchRejection}
- */
- noDeleteOnFetchRejection;
- /**
- * {@link LRUCache.OptionsBase.noDeleteOnStaleGet}
- */
- noDeleteOnStaleGet;
- /**
- * {@link LRUCache.OptionsBase.allowStaleOnFetchAbort}
- */
- allowStaleOnFetchAbort;
- /**
- * {@link LRUCache.OptionsBase.allowStaleOnFetchRejection}
- */
- allowStaleOnFetchRejection;
- /**
- * {@link LRUCache.OptionsBase.ignoreFetchAbort}
- */
- ignoreFetchAbort;
- // computed properties
- #size;
- #calculatedSize;
- #keyMap;
- #keyList;
- #valList;
- #next;
- #prev;
- #head;
- #tail;
- #free;
- #disposed;
- #sizes;
- #starts;
- #ttls;
- #hasDispose;
- #hasFetchMethod;
- #hasDisposeAfter;
- /**
- * Do not call this method unless you need to inspect the
- * inner workings of the cache. If anything returned by this
- * object is modified in any way, strange breakage may occur.
- *
- * These fields are private for a reason!
- *
- * @internal
- */
- static unsafeExposeInternals(c) {
- return {
- // properties
- starts: c.#starts,
- ttls: c.#ttls,
- sizes: c.#sizes,
- keyMap: c.#keyMap,
- keyList: c.#keyList,
- valList: c.#valList,
- next: c.#next,
- prev: c.#prev,
- get head() {
- return c.#head;
- },
- get tail() {
- return c.#tail;
- },
- free: c.#free,
- // methods
- isBackgroundFetch: (p) => c.#isBackgroundFetch(p),
- backgroundFetch: (k, index, options, context) => c.#backgroundFetch(k, index, options, context),
- moveToTail: (index) => c.#moveToTail(index),
- indexes: (options) => c.#indexes(options),
- rindexes: (options) => c.#rindexes(options),
- isStale: (index) => c.#isStale(index),
- };
- }
- // Protected read-only members
- /**
- * {@link LRUCache.OptionsBase.max} (read-only)
- */
- get max() {
- return this.#max;
- }
- /**
- * {@link LRUCache.OptionsBase.maxSize} (read-only)
- */
- get maxSize() {
- return this.#maxSize;
- }
- /**
- * The total computed size of items in the cache (read-only)
- */
- get calculatedSize() {
- return this.#calculatedSize;
- }
- /**
- * The number of items stored in the cache (read-only)
- */
- get size() {
- return this.#size;
- }
- /**
- * {@link LRUCache.OptionsBase.fetchMethod} (read-only)
- */
- get fetchMethod() {
- return this.#fetchMethod;
- }
- get memoMethod() {
- return this.#memoMethod;
- }
- /**
- * {@link LRUCache.OptionsBase.dispose} (read-only)
- */
- get dispose() {
- return this.#dispose;
- }
- /**
- * {@link LRUCache.OptionsBase.disposeAfter} (read-only)
- */
- get disposeAfter() {
- return this.#disposeAfter;
- }
- constructor(options) {
- const { max = 0, ttl, ttlResolution = 1, ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0, maxEntrySize = 0, sizeCalculation, fetchMethod, memoMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, } = options;
- if (max !== 0 && !isPosInt(max)) {
- throw new TypeError('max option must be a nonnegative integer');
- }
- const UintArray = max ? getUintArray(max) : Array;
- if (!UintArray) {
- throw new Error('invalid max value: ' + max);
- }
- this.#max = max;
- this.#maxSize = maxSize;
- this.maxEntrySize = maxEntrySize || this.#maxSize;
- this.sizeCalculation = sizeCalculation;
- if (this.sizeCalculation) {
- if (!this.#maxSize && !this.maxEntrySize) {
- throw new TypeError('cannot set sizeCalculation without setting maxSize or maxEntrySize');
- }
- if (typeof this.sizeCalculation !== 'function') {
- throw new TypeError('sizeCalculation set to non-function');
- }
- }
- if (memoMethod !== undefined &&
- typeof memoMethod !== 'function') {
- throw new TypeError('memoMethod must be a function if defined');
- }
- this.#memoMethod = memoMethod;
- if (fetchMethod !== undefined &&
- typeof fetchMethod !== 'function') {
- throw new TypeError('fetchMethod must be a function if specified');
- }
- this.#fetchMethod = fetchMethod;
- this.#hasFetchMethod = !!fetchMethod;
- this.#keyMap = new Map();
- this.#keyList = new Array(max).fill(undefined);
- this.#valList = new Array(max).fill(undefined);
- this.#next = new UintArray(max);
- this.#prev = new UintArray(max);
- this.#head = 0;
- this.#tail = 0;
- this.#free = Stack.create(max);
- this.#size = 0;
- this.#calculatedSize = 0;
- if (typeof dispose === 'function') {
- this.#dispose = dispose;
- }
- if (typeof disposeAfter === 'function') {
- this.#disposeAfter = disposeAfter;
- this.#disposed = [];
- }
- else {
- this.#disposeAfter = undefined;
- this.#disposed = undefined;
- }
- this.#hasDispose = !!this.#dispose;
- this.#hasDisposeAfter = !!this.#disposeAfter;
- this.noDisposeOnSet = !!noDisposeOnSet;
- this.noUpdateTTL = !!noUpdateTTL;
- this.noDeleteOnFetchRejection = !!noDeleteOnFetchRejection;
- this.allowStaleOnFetchRejection = !!allowStaleOnFetchRejection;
- this.allowStaleOnFetchAbort = !!allowStaleOnFetchAbort;
- this.ignoreFetchAbort = !!ignoreFetchAbort;
- // NB: maxEntrySize is set to maxSize if it's set
- if (this.maxEntrySize !== 0) {
- if (this.#maxSize !== 0) {
- if (!isPosInt(this.#maxSize)) {
- throw new TypeError('maxSize must be a positive integer if specified');
- }
- }
- if (!isPosInt(this.maxEntrySize)) {
- throw new TypeError('maxEntrySize must be a positive integer if specified');
- }
- this.#initializeSizeTracking();
- }
- this.allowStale = !!allowStale;
- this.noDeleteOnStaleGet = !!noDeleteOnStaleGet;
- this.updateAgeOnGet = !!updateAgeOnGet;
- this.updateAgeOnHas = !!updateAgeOnHas;
- this.ttlResolution =
- isPosInt(ttlResolution) || ttlResolution === 0
- ? ttlResolution
- : 1;
- this.ttlAutopurge = !!ttlAutopurge;
- this.ttl = ttl || 0;
- if (this.ttl) {
- if (!isPosInt(this.ttl)) {
- throw new TypeError('ttl must be a positive integer if specified');
- }
- this.#initializeTTLTracking();
- }
- // do not allow completely unbounded caches
- if (this.#max === 0 && this.ttl === 0 && this.#maxSize === 0) {
- throw new TypeError('At least one of max, maxSize, or ttl is required');
- }
- if (!this.ttlAutopurge && !this.#max && !this.#maxSize) {
- const code = 'LRU_CACHE_UNBOUNDED';
- if (shouldWarn(code)) {
- warned.add(code);
- const msg = 'TTL caching without ttlAutopurge, max, or maxSize can ' +
- 'result in unbounded memory consumption.';
- emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache);
- }
- }
- }
- /**
- * Return the number of ms left in the item's TTL. If item is not in cache,
- * returns `0`. Returns `Infinity` if item is in cache without a defined TTL.
- */
- getRemainingTTL(key) {
- return this.#keyMap.has(key) ? Infinity : 0;
- }
- #initializeTTLTracking() {
- const ttls = new ZeroArray(this.#max);
- const starts = new ZeroArray(this.#max);
- this.#ttls = ttls;
- this.#starts = starts;
- this.#setItemTTL = (index, ttl, start = perf.now()) => {
- starts[index] = ttl !== 0 ? start : 0;
- ttls[index] = ttl;
- if (ttl !== 0 && this.ttlAutopurge) {
- const t = setTimeout(() => {
- if (this.#isStale(index)) {
- this.#delete(this.#keyList[index], 'expire');
- }
- }, ttl + 1);
- // unref() not supported on all platforms
- /* c8 ignore start */
- if (t.unref) {
- t.unref();
- }
- /* c8 ignore stop */
- }
- };
- this.#updateItemAge = index => {
- starts[index] = ttls[index] !== 0 ? perf.now() : 0;
- };
- this.#statusTTL = (status, index) => {
- if (ttls[index]) {
- const ttl = ttls[index];
- const start = starts[index];
- /* c8 ignore next */
- if (!ttl || !start)
- return;
- status.ttl = ttl;
- status.start = start;
- status.now = cachedNow || getNow();
- const age = status.now - start;
- status.remainingTTL = ttl - age;
- }
- };
- // debounce calls to perf.now() to 1s so we're not hitting
- // that costly call repeatedly.
- let cachedNow = 0;
- const getNow = () => {
- const n = perf.now();
- if (this.ttlResolution > 0) {
- cachedNow = n;
- const t = setTimeout(() => (cachedNow = 0), this.ttlResolution);
- // not available on all platforms
- /* c8 ignore start */
- if (t.unref) {
- t.unref();
- }
- /* c8 ignore stop */
- }
- return n;
- };
- this.getRemainingTTL = key => {
- const index = this.#keyMap.get(key);
- if (index === undefined) {
- return 0;
- }
- const ttl = ttls[index];
- const start = starts[index];
- if (!ttl || !start) {
- return Infinity;
- }
- const age = (cachedNow || getNow()) - start;
- return ttl - age;
- };
- this.#isStale = index => {
- const s = starts[index];
- const t = ttls[index];
- return !!t && !!s && (cachedNow || getNow()) - s > t;
- };
- }
- // conditionally set private methods related to TTL
- #updateItemAge = () => { };
- #statusTTL = () => { };
- #setItemTTL = () => { };
- /* c8 ignore stop */
- #isStale = () => false;
- #initializeSizeTracking() {
- const sizes = new ZeroArray(this.#max);
- this.#calculatedSize = 0;
- this.#sizes = sizes;
- this.#removeItemSize = index => {
- this.#calculatedSize -= sizes[index];
- sizes[index] = 0;
- };
- this.#requireSize = (k, v, size, sizeCalculation) => {
- // provisionally accept background fetches.
- // actual value size will be checked when they return.
- if (this.#isBackgroundFetch(v)) {
- return 0;
- }
- if (!isPosInt(size)) {
- if (sizeCalculation) {
- if (typeof sizeCalculation !== 'function') {
- throw new TypeError('sizeCalculation must be a function');
- }
- size = sizeCalculation(v, k);
- if (!isPosInt(size)) {
- throw new TypeError('sizeCalculation return invalid (expect positive integer)');
- }
- }
- else {
- throw new TypeError('invalid size value (must be positive integer). ' +
- 'When maxSize or maxEntrySize is used, sizeCalculation ' +
- 'or size must be set.');
- }
- }
- return size;
- };
- this.#addItemSize = (index, size, status) => {
- sizes[index] = size;
- if (this.#maxSize) {
- const maxSize = this.#maxSize - sizes[index];
- while (this.#calculatedSize > maxSize) {
- this.#evict(true);
- }
- }
- this.#calculatedSize += sizes[index];
- if (status) {
- status.entrySize = size;
- status.totalCalculatedSize = this.#calculatedSize;
- }
- };
- }
- #removeItemSize = _i => { };
- #addItemSize = (_i, _s, _st) => { };
- #requireSize = (_k, _v, size, sizeCalculation) => {
- if (size || sizeCalculation) {
- throw new TypeError('cannot set size without setting maxSize or maxEntrySize on cache');
- }
- return 0;
- };
- *#indexes({ allowStale = this.allowStale } = {}) {
- if (this.#size) {
- for (let i = this.#tail; true;) {
- if (!this.#isValidIndex(i)) {
- break;
- }
- if (allowStale || !this.#isStale(i)) {
- yield i;
- }
- if (i === this.#head) {
- break;
- }
- else {
- i = this.#prev[i];
- }
- }
- }
- }
- *#rindexes({ allowStale = this.allowStale } = {}) {
- if (this.#size) {
- for (let i = this.#head; true;) {
- if (!this.#isValidIndex(i)) {
- break;
- }
- if (allowStale || !this.#isStale(i)) {
- yield i;
- }
- if (i === this.#tail) {
- break;
- }
- else {
- i = this.#next[i];
- }
- }
- }
- }
- #isValidIndex(index) {
- return (index !== undefined &&
- this.#keyMap.get(this.#keyList[index]) === index);
- }
- /**
- * Return a generator yielding `[key, value]` pairs,
- * in order from most recently used to least recently used.
- */
- *entries() {
- for (const i of this.#indexes()) {
- if (this.#valList[i] !== undefined &&
- this.#keyList[i] !== undefined &&
- !this.#isBackgroundFetch(this.#valList[i])) {
- yield [this.#keyList[i], this.#valList[i]];
- }
- }
- }
- /**
- * Inverse order version of {@link LRUCache.entries}
- *
- * Return a generator yielding `[key, value]` pairs,
- * in order from least recently used to most recently used.
- */
- *rentries() {
- for (const i of this.#rindexes()) {
- if (this.#valList[i] !== undefined &&
- this.#keyList[i] !== undefined &&
- !this.#isBackgroundFetch(this.#valList[i])) {
- yield [this.#keyList[i], this.#valList[i]];
- }
- }
- }
- /**
- * Return a generator yielding the keys in the cache,
- * in order from most recently used to least recently used.
- */
- *keys() {
- for (const i of this.#indexes()) {
- const k = this.#keyList[i];
- if (k !== undefined &&
- !this.#isBackgroundFetch(this.#valList[i])) {
- yield k;
- }
- }
- }
- /**
- * Inverse order version of {@link LRUCache.keys}
- *
- * Return a generator yielding the keys in the cache,
- * in order from least recently used to most recently used.
- */
- *rkeys() {
- for (const i of this.#rindexes()) {
- const k = this.#keyList[i];
- if (k !== undefined &&
- !this.#isBackgroundFetch(this.#valList[i])) {
- yield k;
- }
- }
- }
- /**
- * Return a generator yielding the values in the cache,
- * in order from most recently used to least recently used.
- */
- *values() {
- for (const i of this.#indexes()) {
- const v = this.#valList[i];
- if (v !== undefined &&
- !this.#isBackgroundFetch(this.#valList[i])) {
- yield this.#valList[i];
- }
- }
- }
- /**
- * Inverse order version of {@link LRUCache.values}
- *
- * Return a generator yielding the values in the cache,
- * in order from least recently used to most recently used.
- */
- *rvalues() {
- for (const i of this.#rindexes()) {
- const v = this.#valList[i];
- if (v !== undefined &&
- !this.#isBackgroundFetch(this.#valList[i])) {
- yield this.#valList[i];
- }
- }
- }
- /**
- * Iterating over the cache itself yields the same results as
- * {@link LRUCache.entries}
- */
- [Symbol.iterator]() {
- return this.entries();
- }
- /**
- * A String value that is used in the creation of the default string
- * description of an object. Called by the built-in method
- * `Object.prototype.toString`.
- */
- [Symbol.toStringTag] = 'LRUCache';
- /**
- * Find a value for which the supplied fn method returns a truthy value,
- * similar to `Array.find()`. fn is called as `fn(value, key, cache)`.
- */
- find(fn, getOptions = {}) {
- for (const i of this.#indexes()) {
- const v = this.#valList[i];
- const value = this.#isBackgroundFetch(v)
- ? v.__staleWhileFetching
- : v;
- if (value === undefined)
- continue;
- if (fn(value, this.#keyList[i], this)) {
- return this.get(this.#keyList[i], getOptions);
- }
- }
- }
- /**
- * Call the supplied function on each item in the cache, in order from most
- * recently used to least recently used.
- *
- * `fn` is called as `fn(value, key, cache)`.
- *
- * If `thisp` is provided, function will be called in the `this`-context of
- * the provided object, or the cache if no `thisp` object is provided.
- *
- * Does not update age or recenty of use, or iterate over stale values.
- */
- forEach(fn, thisp = this) {
- for (const i of this.#indexes()) {
- const v = this.#valList[i];
- const value = this.#isBackgroundFetch(v)
- ? v.__staleWhileFetching
- : v;
- if (value === undefined)
- continue;
- fn.call(thisp, value, this.#keyList[i], this);
- }
- }
- /**
- * The same as {@link LRUCache.forEach} but items are iterated over in
- * reverse order. (ie, less recently used items are iterated over first.)
- */
- rforEach(fn, thisp = this) {
- for (const i of this.#rindexes()) {
- const v = this.#valList[i];
- const value = this.#isBackgroundFetch(v)
- ? v.__staleWhileFetching
- : v;
- if (value === undefined)
- continue;
- fn.call(thisp, value, this.#keyList[i], this);
- }
- }
- /**
- * Delete any stale entries. Returns true if anything was removed,
- * false otherwise.
- */
- purgeStale() {
- let deleted = false;
- for (const i of this.#rindexes({ allowStale: true })) {
- if (this.#isStale(i)) {
- this.#delete(this.#keyList[i], 'expire');
- deleted = true;
- }
- }
- return deleted;
- }
- /**
- * Get the extended info about a given entry, to get its value, size, and
- * TTL info simultaneously. Returns `undefined` if the key is not present.
- *
- * Unlike {@link LRUCache#dump}, which is designed to be portable and survive
- * serialization, the `start` value is always the current timestamp, and the
- * `ttl` is a calculated remaining time to live (negative if expired).
- *
- * Always returns stale values, if their info is found in the cache, so be
- * sure to check for expirations (ie, a negative {@link LRUCache.Entry#ttl})
- * if relevant.
- */
- info(key) {
- const i = this.#keyMap.get(key);
- if (i === undefined)
- return undefined;
- const v = this.#valList[i];
- const value = this.#isBackgroundFetch(v)
- ? v.__staleWhileFetching
- : v;
- if (value === undefined)
- return undefined;
- const entry = { value };
- if (this.#ttls && this.#starts) {
- const ttl = this.#ttls[i];
- const start = this.#starts[i];
- if (ttl && start) {
- const remain = ttl - (perf.now() - start);
- entry.ttl = remain;
- entry.start = Date.now();
- }
- }
- if (this.#sizes) {
- entry.size = this.#sizes[i];
- }
- return entry;
- }
- /**
- * Return an array of [key, {@link LRUCache.Entry}] tuples which can be
- * passed to {@link LRUCache#load}.
- *
- * The `start` fields are calculated relative to a portable `Date.now()`
- * timestamp, even if `performance.now()` is available.
- *
- * Stale entries are always included in the `dump`, even if
- * {@link LRUCache.OptionsBase.allowStale} is false.
- *
- * Note: this returns an actual array, not a generator, so it can be more
- * easily passed around.
- */
- dump() {
- const arr = [];
- for (const i of this.#indexes({ allowStale: true })) {
- const key = this.#keyList[i];
- const v = this.#valList[i];
- const value = this.#isBackgroundFetch(v)
- ? v.__staleWhileFetching
- : v;
- if (value === undefined || key === undefined)
- continue;
- const entry = { value };
- if (this.#ttls && this.#starts) {
- entry.ttl = this.#ttls[i];
- // always dump the start relative to a portable timestamp
- // it's ok for this to be a bit slow, it's a rare operation.
- const age = perf.now() - this.#starts[i];
- entry.start = Math.floor(Date.now() - age);
- }
- if (this.#sizes) {
- entry.size = this.#sizes[i];
- }
- arr.unshift([key, entry]);
- }
- return arr;
- }
- /**
- * Reset the cache and load in the items in entries in the order listed.
- *
- * The shape of the resulting cache may be different if the same options are
- * not used in both caches.
- *
- * The `start` fields are assumed to be calculated relative to a portable
- * `Date.now()` timestamp, even if `performance.now()` is available.
- */
- load(arr) {
- this.clear();
- for (const [key, entry] of arr) {
- if (entry.start) {
- // entry.start is a portable timestamp, but we may be using
- // node's performance.now(), so calculate the offset, so that
- // we get the intended remaining TTL, no matter how long it's
- // been on ice.
- //
- // it's ok for this to be a bit slow, it's a rare operation.
- const age = Date.now() - entry.start;
- entry.start = perf.now() - age;
- }
- this.set(key, entry.value, entry);
- }
- }
- /**
- * Add a value to the cache.
- *
- * Note: if `undefined` is specified as a value, this is an alias for
- * {@link LRUCache#delete}
- *
- * Fields on the {@link LRUCache.SetOptions} options param will override
- * their corresponding values in the constructor options for the scope
- * of this single `set()` operation.
- *
- * If `start` is provided, then that will set the effective start
- * time for the TTL calculation. Note that this must be a previous
- * value of `performance.now()` if supported, or a previous value of
- * `Date.now()` if not.
- *
- * Options object may also include `size`, which will prevent
- * calling the `sizeCalculation` function and just use the specified
- * number if it is a positive integer, and `noDisposeOnSet` which
- * will prevent calling a `dispose` function in the case of
- * overwrites.
- *
- * If the `size` (or return value of `sizeCalculation`) for a given
- * entry is greater than `maxEntrySize`, then the item will not be
- * added to the cache.
- *
- * Will update the recency of the entry.
- *
- * If the value is `undefined`, then this is an alias for
- * `cache.delete(key)`. `undefined` is never stored in the cache.
- */
- set(k, v, setOptions = {}) {
- if (v === undefined) {
- this.delete(k);
- return this;
- }
- const { ttl = this.ttl, start, noDisposeOnSet = this.noDisposeOnSet, sizeCalculation = this.sizeCalculation, status, } = setOptions;
- let { noUpdateTTL = this.noUpdateTTL } = setOptions;
- const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation);
- // if the item doesn't fit, don't do anything
- // NB: maxEntrySize set to maxSize by default
- if (this.maxEntrySize && size > this.maxEntrySize) {
- if (status) {
- status.set = 'miss';
- status.maxEntrySizeExceeded = true;
- }
- // have to delete, in case something is there already.
- this.#delete(k, 'set');
- return this;
- }
- let index = this.#size === 0 ? undefined : this.#keyMap.get(k);
- if (index === undefined) {
- // addition
- index = (this.#size === 0
- ? this.#tail
- : this.#free.length !== 0
- ? this.#free.pop()
- : this.#size === this.#max
- ? this.#evict(false)
- : this.#size);
- this.#keyList[index] = k;
- this.#valList[index] = v;
- this.#keyMap.set(k, index);
- this.#next[this.#tail] = index;
- this.#prev[index] = this.#tail;
- this.#tail = index;
- this.#size++;
- this.#addItemSize(index, size, status);
- if (status)
- status.set = 'add';
- noUpdateTTL = false;
- }
- else {
- // update
- this.#moveToTail(index);
- const oldVal = this.#valList[index];
- if (v !== oldVal) {
- if (this.#hasFetchMethod && this.#isBackgroundFetch(oldVal)) {
- oldVal.__abortController.abort(new Error('replaced'));
- const { __staleWhileFetching: s } = oldVal;
- if (s !== undefined && !noDisposeOnSet) {
- if (this.#hasDispose) {
- this.#dispose?.(s, k, 'set');
- }
- if (this.#hasDisposeAfter) {
- this.#disposed?.push([s, k, 'set']);
- }
- }
- }
- else if (!noDisposeOnSet) {
- if (this.#hasDispose) {
- this.#dispose?.(oldVal, k, 'set');
- }
- if (this.#hasDisposeAfter) {
- this.#disposed?.push([oldVal, k, 'set']);
- }
- }
- this.#removeItemSize(index);
- this.#addItemSize(index, size, status);
- this.#valList[index] = v;
- if (status) {
- status.set = 'replace';
- const oldValue = oldVal && this.#isBackgroundFetch(oldVal)
- ? oldVal.__staleWhileFetching
- : oldVal;
- if (oldValue !== undefined)
- status.oldValue = oldValue;
- }
- }
- else if (status) {
- status.set = 'update';
- }
- }
- if (ttl !== 0 && !this.#ttls) {
- this.#initializeTTLTracking();
- }
- if (this.#ttls) {
- if (!noUpdateTTL) {
- this.#setItemTTL(index, ttl, start);
- }
- if (status)
- this.#statusTTL(status, index);
- }
- if (!noDisposeOnSet && this.#hasDisposeAfter && this.#disposed) {
- const dt = this.#disposed;
- let task;
- while ((task = dt?.shift())) {
- this.#disposeAfter?.(...task);
- }
- }
- return this;
- }
- /**
- * Evict the least recently used item, returning its value or
- * `undefined` if cache is empty.
- */
- pop() {
- try {
- while (this.#size) {
- const val = this.#valList[this.#head];
- this.#evict(true);
- if (this.#isBackgroundFetch(val)) {
- if (val.__staleWhileFetching) {
- return val.__staleWhileFetching;
- }
- }
- else if (val !== undefined) {
- return val;
- }
- }
- }
- finally {
- if (this.#hasDisposeAfter && this.#disposed) {
- const dt = this.#disposed;
- let task;
- while ((task = dt?.shift())) {
- this.#disposeAfter?.(...task);
- }
- }
- }
- }
- #evict(free) {
- const head = this.#head;
- const k = this.#keyList[head];
- const v = this.#valList[head];
- if (this.#hasFetchMethod && this.#isBackgroundFetch(v)) {
- v.__abortController.abort(new Error('evicted'));
- }
- else if (this.#hasDispose || this.#hasDisposeAfter) {
- if (this.#hasDispose) {
- this.#dispose?.(v, k, 'evict');
- }
- if (this.#hasDisposeAfter) {
- this.#disposed?.push([v, k, 'evict']);
- }
- }
- this.#removeItemSize(head);
- // if we aren't about to use the index, then null these out
- if (free) {
- this.#keyList[head] = undefined;
- this.#valList[head] = undefined;
- this.#free.push(head);
- }
- if (this.#size === 1) {
- this.#head = this.#tail = 0;
- this.#free.length = 0;
- }
- else {
- this.#head = this.#next[head];
- }
- this.#keyMap.delete(k);
- this.#size--;
- return head;
- }
- /**
- * Check if a key is in the cache, without updating the recency of use.
- * Will return false if the item is stale, even though it is technically
- * in the cache.
- *
- * Check if a key is in the cache, without updating the recency of
- * use. Age is updated if {@link LRUCache.OptionsBase.updateAgeOnHas} is set
- * to `true` in either the options or the constructor.
- *
- * Will return `false` if the item is stale, even though it is technically in
- * the cache. The difference can be determined (if it matters) by using a
- * `status` argument, and inspecting the `has` field.
- *
- * Will not update item age unless
- * {@link LRUCache.OptionsBase.updateAgeOnHas} is set.
- */
- has(k, hasOptions = {}) {
- const { updateAgeOnHas = this.updateAgeOnHas, status } = hasOptions;
- const index = this.#keyMap.get(k);
- if (index !== undefined) {
- const v = this.#valList[index];
- if (this.#isBackgroundFetch(v) &&
- v.__staleWhileFetching === undefined) {
- return false;
- }
- if (!this.#isStale(index)) {
- if (updateAgeOnHas) {
- this.#updateItemAge(index);
- }
- if (status) {
- status.has = 'hit';
- this.#statusTTL(status, index);
- }
- return true;
- }
- else if (status) {
- status.has = 'stale';
- this.#statusTTL(status, index);
- }
- }
- else if (status) {
- status.has = 'miss';
- }
- return false;
- }
- /**
- * Like {@link LRUCache#get} but doesn't update recency or delete stale
- * items.
- *
- * Returns `undefined` if the item is stale, unless
- * {@link LRUCache.OptionsBase.allowStale} is set.
- */
- peek(k, peekOptions = {}) {
- const { allowStale = this.allowStale } = peekOptions;
- const index = this.#keyMap.get(k);
- if (index === undefined ||
- (!allowStale && this.#isStale(index))) {
- return;
- }
- const v = this.#valList[index];
- // either stale and allowed, or forcing a refresh of non-stale value
- return this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
- }
- #backgroundFetch(k, index, options, context) {
- const v = index === undefined ? undefined : this.#valList[index];
- if (this.#isBackgroundFetch(v)) {
- return v;
- }
- const ac = new AC();
- const { signal } = options;
- // when/if our AC signals, then stop listening to theirs.
- signal?.addEventListener('abort', () => ac.abort(signal.reason), {
- signal: ac.signal,
- });
- const fetchOpts = {
- signal: ac.signal,
- options,
- context,
- };
- const cb = (v, updateCache = false) => {
- const { aborted } = ac.signal;
- const ignoreAbort = options.ignoreFetchAbort && v !== undefined;
- if (options.status) {
- if (aborted && !updateCache) {
- options.status.fetchAborted = true;
- options.status.fetchError = ac.signal.reason;
- if (ignoreAbort)
- options.status.fetchAbortIgnored = true;
- }
- else {
- options.status.fetchResolved = true;
- }
- }
- if (aborted && !ignoreAbort && !updateCache) {
- return fetchFail(ac.signal.reason);
- }
- // either we didn't abort, and are still here, or we did, and ignored
- const bf = p;
- if (this.#valList[index] === p) {
- if (v === undefined) {
- if (bf.__staleWhileFetching) {
- this.#valList[index] = bf.__staleWhileFetching;
- }
- else {
- this.#delete(k, 'fetch');
- }
- }
- else {
- if (options.status)
- options.status.fetchUpdated = true;
- this.set(k, v, fetchOpts.options);
- }
- }
- return v;
- };
- const eb = (er) => {
- if (options.status) {
- options.status.fetchRejected = true;
- options.status.fetchError = er;
- }
- return fetchFail(er);
- };
- const fetchFail = (er) => {
- const { aborted } = ac.signal;
- const allowStaleAborted = aborted && options.allowStaleOnFetchAbort;
- const allowStale = allowStaleAborted || options.allowStaleOnFetchRejection;
- const noDelete = allowStale || options.noDeleteOnFetchRejection;
- const bf = p;
- if (this.#valList[index] === p) {
- // if we allow stale on fetch rejections, then we need to ensure that
- // the stale value is not removed from the cache when the fetch fails.
- const del = !noDelete || bf.__staleWhileFetching === undefined;
- if (del) {
- this.#delete(k, 'fetch');
- }
- else if (!allowStaleAborted) {
- // still replace the *promise* with the stale value,
- // since we are done with the promise at this point.
- // leave it untouched if we're still waiting for an
- // aborted background fetch that hasn't yet returned.
- this.#valList[index] = bf.__staleWhileFetching;
- }
- }
- if (allowStale) {
- if (options.status && bf.__staleWhileFetching !== undefined) {
- options.status.returnedStale = true;
- }
- return bf.__staleWhileFetching;
- }
- else if (bf.__returned === bf) {
- throw er;
- }
- };
- const pcall = (res, rej) => {
- const fmp = this.#fetchMethod?.(k, v, fetchOpts);
- if (fmp && fmp instanceof Promise) {
- fmp.then(v => res(v === undefined ? undefined : v), rej);
- }
- // ignored, we go until we finish, regardless.
- // defer check until we are actually aborting,
- // so fetchMethod can override.
- ac.signal.addEventListener('abort', () => {
- if (!options.ignoreFetchAbort ||
- options.allowStaleOnFetchAbort) {
- res(undefined);
- // when it eventually resolves, update the cache.
- if (options.allowStaleOnFetchAbort) {
- res = v => cb(v, true);
- }
- }
- });
- };
- if (options.status)
- options.status.fetchDispatched = true;
- const p = new Promise(pcall).then(cb, eb);
- const bf = Object.assign(p, {
- __abortController: ac,
- __staleWhileFetching: v,
- __returned: undefined,
- });
- if (index === undefined) {
- // internal, don't expose status.
- this.set(k, bf, { ...fetchOpts.options, status: undefined });
- index = this.#keyMap.get(k);
- }
- else {
- this.#valList[index] = bf;
- }
- return bf;
- }
- #isBackgroundFetch(p) {
- if (!this.#hasFetchMethod)
- return false;
- const b = p;
- return (!!b &&
- b instanceof Promise &&
- b.hasOwnProperty('__staleWhileFetching') &&
- b.__abortController instanceof AC);
- }
- async fetch(k, fetchOptions = {}) {
- const {
- // get options
- allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet,
- // set options
- ttl = this.ttl, noDisposeOnSet = this.noDisposeOnSet, size = 0, sizeCalculation = this.sizeCalculation, noUpdateTTL = this.noUpdateTTL,
- // fetch exclusive options
- noDeleteOnFetchRejection = this.noDeleteOnFetchRejection, allowStaleOnFetchRejection = this.allowStaleOnFetchRejection, ignoreFetchAbort = this.ignoreFetchAbort, allowStaleOnFetchAbort = this.allowStaleOnFetchAbort, context, forceRefresh = false, status, signal, } = fetchOptions;
- if (!this.#hasFetchMethod) {
- if (status)
- status.fetch = 'get';
- return this.get(k, {
- allowStale,
- updateAgeOnGet,
- noDeleteOnStaleGet,
- status,
- });
- }
- const options = {
- allowStale,
- updateAgeOnGet,
- noDeleteOnStaleGet,
- ttl,
- noDisposeOnSet,
- size,
- sizeCalculation,
- noUpdateTTL,
- noDeleteOnFetchRejection,
- allowStaleOnFetchRejection,
- allowStaleOnFetchAbort,
- ignoreFetchAbort,
- status,
- signal,
- };
- let index = this.#keyMap.get(k);
- if (index === undefined) {
- if (status)
- status.fetch = 'miss';
- const p = this.#backgroundFetch(k, index, options, context);
- return (p.__returned = p);
- }
- else {
- // in cache, maybe already fetching
- const v = this.#valList[index];
- if (this.#isBackgroundFetch(v)) {
- const stale = allowStale && v.__staleWhileFetching !== undefined;
- if (status) {
- status.fetch = 'inflight';
- if (stale)
- status.returnedStale = true;
- }
- return stale ? v.__staleWhileFetching : (v.__returned = v);
- }
- // if we force a refresh, that means do NOT serve the cached value,
- // unless we are already in the process of refreshing the cache.
- const isStale = this.#isStale(index);
- if (!forceRefresh && !isStale) {
- if (status)
- status.fetch = 'hit';
- this.#moveToTail(index);
- if (updateAgeOnGet) {
- this.#updateItemAge(index);
- }
- if (status)
- this.#statusTTL(status, index);
- return v;
- }
- // ok, it is stale or a forced refresh, and not already fetching.
- // refresh the cache.
- const p = this.#backgroundFetch(k, index, options, context);
- const hasStale = p.__staleWhileFetching !== undefined;
- const staleVal = hasStale && allowStale;
- if (status) {
- status.fetch = isStale ? 'stale' : 'refresh';
- if (staleVal && isStale)
- status.returnedStale = true;
- }
- return staleVal ? p.__staleWhileFetching : (p.__returned = p);
- }
- }
- async forceFetch(k, fetchOptions = {}) {
- const v = await this.fetch(k, fetchOptions);
- if (v === undefined)
- throw new Error('fetch() returned undefined');
- return v;
- }
- memo(k, memoOptions = {}) {
- const memoMethod = this.#memoMethod;
- if (!memoMethod) {
- throw new Error('no memoMethod provided to constructor');
- }
- const { context, forceRefresh, ...options } = memoOptions;
- const v = this.get(k, options);
- if (!forceRefresh && v !== undefined)
- return v;
- const vv = memoMethod(k, v, {
- options,
- context,
- });
- this.set(k, vv, options);
- return vv;
- }
- /**
- * Return a value from the cache. Will update the recency of the cache
- * entry found.
- *
- * If the key is not found, get() will return `undefined`.
- */
- get(k, getOptions = {}) {
- const { allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet, status, } = getOptions;
- const index = this.#keyMap.get(k);
- if (index !== undefined) {
- const value = this.#valList[index];
- const fetching = this.#isBackgroundFetch(value);
- if (status)
- this.#statusTTL(status, index);
- if (this.#isStale(index)) {
- if (status)
- status.get = 'stale';
- // delete only if not an in-flight background fetch
- if (!fetching) {
- if (!noDeleteOnStaleGet) {
- this.#delete(k, 'expire');
- }
- if (status && allowStale)
- status.returnedStale = true;
- return allowStale ? value : undefined;
- }
- else {
- if (status &&
- allowStale &&
- value.__staleWhileFetching !== undefined) {
- status.returnedStale = true;
- }
- return allowStale ? value.__staleWhileFetching : undefined;
- }
- }
- else {
- if (status)
- status.get = 'hit';
- // if we're currently fetching it, we don't actually have it yet
- // it's not stale, which means this isn't a staleWhileRefetching.
- // If it's not stale, and fetching, AND has a __staleWhileFetching
- // value, then that means the user fetched with {forceRefresh:true},
- // so it's safe to return that value.
- if (fetching) {
- return value.__staleWhileFetching;
- }
- this.#moveToTail(index);
- if (updateAgeOnGet) {
- this.#updateItemAge(index);
- }
- return value;
- }
- }
- else if (status) {
- status.get = 'miss';
- }
- }
- #connect(p, n) {
- this.#prev[n] = p;
- this.#next[p] = n;
- }
- #moveToTail(index) {
- // if tail already, nothing to do
- // if head, move head to next[index]
- // else
- // move next[prev[index]] to next[index] (head has no prev)
- // move prev[next[index]] to prev[index]
- // prev[index] = tail
- // next[tail] = index
- // tail = index
- if (index !== this.#tail) {
- if (index === this.#head) {
- this.#head = this.#next[index];
- }
- else {
- this.#connect(this.#prev[index], this.#next[index]);
- }
- this.#connect(this.#tail, index);
- this.#tail = index;
- }
- }
- /**
- * Deletes a key out of the cache.
- *
- * Returns true if the key was deleted, false otherwise.
- */
- delete(k) {
- return this.#delete(k, 'delete');
- }
- #delete(k, reason) {
- let deleted = false;
- if (this.#size !== 0) {
- const index = this.#keyMap.get(k);
- if (index !== undefined) {
- deleted = true;
- if (this.#size === 1) {
- this.#clear(reason);
- }
- else {
- this.#removeItemSize(index);
- const v = this.#valList[index];
- if (this.#isBackgroundFetch(v)) {
- v.__abortController.abort(new Error('deleted'));
- }
- else if (this.#hasDispose || this.#hasDisposeAfter) {
- if (this.#hasDispose) {
- this.#dispose?.(v, k, reason);
- }
- if (this.#hasDisposeAfter) {
- this.#disposed?.push([v, k, reason]);
- }
- }
- this.#keyMap.delete(k);
- this.#keyList[index] = undefined;
- this.#valList[index] = undefined;
- if (index === this.#tail) {
- this.#tail = this.#prev[index];
- }
- else if (index === this.#head) {
- this.#head = this.#next[index];
- }
- else {
- const pi = this.#prev[index];
- this.#next[pi] = this.#next[index];
- const ni = this.#next[index];
- this.#prev[ni] = this.#prev[index];
- }
- this.#size--;
- this.#free.push(index);
- }
- }
- }
- if (this.#hasDisposeAfter && this.#disposed?.length) {
- const dt = this.#disposed;
- let task;
- while ((task = dt?.shift())) {
- this.#disposeAfter?.(...task);
- }
- }
- return deleted;
- }
- /**
- * Clear the cache entirely, throwing away all values.
- */
- clear() {
- return this.#clear('delete');
- }
- #clear(reason) {
- for (const index of this.#rindexes({ allowStale: true })) {
- const v = this.#valList[index];
- if (this.#isBackgroundFetch(v)) {
- v.__abortController.abort(new Error('deleted'));
- }
- else {
- const k = this.#keyList[index];
- if (this.#hasDispose) {
- this.#dispose?.(v, k, reason);
- }
- if (this.#hasDisposeAfter) {
- this.#disposed?.push([v, k, reason]);
- }
- }
- }
- this.#keyMap.clear();
- this.#valList.fill(undefined);
- this.#keyList.fill(undefined);
- if (this.#ttls && this.#starts) {
- this.#ttls.fill(0);
- this.#starts.fill(0);
- }
- if (this.#sizes) {
- this.#sizes.fill(0);
- }
- this.#head = 0;
- this.#tail = 0;
- this.#free.length = 0;
- this.#calculatedSize = 0;
- this.#size = 0;
- if (this.#hasDisposeAfter && this.#disposed) {
- const dt = this.#disposed;
- let task;
- while ((task = dt?.shift())) {
- this.#disposeAfter?.(...task);
- }
- }
- }
- }
- //# sourceMappingURL=index.js.map
|