Team Ai
Datasetpublic

codekingpro/portable-devtools

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

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

codekingpro/portable-devtools · Team Ai