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