Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
index.js1591 linesDownload Raw Back to esm
1/**
2 * @module LRUCache
3 */
4const defaultPerf = (typeof performance === 'object' &&
5    performance &&
6    typeof performance.now === 'function') ?
7    performance
8    : Date;
9const warned = new Set();
10/* c8 ignore start */
11const PROCESS = (typeof process === 'object' && !!process ?
12    process
13    : {});
14/* c8 ignore start */
15const emitWarning = (msg, type, code, fn) => {
16    typeof PROCESS.emitWarning === 'function' ?
17        PROCESS.emitWarning(msg, type, code, fn)
18        : console.error(`[${code}] ${type}: ${msg}`);
19};
20let AC = globalThis.AbortController;
21let AS = globalThis.AbortSignal;
22/* c8 ignore start */
23if (typeof AC === 'undefined') {
24    //@ts-ignore
25    AS = class AbortSignal {
26        onabort;
27        _onabort = [];
28        reason;
29        aborted = false;
30        addEventListener(_, fn) {
31            this._onabort.push(fn);
32        }
33    };
34    //@ts-ignore
35    AC = class AbortController {
36        constructor() {
37            warnACPolyfill();
38        }
39        signal = new AS();
40        abort(reason) {
41            if (this.signal.aborted)
42                return;
43            //@ts-ignore
44            this.signal.reason = reason;
45            //@ts-ignore
46            this.signal.aborted = true;
47            //@ts-ignore
48            for (const fn of this.signal._onabort) {
49                fn(reason);
50            }
51            this.signal.onabort?.(reason);
52        }
53    };
54    let printACPolyfillWarning = PROCESS.env?.LRU_CACHE_IGNORE_AC_WARNING !== '1';
55    const warnACPolyfill = () => {
56        if (!printACPolyfillWarning)
57            return;
58        printACPolyfillWarning = false;
59        emitWarning('AbortController is not defined. If using lru-cache in ' +
60            'node 14, load an AbortController polyfill from the ' +
61            '`node-abort-controller` package. A minimal polyfill is ' +
62            'provided for use by LRUCache.fetch(), but it should not be ' +
63            'relied upon in other contexts (eg, passing it to other APIs that ' +
64            'use AbortController/AbortSignal might have undesirable effects). ' +
65            'You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.', 'NO_ABORT_CONTROLLER', 'ENOTSUP', warnACPolyfill);
66    };
67}
68/* c8 ignore stop */
69const shouldWarn = (code) => !warned.has(code);
70const TYPE = Symbol('type');
71const isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n);
72/* c8 ignore start */
73// This is a little bit ridiculous, tbh.
74// The maximum array length is 2^32-1 or thereabouts on most JS impls.
75// And well before that point, you're caching the entire world, I mean,
76// that's ~32GB of just integers for the next/prev links, plus whatever
77// else to hold that many keys and values.  Just filling the memory with
78// zeroes at init time is brutal when you get that big.
79// But why not be complete?
80// Maybe in the future, these limits will have expanded.
81const getUintArray = (max) => !isPosInt(max) ? null
82    : max <= Math.pow(2, 8) ? Uint8Array
83        : max <= Math.pow(2, 16) ? Uint16Array
84            : max <= Math.pow(2, 32) ? Uint32Array
85                : max <= Number.MAX_SAFE_INTEGER ? ZeroArray
86                    : null;
87/* c8 ignore stop */
88class ZeroArray extends Array {
89    constructor(size) {
90        super(size);
91        this.fill(0);
92    }
93}
94class Stack {
95    heap;
96    length;
97    // private constructor
98    static #constructing = false;
99    static create(max) {
100        const HeapCls = getUintArray(max);
101        if (!HeapCls)
102            return [];
103        Stack.#constructing = true;
104        const s = new Stack(max, HeapCls);
105        Stack.#constructing = false;
106        return s;
107    }
108    constructor(max, HeapCls) {
109        /* c8 ignore start */
110        if (!Stack.#constructing) {
111            throw new TypeError('instantiate Stack using Stack.create(n)');
112        }
113        /* c8 ignore stop */
114        this.heap = new HeapCls(max);
115        this.length = 0;
116    }
117    push(n) {
118        this.heap[this.length++] = n;
119    }
120    pop() {
121        return this.heap[--this.length];
122    }
123}
124/**
125 * Default export, the thing you're using this module to get.
126 *
127 * The `K` and `V` types define the key and value types, respectively. The
128 * optional `FC` type defines the type of the `context` object passed to
129 * `cache.fetch()` and `cache.memo()`.
130 *
131 * Keys and values **must not** be `null` or `undefined`.
132 *
133 * All properties from the options object (with the exception of `max`,
134 * `maxSize`, `fetchMethod`, `memoMethod`, `dispose` and `disposeAfter`) are
135 * added as normal public members. (The listed options are read-only getters.)
136 *
137 * Changing any of these will alter the defaults for subsequent method calls.
138 */
139export class LRUCache {
140    // options that cannot be changed without disaster
141    #max;
142    #maxSize;
143    #dispose;
144    #onInsert;
145    #disposeAfter;
146    #fetchMethod;
147    #memoMethod;
148    #perf;
149    /**
150     * {@link LRUCache.OptionsBase.perf}
151     */
152    get perf() {
153        return this.#perf;
154    }
155    /**
156     * {@link LRUCache.OptionsBase.ttl}
157     */
158    ttl;
159    /**
160     * {@link LRUCache.OptionsBase.ttlResolution}
161     */
162    ttlResolution;
163    /**
164     * {@link LRUCache.OptionsBase.ttlAutopurge}
165     */
166    ttlAutopurge;
167    /**
168     * {@link LRUCache.OptionsBase.updateAgeOnGet}
169     */
170    updateAgeOnGet;
171    /**
172     * {@link LRUCache.OptionsBase.updateAgeOnHas}
173     */
174    updateAgeOnHas;
175    /**
176     * {@link LRUCache.OptionsBase.allowStale}
177     */
178    allowStale;
179    /**
180     * {@link LRUCache.OptionsBase.noDisposeOnSet}
181     */
182    noDisposeOnSet;
183    /**
184     * {@link LRUCache.OptionsBase.noUpdateTTL}
185     */
186    noUpdateTTL;
187    /**
188     * {@link LRUCache.OptionsBase.maxEntrySize}
189     */
190    maxEntrySize;
191    /**
192     * {@link LRUCache.OptionsBase.sizeCalculation}
193     */
194    sizeCalculation;
195    /**
196     * {@link LRUCache.OptionsBase.noDeleteOnFetchRejection}
197     */
198    noDeleteOnFetchRejection;
199    /**
200     * {@link LRUCache.OptionsBase.noDeleteOnStaleGet}
201     */
202    noDeleteOnStaleGet;
203    /**
204     * {@link LRUCache.OptionsBase.allowStaleOnFetchAbort}
205     */
206    allowStaleOnFetchAbort;
207    /**
208     * {@link LRUCache.OptionsBase.allowStaleOnFetchRejection}
209     */
210    allowStaleOnFetchRejection;
211    /**
212     * {@link LRUCache.OptionsBase.ignoreFetchAbort}
213     */
214    ignoreFetchAbort;
215    // computed properties
216    #size;
217    #calculatedSize;
218    #keyMap;
219    #keyList;
220    #valList;
221    #next;
222    #prev;
223    #head;
224    #tail;
225    #free;
226    #disposed;
227    #sizes;
228    #starts;
229    #ttls;
230    #autopurgeTimers;
231    #hasDispose;
232    #hasFetchMethod;
233    #hasDisposeAfter;
234    #hasOnInsert;
235    /**
236     * Do not call this method unless you need to inspect the
237     * inner workings of the cache.  If anything returned by this
238     * object is modified in any way, strange breakage may occur.
239     *
240     * These fields are private for a reason!
241     *
242     * @internal
243     */
244    static unsafeExposeInternals(c) {
245        return {
246            // properties
247            starts: c.#starts,
248            ttls: c.#ttls,
249            autopurgeTimers: c.#autopurgeTimers,
250            sizes: c.#sizes,
251            keyMap: c.#keyMap,
252            keyList: c.#keyList,
253            valList: c.#valList,
254            next: c.#next,
255            prev: c.#prev,
256            get head() {
257                return c.#head;
258            },
259            get tail() {
260                return c.#tail;
261            },
262            free: c.#free,
263            // methods
264            isBackgroundFetch: (p) => c.#isBackgroundFetch(p),
265            backgroundFetch: (k, index, options, context) => c.#backgroundFetch(k, index, options, context),
266            moveToTail: (index) => c.#moveToTail(index),
267            indexes: (options) => c.#indexes(options),
268            rindexes: (options) => c.#rindexes(options),
269            isStale: (index) => c.#isStale(index),
270        };
271    }
272    // Protected read-only members
273    /**
274     * {@link LRUCache.OptionsBase.max} (read-only)
275     */
276    get max() {
277        return this.#max;
278    }
279    /**
280     * {@link LRUCache.OptionsBase.maxSize} (read-only)
281     */
282    get maxSize() {
283        return this.#maxSize;
284    }
285    /**
286     * The total computed size of items in the cache (read-only)
287     */
288    get calculatedSize() {
289        return this.#calculatedSize;
290    }
291    /**
292     * The number of items stored in the cache (read-only)
293     */
294    get size() {
295        return this.#size;
296    }
297    /**
298     * {@link LRUCache.OptionsBase.fetchMethod} (read-only)
299     */
300    get fetchMethod() {
301        return this.#fetchMethod;
302    }
303    get memoMethod() {
304        return this.#memoMethod;
305    }
306    /**
307     * {@link LRUCache.OptionsBase.dispose} (read-only)
308     */
309    get dispose() {
310        return this.#dispose;
311    }
312    /**
313     * {@link LRUCache.OptionsBase.onInsert} (read-only)
314     */
315    get onInsert() {
316        return this.#onInsert;
317    }
318    /**
319     * {@link LRUCache.OptionsBase.disposeAfter} (read-only)
320     */
321    get disposeAfter() {
322        return this.#disposeAfter;
323    }
324    constructor(options) {
325        const { max = 0, ttl, ttlResolution = 1, ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, onInsert, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0, maxEntrySize = 0, sizeCalculation, fetchMethod, memoMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, perf, } = options;
326        if (perf !== undefined) {
327            if (typeof perf?.now !== 'function') {
328                throw new TypeError('perf option must have a now() method if specified');
329            }
330        }
331        this.#perf = perf ?? defaultPerf;
332        if (max !== 0 && !isPosInt(max)) {
333            throw new TypeError('max option must be a nonnegative integer');
334        }
335        const UintArray = max ? getUintArray(max) : Array;
336        if (!UintArray) {
337            throw new Error('invalid max value: ' + max);
338        }
339        this.#max = max;
340        this.#maxSize = maxSize;
341        this.maxEntrySize = maxEntrySize || this.#maxSize;
342        this.sizeCalculation = sizeCalculation;
343        if (this.sizeCalculation) {
344            if (!this.#maxSize && !this.maxEntrySize) {
345                throw new TypeError('cannot set sizeCalculation without setting maxSize or maxEntrySize');
346            }
347            if (typeof this.sizeCalculation !== 'function') {
348                throw new TypeError('sizeCalculation set to non-function');
349            }
350        }
351        if (memoMethod !== undefined && typeof memoMethod !== 'function') {
352            throw new TypeError('memoMethod must be a function if defined');
353        }
354        this.#memoMethod = memoMethod;
355        if (fetchMethod !== undefined && typeof fetchMethod !== 'function') {
356            throw new TypeError('fetchMethod must be a function if specified');
357        }
358        this.#fetchMethod = fetchMethod;
359        this.#hasFetchMethod = !!fetchMethod;
360        this.#keyMap = new Map();
361        this.#keyList = new Array(max).fill(undefined);
362        this.#valList = new Array(max).fill(undefined);
363        this.#next = new UintArray(max);
364        this.#prev = new UintArray(max);
365        this.#head = 0;
366        this.#tail = 0;
367        this.#free = Stack.create(max);
368        this.#size = 0;
369        this.#calculatedSize = 0;
370        if (typeof dispose === 'function') {
371            this.#dispose = dispose;
372        }
373        if (typeof onInsert === 'function') {
374            this.#onInsert = onInsert;
375        }
376        if (typeof disposeAfter === 'function') {
377            this.#disposeAfter = disposeAfter;
378            this.#disposed = [];
379        }
380        else {
381            this.#disposeAfter = undefined;
382            this.#disposed = undefined;
383        }
384        this.#hasDispose = !!this.#dispose;
385        this.#hasOnInsert = !!this.#onInsert;
386        this.#hasDisposeAfter = !!this.#disposeAfter;
387        this.noDisposeOnSet = !!noDisposeOnSet;
388        this.noUpdateTTL = !!noUpdateTTL;
389        this.noDeleteOnFetchRejection = !!noDeleteOnFetchRejection;
390        this.allowStaleOnFetchRejection = !!allowStaleOnFetchRejection;
391        this.allowStaleOnFetchAbort = !!allowStaleOnFetchAbort;
392        this.ignoreFetchAbort = !!ignoreFetchAbort;
393        // NB: maxEntrySize is set to maxSize if it's set
394        if (this.maxEntrySize !== 0) {
395            if (this.#maxSize !== 0) {
396                if (!isPosInt(this.#maxSize)) {
397                    throw new TypeError('maxSize must be a positive integer if specified');
398                }
399            }
400            if (!isPosInt(this.maxEntrySize)) {
401                throw new TypeError('maxEntrySize must be a positive integer if specified');
402            }
403            this.#initializeSizeTracking();
404        }
405        this.allowStale = !!allowStale;
406        this.noDeleteOnStaleGet = !!noDeleteOnStaleGet;
407        this.updateAgeOnGet = !!updateAgeOnGet;
408        this.updateAgeOnHas = !!updateAgeOnHas;
409        this.ttlResolution =
410            isPosInt(ttlResolution) || ttlResolution === 0 ? ttlResolution : 1;
411        this.ttlAutopurge = !!ttlAutopurge;
412        this.ttl = ttl || 0;
413        if (this.ttl) {
414            if (!isPosInt(this.ttl)) {
415                throw new TypeError('ttl must be a positive integer if specified');
416            }
417            this.#initializeTTLTracking();
418        }
419        // do not allow completely unbounded caches
420        if (this.#max === 0 && this.ttl === 0 && this.#maxSize === 0) {
421            throw new TypeError('At least one of max, maxSize, or ttl is required');
422        }
423        if (!this.ttlAutopurge && !this.#max && !this.#maxSize) {
424            const code = 'LRU_CACHE_UNBOUNDED';
425            if (shouldWarn(code)) {
426                warned.add(code);
427                const msg = 'TTL caching without ttlAutopurge, max, or maxSize can ' +
428                    'result in unbounded memory consumption.';
429                emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache);
430            }
431        }
432    }
433    /**
434     * Return the number of ms left in the item's TTL. If item is not in cache,
435     * returns `0`. Returns `Infinity` if item is in cache without a defined TTL.
436     */
437    getRemainingTTL(key) {
438        return this.#keyMap.has(key) ? Infinity : 0;
439    }
440    #initializeTTLTracking() {
441        const ttls = new ZeroArray(this.#max);
442        const starts = new ZeroArray(this.#max);
443        this.#ttls = ttls;
444        this.#starts = starts;
445        const purgeTimers = this.ttlAutopurge ?
446            new Array(this.#max)
447            : undefined;
448        this.#autopurgeTimers = purgeTimers;
449        this.#setItemTTL = (index, ttl, start = this.#perf.now()) => {
450            starts[index] = ttl !== 0 ? start : 0;
451            ttls[index] = ttl;
452            setPurgetTimer(index, ttl);
453        };
454        this.#updateItemAge = index => {
455            starts[index] = ttls[index] !== 0 ? this.#perf.now() : 0;
456            setPurgetTimer(index, ttls[index]);
457        };
458        // clear out the purge timer if we're setting TTL to 0, and
459        // previously had a ttl purge timer running, so it doesn't
460        // fire unnecessarily. Don't need to do this if we're not doing
461        // autopurge.
462        const setPurgetTimer = !this.ttlAutopurge ?
463            () => { }
464            : (index, ttl) => {
465                if (purgeTimers?.[index]) {
466                    clearTimeout(purgeTimers[index]);
467                    purgeTimers[index] = undefined;
468                }
469                if (ttl && ttl !== 0 && purgeTimers) {
470                    const t = setTimeout(() => {
471                        if (this.#isStale(index)) {
472                            this.#delete(this.#keyList[index], 'expire');
473                        }
474                    }, ttl + 1);
475                    // unref() not supported on all platforms
476                    /* c8 ignore start */
477                    if (t.unref) {
478                        t.unref();
479                    }
480                    /* c8 ignore stop */
481                    purgeTimers[index] = t;
482                }
483            };
484        this.#statusTTL = (status, index) => {
485            if (ttls[index]) {
486                const ttl = ttls[index];
487                const start = starts[index];
488                /* c8 ignore next */
489                if (!ttl || !start)
490                    return;
491                status.ttl = ttl;
492                status.start = start;
493                status.now = cachedNow || getNow();
494                const age = status.now - start;
495                status.remainingTTL = ttl - age;
496            }
497        };
498        // debounce calls to perf.now() to 1s so we're not hitting
499        // that costly call repeatedly.
500        let cachedNow = 0;
501        const getNow = () => {
502            const n = this.#perf.now();
503            if (this.ttlResolution > 0) {
504                cachedNow = n;
505                const t = setTimeout(() => (cachedNow = 0), this.ttlResolution);
506                // not available on all platforms
507                /* c8 ignore start */
508                if (t.unref) {
509                    t.unref();
510                }
511                /* c8 ignore stop */
512            }
513            return n;
514        };
515        this.getRemainingTTL = key => {
516            const index = this.#keyMap.get(key);
517            if (index === undefined) {
518                return 0;
519            }
520            const ttl = ttls[index];
521            const start = starts[index];
522            if (!ttl || !start) {
523                return Infinity;
524            }
525            const age = (cachedNow || getNow()) - start;
526            return ttl - age;
527        };
528        this.#isStale = index => {
529            const s = starts[index];
530            const t = ttls[index];
531            return !!t && !!s && (cachedNow || getNow()) - s > t;
532        };
533    }
534    // conditionally set private methods related to TTL
535    #updateItemAge = () => { };
536    #statusTTL = () => { };
537    #setItemTTL = () => { };
538    /* c8 ignore stop */
539    #isStale = () => false;
540    #initializeSizeTracking() {
541        const sizes = new ZeroArray(this.#max);
542        this.#calculatedSize = 0;
543        this.#sizes = sizes;
544        this.#removeItemSize = index => {
545            this.#calculatedSize -= sizes[index];
546            sizes[index] = 0;
547        };
548        this.#requireSize = (k, v, size, sizeCalculation) => {
549            // provisionally accept background fetches.
550            // actual value size will be checked when they return.
551            if (this.#isBackgroundFetch(v)) {
552                return 0;
553            }
554            if (!isPosInt(size)) {
555                if (sizeCalculation) {
556                    if (typeof sizeCalculation !== 'function') {
557                        throw new TypeError('sizeCalculation must be a function');
558                    }
559                    size = sizeCalculation(v, k);
560                    if (!isPosInt(size)) {
561                        throw new TypeError('sizeCalculation return invalid (expect positive integer)');
562                    }
563                }
564                else {
565                    throw new TypeError('invalid size value (must be positive integer). ' +
566                        'When maxSize or maxEntrySize is used, sizeCalculation ' +
567                        'or size must be set.');
568                }
569            }
570            return size;
571        };
572        this.#addItemSize = (index, size, status) => {
573            sizes[index] = size;
574            if (this.#maxSize) {
575                const maxSize = this.#maxSize - sizes[index];
576                while (this.#calculatedSize > maxSize) {
577                    this.#evict(true);
578                }
579            }
580            this.#calculatedSize += sizes[index];
581            if (status) {
582                status.entrySize = size;
583                status.totalCalculatedSize = this.#calculatedSize;
584            }
585        };
586    }
587    #removeItemSize = _i => { };
588    #addItemSize = (_i, _s, _st) => { };
589    #requireSize = (_k, _v, size, sizeCalculation) => {
590        if (size || sizeCalculation) {
591            throw new TypeError('cannot set size without setting maxSize or maxEntrySize on cache');
592        }
593        return 0;
594    };
595    *#indexes({ allowStale = this.allowStale } = {}) {
596        if (this.#size) {
597            for (let i = this.#tail; true;) {
598                if (!this.#isValidIndex(i)) {
599                    break;
600                }
601                if (allowStale || !this.#isStale(i)) {
602                    yield i;
603                }
604                if (i === this.#head) {
605                    break;
606                }
607                else {
608                    i = this.#prev[i];
609                }
610            }
611        }
612    }
613    *#rindexes({ allowStale = this.allowStale } = {}) {
614        if (this.#size) {
615            for (let i = this.#head; true;) {
616                if (!this.#isValidIndex(i)) {
617                    break;
618                }
619                if (allowStale || !this.#isStale(i)) {
620                    yield i;
621                }
622                if (i === this.#tail) {
623                    break;
624                }
625                else {
626                    i = this.#next[i];
627                }
628            }
629        }
630    }
631    #isValidIndex(index) {
632        return (index !== undefined &&
633            this.#keyMap.get(this.#keyList[index]) === index);
634    }
635    /**
636     * Return a generator yielding `[key, value]` pairs,
637     * in order from most recently used to least recently used.
638     */
639    *entries() {
640        for (const i of this.#indexes()) {
641            if (this.#valList[i] !== undefined &&
642                this.#keyList[i] !== undefined &&
643                !this.#isBackgroundFetch(this.#valList[i])) {
644                yield [this.#keyList[i], this.#valList[i]];
645            }
646        }
647    }
648    /**
649     * Inverse order version of {@link LRUCache.entries}
650     *
651     * Return a generator yielding `[key, value]` pairs,
652     * in order from least recently used to most recently used.
653     */
654    *rentries() {
655        for (const i of this.#rindexes()) {
656            if (this.#valList[i] !== undefined &&
657                this.#keyList[i] !== undefined &&
658                !this.#isBackgroundFetch(this.#valList[i])) {
659                yield [this.#keyList[i], this.#valList[i]];
660            }
661        }
662    }
663    /**
664     * Return a generator yielding the keys in the cache,
665     * in order from most recently used to least recently used.
666     */
667    *keys() {
668        for (const i of this.#indexes()) {
669            const k = this.#keyList[i];
670            if (k !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
671                yield k;
672            }
673        }
674    }
675    /**
676     * Inverse order version of {@link LRUCache.keys}
677     *
678     * Return a generator yielding the keys in the cache,
679     * in order from least recently used to most recently used.
680     */
681    *rkeys() {
682        for (const i of this.#rindexes()) {
683            const k = this.#keyList[i];
684            if (k !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
685                yield k;
686            }
687        }
688    }
689    /**
690     * Return a generator yielding the values in the cache,
691     * in order from most recently used to least recently used.
692     */
693    *values() {
694        for (const i of this.#indexes()) {
695            const v = this.#valList[i];
696            if (v !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
697                yield this.#valList[i];
698            }
699        }
700    }
701    /**
702     * Inverse order version of {@link LRUCache.values}
703     *
704     * Return a generator yielding the values in the cache,
705     * in order from least recently used to most recently used.
706     */
707    *rvalues() {
708        for (const i of this.#rindexes()) {
709            const v = this.#valList[i];
710            if (v !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
711                yield this.#valList[i];
712            }
713        }
714    }
715    /**
716     * Iterating over the cache itself yields the same results as
717     * {@link LRUCache.entries}
718     */
719    [Symbol.iterator]() {
720        return this.entries();
721    }
722    /**
723     * A String value that is used in the creation of the default string
724     * description of an object. Called by the built-in method
725     * `Object.prototype.toString`.
726     */
727    [Symbol.toStringTag] = 'LRUCache';
728    /**
729     * Find a value for which the supplied fn method returns a truthy value,
730     * similar to `Array.find()`. fn is called as `fn(value, key, cache)`.
731     */
732    find(fn, getOptions = {}) {
733        for (const i of this.#indexes()) {
734            const v = this.#valList[i];
735            const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
736            if (value === undefined)
737                continue;
738            if (fn(value, this.#keyList[i], this)) {
739                return this.get(this.#keyList[i], getOptions);
740            }
741        }
742    }
743    /**
744     * Call the supplied function on each item in the cache, in order from most
745     * recently used to least recently used.
746     *
747     * `fn` is called as `fn(value, key, cache)`.
748     *
749     * If `thisp` is provided, function will be called in the `this`-context of
750     * the provided object, or the cache if no `thisp` object is provided.
751     *
752     * Does not update age or recenty of use, or iterate over stale values.
753     */
754    forEach(fn, thisp = this) {
755        for (const i of this.#indexes()) {
756            const v = this.#valList[i];
757            const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
758            if (value === undefined)
759                continue;
760            fn.call(thisp, value, this.#keyList[i], this);
761        }
762    }
763    /**
764     * The same as {@link LRUCache.forEach} but items are iterated over in
765     * reverse order.  (ie, less recently used items are iterated over first.)
766     */
767    rforEach(fn, thisp = this) {
768        for (const i of this.#rindexes()) {
769            const v = this.#valList[i];
770            const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
771            if (value === undefined)
772                continue;
773            fn.call(thisp, value, this.#keyList[i], this);
774        }
775    }
776    /**
777     * Delete any stale entries. Returns true if anything was removed,
778     * false otherwise.
779     */
780    purgeStale() {
781        let deleted = false;
782        for (const i of this.#rindexes({ allowStale: true })) {
783            if (this.#isStale(i)) {
784                this.#delete(this.#keyList[i], 'expire');
785                deleted = true;
786            }
787        }
788        return deleted;
789    }
790    /**
791     * Get the extended info about a given entry, to get its value, size, and
792     * TTL info simultaneously. Returns `undefined` if the key is not present.
793     *
794     * Unlike {@link LRUCache#dump}, which is designed to be portable and survive
795     * serialization, the `start` value is always the current timestamp, and the
796     * `ttl` is a calculated remaining time to live (negative if expired).
797     *
798     * Always returns stale values, if their info is found in the cache, so be
799     * sure to check for expirations (ie, a negative {@link LRUCache.Entry#ttl})
800     * if relevant.
801     */
802    info(key) {
803        const i = this.#keyMap.get(key);
804        if (i === undefined)
805            return undefined;
806        const v = this.#valList[i];
807        /* c8 ignore start - this isn't tested for the info function,
808         * but it's the same logic as found in other places. */
809        const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
810        if (value === undefined)
811            return undefined;
812        /* c8 ignore end */
813        const entry = { value };
814        if (this.#ttls && this.#starts) {
815            const ttl = this.#ttls[i];
816            const start = this.#starts[i];
817            if (ttl && start) {
818                const remain = ttl - (this.#perf.now() - start);
819                entry.ttl = remain;
820                entry.start = Date.now();
821            }
822        }
823        if (this.#sizes) {
824            entry.size = this.#sizes[i];
825        }
826        return entry;
827    }
828    /**
829     * Return an array of [key, {@link LRUCache.Entry}] tuples which can be
830     * passed to {@link LRUCache#load}.
831     *
832     * The `start` fields are calculated relative to a portable `Date.now()`
833     * timestamp, even if `performance.now()` is available.
834     *
835     * Stale entries are always included in the `dump`, even if
836     * {@link LRUCache.OptionsBase.allowStale} is false.
837     *
838     * Note: this returns an actual array, not a generator, so it can be more
839     * easily passed around.
840     */
841    dump() {
842        const arr = [];
843        for (const i of this.#indexes({ allowStale: true })) {
844            const key = this.#keyList[i];
845            const v = this.#valList[i];
846            const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
847            if (value === undefined || key === undefined)
848                continue;
849            const entry = { value };
850            if (this.#ttls && this.#starts) {
851                entry.ttl = this.#ttls[i];
852                // always dump the start relative to a portable timestamp
853                // it's ok for this to be a bit slow, it's a rare operation.
854                const age = this.#perf.now() - this.#starts[i];
855                entry.start = Math.floor(Date.now() - age);
856            }
857            if (this.#sizes) {
858                entry.size = this.#sizes[i];
859            }
860            arr.unshift([key, entry]);
861        }
862        return arr;
863    }
864    /**
865     * Reset the cache and load in the items in entries in the order listed.
866     *
867     * The shape of the resulting cache may be different if the same options are
868     * not used in both caches.
869     *
870     * The `start` fields are assumed to be calculated relative to a portable
871     * `Date.now()` timestamp, even if `performance.now()` is available.
872     */
873    load(arr) {
874        this.clear();
875        for (const [key, entry] of arr) {
876            if (entry.start) {
877                // entry.start is a portable timestamp, but we may be using
878                // node's performance.now(), so calculate the offset, so that
879                // we get the intended remaining TTL, no matter how long it's
880                // been on ice.
881                //
882                // it's ok for this to be a bit slow, it's a rare operation.
883                const age = Date.now() - entry.start;
884                entry.start = this.#perf.now() - age;
885            }
886            this.set(key, entry.value, entry);
887        }
888    }
889    /**
890     * Add a value to the cache.
891     *
892     * Note: if `undefined` is specified as a value, this is an alias for
893     * {@link LRUCache#delete}
894     *
895     * Fields on the {@link LRUCache.SetOptions} options param will override
896     * their corresponding values in the constructor options for the scope
897     * of this single `set()` operation.
898     *
899     * If `start` is provided, then that will set the effective start
900     * time for the TTL calculation. Note that this must be a previous
901     * value of `performance.now()` if supported, or a previous value of
902     * `Date.now()` if not.
903     *
904     * Options object may also include `size`, which will prevent
905     * calling the `sizeCalculation` function and just use the specified
906     * number if it is a positive integer, and `noDisposeOnSet` which
907     * will prevent calling a `dispose` function in the case of
908     * overwrites.
909     *
910     * If the `size` (or return value of `sizeCalculation`) for a given
911     * entry is greater than `maxEntrySize`, then the item will not be
912     * added to the cache.
913     *
914     * Will update the recency of the entry.
915     *
916     * If the value is `undefined`, then this is an alias for
917     * `cache.delete(key)`. `undefined` is never stored in the cache.
918     */
919    set(k, v, setOptions = {}) {
920        if (v === undefined) {
921            this.delete(k);
922            return this;
923        }
924        const { ttl = this.ttl, start, noDisposeOnSet = this.noDisposeOnSet, sizeCalculation = this.sizeCalculation, status, } = setOptions;
925        let { noUpdateTTL = this.noUpdateTTL } = setOptions;
926        const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation);
927        // if the item doesn't fit, don't do anything
928        // NB: maxEntrySize set to maxSize by default
929        if (this.maxEntrySize && size > this.maxEntrySize) {
930            if (status) {
931                status.set = 'miss';
932                status.maxEntrySizeExceeded = true;
933            }
934            // have to delete, in case something is there already.
935            this.#delete(k, 'set');
936            return this;
937        }
938        let index = this.#size === 0 ? undefined : this.#keyMap.get(k);
939        if (index === undefined) {
940            // addition
941            index = (this.#size === 0 ? this.#tail
942                : this.#free.length !== 0 ? this.#free.pop()
943                    : this.#size === this.#max ? this.#evict(false)
944                        : this.#size);
945            this.#keyList[index] = k;
946            this.#valList[index] = v;
947            this.#keyMap.set(k, index);
948            this.#next[this.#tail] = index;
949            this.#prev[index] = this.#tail;
950            this.#tail = index;
951            this.#size++;
952            this.#addItemSize(index, size, status);
953            if (status)
954                status.set = 'add';
955            noUpdateTTL = false;
956            if (this.#hasOnInsert) {
957                this.#onInsert?.(v, k, 'add');
958            }
959        }
960        else {
961            // update
962            this.#moveToTail(index);
963            const oldVal = this.#valList[index];
964            if (v !== oldVal) {
965                if (this.#hasFetchMethod && this.#isBackgroundFetch(oldVal)) {
966                    oldVal.__abortController.abort(new Error('replaced'));
967                    const { __staleWhileFetching: s } = oldVal;
968                    if (s !== undefined && !noDisposeOnSet) {
969                        if (this.#hasDispose) {
970                            this.#dispose?.(s, k, 'set');
971                        }
972                        if (this.#hasDisposeAfter) {
973                            this.#disposed?.push([s, k, 'set']);
974                        }
975                    }
976                }
977                else if (!noDisposeOnSet) {
978                    if (this.#hasDispose) {
979                        this.#dispose?.(oldVal, k, 'set');
980                    }
981                    if (this.#hasDisposeAfter) {
982                        this.#disposed?.push([oldVal, k, 'set']);
983                    }
984                }
985                this.#removeItemSize(index);
986                this.#addItemSize(index, size, status);
987                this.#valList[index] = v;
988                if (status) {
989                    status.set = 'replace';
990                    const oldValue = oldVal && this.#isBackgroundFetch(oldVal) ?
991                        oldVal.__staleWhileFetching
992                        : oldVal;
993                    if (oldValue !== undefined)
994                        status.oldValue = oldValue;
995                }
996            }
997            else if (status) {
998                status.set = 'update';
999            }
1000            if (this.#hasOnInsert) {
1001                this.onInsert?.(v, k, v === oldVal ? 'update' : 'replace');
1002            }
1003        }
1004        if (ttl !== 0 && !this.#ttls) {
1005            this.#initializeTTLTracking();
1006        }
1007        if (this.#ttls) {
1008            if (!noUpdateTTL) {
1009                this.#setItemTTL(index, ttl, start);
1010            }
1011            if (status)
1012                this.#statusTTL(status, index);
1013        }
1014        if (!noDisposeOnSet && this.#hasDisposeAfter && this.#disposed) {
1015            const dt = this.#disposed;
1016            let task;
1017            while ((task = dt?.shift())) {
1018                this.#disposeAfter?.(...task);
1019            }
1020        }
1021        return this;
1022    }
1023    /**
1024     * Evict the least recently used item, returning its value or
1025     * `undefined` if cache is empty.
1026     */
1027    pop() {
1028        try {
1029            while (this.#size) {
1030                const val = this.#valList[this.#head];
1031                this.#evict(true);
1032                if (this.#isBackgroundFetch(val)) {
1033                    if (val.__staleWhileFetching) {
1034                        return val.__staleWhileFetching;
1035                    }
1036                }
1037                else if (val !== undefined) {
1038                    return val;
1039                }
1040            }
1041        }
1042        finally {
1043            if (this.#hasDisposeAfter && this.#disposed) {
1044                const dt = this.#disposed;
1045                let task;
1046                while ((task = dt?.shift())) {
1047                    this.#disposeAfter?.(...task);
1048                }
1049            }
1050        }
1051    }
1052    #evict(free) {
1053        const head = this.#head;
1054        const k = this.#keyList[head];
1055        const v = this.#valList[head];
1056        if (this.#hasFetchMethod && this.#isBackgroundFetch(v)) {
1057            v.__abortController.abort(new Error('evicted'));
1058        }
1059        else if (this.#hasDispose || this.#hasDisposeAfter) {
1060            if (this.#hasDispose) {
1061                this.#dispose?.(v, k, 'evict');
1062            }
1063            if (this.#hasDisposeAfter) {
1064                this.#disposed?.push([v, k, 'evict']);
1065            }
1066        }
1067        this.#removeItemSize(head);
1068        if (this.#autopurgeTimers?.[head]) {
1069            clearTimeout(this.#autopurgeTimers[head]);
1070            this.#autopurgeTimers[head] = undefined;
1071        }
1072        // if we aren't about to use the index, then null these out
1073        if (free) {
1074            this.#keyList[head] = undefined;
1075            this.#valList[head] = undefined;
1076            this.#free.push(head);
1077        }
1078        if (this.#size === 1) {
1079            this.#head = this.#tail = 0;
1080            this.#free.length = 0;
1081        }
1082        else {
1083            this.#head = this.#next[head];
1084        }
1085        this.#keyMap.delete(k);
1086        this.#size--;
1087        return head;
1088    }
1089    /**
1090     * Check if a key is in the cache, without updating the recency of use.
1091     * Will return false if the item is stale, even though it is technically
1092     * in the cache.
1093     *
1094     * Check if a key is in the cache, without updating the recency of
1095     * use. Age is updated if {@link LRUCache.OptionsBase.updateAgeOnHas} is set
1096     * to `true` in either the options or the constructor.
1097     *
1098     * Will return `false` if the item is stale, even though it is technically in
1099     * the cache. The difference can be determined (if it matters) by using a
1100     * `status` argument, and inspecting the `has` field.
1101     *
1102     * Will not update item age unless
1103     * {@link LRUCache.OptionsBase.updateAgeOnHas} is set.
1104     */
1105    has(k, hasOptions = {}) {
1106        const { updateAgeOnHas = this.updateAgeOnHas, status } = hasOptions;
1107        const index = this.#keyMap.get(k);
1108        if (index !== undefined) {
1109            const v = this.#valList[index];
1110            if (this.#isBackgroundFetch(v) &&
1111                v.__staleWhileFetching === undefined) {
1112                return false;
1113            }
1114            if (!this.#isStale(index)) {
1115                if (updateAgeOnHas) {
1116                    this.#updateItemAge(index);
1117                }
1118                if (status) {
1119                    status.has = 'hit';
1120                    this.#statusTTL(status, index);
1121                }
1122                return true;
1123            }
1124            else if (status) {
1125                status.has = 'stale';
1126                this.#statusTTL(status, index);
1127            }
1128        }
1129        else if (status) {
1130            status.has = 'miss';
1131        }
1132        return false;
1133    }
1134    /**
1135     * Like {@link LRUCache#get} but doesn't update recency or delete stale
1136     * items.
1137     *
1138     * Returns `undefined` if the item is stale, unless
1139     * {@link LRUCache.OptionsBase.allowStale} is set.
1140     */
1141    peek(k, peekOptions = {}) {
1142        const { allowStale = this.allowStale } = peekOptions;
1143        const index = this.#keyMap.get(k);
1144        if (index === undefined || (!allowStale && this.#isStale(index))) {
1145            return;
1146        }
1147        const v = this.#valList[index];
1148        // either stale and allowed, or forcing a refresh of non-stale value
1149        return this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
1150    }
1151    #backgroundFetch(k, index, options, context) {
1152        const v = index === undefined ? undefined : this.#valList[index];
1153        if (this.#isBackgroundFetch(v)) {
1154            return v;
1155        }
1156        const ac = new AC();
1157        const { signal } = options;
1158        // when/if our AC signals, then stop listening to theirs.
1159        signal?.addEventListener('abort', () => ac.abort(signal.reason), {
1160            signal: ac.signal,
1161        });
1162        const fetchOpts = {
1163            signal: ac.signal,
1164            options,
1165            context,
1166        };
1167        const cb = (v, updateCache = false) => {
1168            const { aborted } = ac.signal;
1169            const ignoreAbort = options.ignoreFetchAbort && v !== undefined;
1170            const proceed = options.ignoreFetchAbort ||
1171                !!(options.allowStaleOnFetchAbort && v !== undefined);
1172            if (options.status) {
1173                if (aborted && !updateCache) {
1174                    options.status.fetchAborted = true;
1175                    options.status.fetchError = ac.signal.reason;
1176                    if (ignoreAbort)
1177                        options.status.fetchAbortIgnored = true;
1178                }
1179                else {
1180                    options.status.fetchResolved = true;
1181                }
1182            }
1183            if (aborted && !ignoreAbort && !updateCache) {
1184                return fetchFail(ac.signal.reason, proceed);
1185            }
1186            // either we didn't abort, and are still here, or we did, and ignored
1187            const bf = p;
1188            // if nothing else has been written there but we're set to update the
1189            // cache and ignore the abort, or if it's still pending on this specific
1190            // background request, then write it to the cache.
1191            const vl = this.#valList[index];
1192            if (vl === p || (ignoreAbort && updateCache && vl === undefined)) {
1193                if (v === undefined) {
1194                    if (bf.__staleWhileFetching !== undefined) {
1195                        this.#valList[index] = bf.__staleWhileFetching;
1196                    }
1197                    else {
1198                        this.#delete(k, 'fetch');
1199                    }
1200                }

Showing the first 1,200 of 1591 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai