avancement planning

This commit is contained in:
2026-05-26 11:58:39 +02:00
parent 619a2b240a
commit 150b97cd2e
4892 changed files with 99214 additions and 429382 deletions
+108 -30
View File
@@ -1,9 +1,8 @@
/**
* @module LRUCache
*/
export type Perf = {
now: () => number;
};
import type { Perf } from './perf.js';
export type { Perf } from './perf.js';
declare const TYPE: unique symbol;
export type PosInt = number & {
[TYPE]: 'Positive Integer';
@@ -119,8 +118,16 @@ export declare namespace LRUCache {
*
* The `status` option should be a plain JavaScript object. The following
* fields will be set on it appropriately, depending on the situation.
*
* These objects are also the context objects passed to listeners on the
* `lru-cache:metrics` diagnostic channel, and the `lru-cache` tracing
* channels, in platforms that support them.
*/
interface Status<V> {
interface Status<K, V, FC = unknown> {
/**
* The operation being performed
*/
op?: 'get' | 'set' | 'memo' | 'fetch' | 'delete' | 'has' | 'peek';
/**
* The status of a set() operation.
*
@@ -129,7 +136,37 @@ export declare namespace LRUCache {
* - replace: the item was in the cache, and replaced
* - miss: the item was not added to the cache for some reason
*/
set?: 'add' | 'update' | 'replace' | 'miss';
set?: 'add' | 'update' | 'replace' | 'miss' | 'deleted';
/**
* The status of a delete() operation.
*/
delete?: LRUCache.DisposeReason;
/**
* The result of a peek() operation
*
* - hit: the item was found and returned
* - stale: the item is in the cache, but past its ttl and not returned
* - miss: item not in the cache
*/
peek?: 'hit' | 'miss' | 'stale';
/**
* The status of a memo() operation.
*
* - 'hit': the item was found in the cache and returned
* - 'miss': the `memoMethod` function was called
*/
memo?: 'hit' | 'miss';
/**
* The `context` option provided to a memo or fetch operation
*
* In practice, of course, this will be the same type as the `FC`
* fetch context param used to instantiate the LRUCache, but the
* convolutions of threading that through would get quite complicated,
* and preclude forcing/forbidding the passing of a `context` param
* where it is/isn't expected, which is more valuable for error
* prevention.
*/
context?: unknown;
/**
* the ttl stored for the item, or undefined if ttls are not used.
*/
@@ -160,8 +197,15 @@ export declare namespace LRUCache {
*/
maxEntrySizeExceeded?: true;
/**
* The old value, specified in the case of `set:'update'` or
* `set:'replace'`
* The key that was set or retrieved
*/
key?: K;
/**
* The value that was set
*/
value?: V;
/**
* The old value, specified in the case of `set:'replace'`
*/
oldValue?: V;
/**
@@ -187,6 +231,10 @@ export declare namespace LRUCache {
* {@link FetchOptions.forceRefresh} was specified.
*/
fetch?: 'get' | 'inflight' | 'miss' | 'hit' | 'stale' | 'refresh';
/**
* `forceRefresh` option was used for either a fetch or memo operation
*/
forceRefresh?: boolean;
/**
* The {@link OptionsBase.fetchMethod} was called
*/
@@ -208,7 +256,7 @@ export declare namespace LRUCache {
fetchAborted?: true;
/**
* The abort signal received was ignored, and the fetch was allowed to
* continue.
* continue in the background.
*/
fetchAbortIgnored?: true;
/**
@@ -224,15 +272,27 @@ export declare namespace LRUCache {
*
* - fetching: The item is currently being fetched. If a previous value
* is present and allowed, that will be returned.
* - stale: The item is in the cache, and is stale.
* - stale: The item is in the cache, and is stale. If it was returned,
* then the `returnedStale` flag will be set.
* - stale-fetching: The value is being fetched in the background, but is
* currently stale. If the stale value was returned, then the
* `returnedStale` flag will be set.
* - hit: the item is in the cache
* - miss: the item is not in the cache
*/
get?: 'stale' | 'hit' | 'miss';
get?: 'stale' | 'hit' | 'miss' | 'fetching' | 'stale-fetching';
/**
* A fetch or get operation returned a stale value.
*/
returnedStale?: true;
/**
* A tracingChannel trace was started for this operation
*/
trace?: boolean;
/**
* A reference to the cache instance associated with this operation
*/
cache?: LRUCache<K & {}, V & {}, FC>;
}
/**
* options which override the options set in the LRUCache constructor
@@ -250,7 +310,7 @@ export declare namespace LRUCache {
* the fetchMethod is called.
*/
interface FetcherFetchOptions<K, V, FC = unknown> extends Pick<OptionsBase<K, V, FC>, 'allowStale' | 'updateAgeOnGet' | 'noDeleteOnStaleGet' | 'sizeCalculation' | 'ttl' | 'noDisposeOnSet' | 'noUpdateTTL' | 'noDeleteOnFetchRejection' | 'allowStaleOnFetchRejection' | 'ignoreFetchAbort' | 'allowStaleOnFetchAbort'> {
status?: Status<V>;
status?: Status<K, V, FC>;
size?: Size;
}
/**
@@ -272,7 +332,7 @@ export declare namespace LRUCache {
*/
context?: FC;
signal?: AbortSignal;
status?: Status<V>;
status?: Status<K, V, FC>;
}
/**
* Options provided to {@link LRUCache#fetch} when the FC type is something
@@ -285,7 +345,7 @@ export declare namespace LRUCache {
* Options provided to {@link LRUCache#fetch} when the FC type is
* `undefined` or `void`
*/
interface FetchOptionsNoContext<K, V> extends FetchOptions<K, V, undefined> {
interface FetchOptionsNoContext<K, V, FC extends undefined | void = undefined> extends FetchOptions<K, V, FC> {
context?: undefined;
}
interface MemoOptions<K, V, FC = unknown> extends Pick<OptionsBase<K, V, FC>, 'allowStale' | 'updateAgeOnGet' | 'noDeleteOnStaleGet' | 'sizeCalculation' | 'ttl' | 'noDisposeOnSet' | 'noUpdateTTL' | 'noDeleteOnFetchRejection' | 'allowStaleOnFetchRejection' | 'ignoreFetchAbort' | 'allowStaleOnFetchAbort'> {
@@ -303,7 +363,7 @@ export declare namespace LRUCache {
* be required.
*/
context?: FC;
status?: Status<V>;
status?: Status<K, V, FC>;
}
/**
* Options provided to {@link LRUCache#memo} when the FC type is something
@@ -316,7 +376,7 @@ export declare namespace LRUCache {
* Options provided to {@link LRUCache#memo} when the FC type is
* `undefined` or `void`
*/
interface MemoOptionsNoContext<K, V> extends MemoOptions<K, V, undefined> {
interface MemoOptionsNoContext<K, V, FC extends undefined | void = undefined> extends MemoOptions<K, V, FC> {
context?: undefined;
}
/**
@@ -345,7 +405,7 @@ export declare namespace LRUCache {
* the memoMethod is called.
*/
interface MemoizerMemoOptions<K, V, FC = unknown> extends Pick<OptionsBase<K, V, FC>, 'allowStale' | 'updateAgeOnGet' | 'noDeleteOnStaleGet' | 'sizeCalculation' | 'ttl' | 'noDisposeOnSet' | 'noUpdateTTL'> {
status?: Status<V>;
status?: Status<K, V, FC>;
size?: Size;
start?: Milliseconds;
}
@@ -353,18 +413,19 @@ export declare namespace LRUCache {
* Options that may be passed to the {@link LRUCache#has} method.
*/
interface HasOptions<K, V, FC> extends Pick<OptionsBase<K, V, FC>, 'updateAgeOnHas'> {
status?: Status<V>;
status?: Status<K, V, FC>;
}
/**
* Options that may be passed to the {@link LRUCache#get} method.
*/
interface GetOptions<K, V, FC> extends Pick<OptionsBase<K, V, FC>, 'allowStale' | 'updateAgeOnGet' | 'noDeleteOnStaleGet'> {
status?: Status<V>;
status?: Status<K, V, FC>;
}
/**
* Options that may be passed to the {@link LRUCache#peek} method.
*/
interface PeekOptions<K, V, FC> extends Pick<OptionsBase<K, V, FC>, 'allowStale'> {
status?: Status<K, V, FC>;
}
/**
* Options that may be passed to the {@link LRUCache#set} method.
@@ -385,7 +446,7 @@ export declare namespace LRUCache {
* method is in use.
*/
start?: Milliseconds;
status?: Status<V>;
status?: Status<K, V, FC>;
}
/**
* The type signature for the {@link OptionsBase.fetchMethod} option.
@@ -638,6 +699,20 @@ export declare namespace LRUCache {
* though for most cases, only minimally.
*/
maxSize?: Size;
/**
* The effective size for background fetch promises.
*
* This has no effect unless `maxSize` and `sizeCalculation` are used,
* and a {@link LRUCache.OptionsBase.fetchMethod} is provided to
* support {@link LRUCache#fetch}.
*
* If a stale value is present in the cache, then the effective size of
* the background fetch is the size of the stale item it will eventually
* replace. If not, then this value is used as its effective size.
*
* @default 1
*/
backgroundFetchSize?: number;
/**
* The maximum allowed size for any single item in the cache.
*
@@ -940,6 +1015,8 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
* {@link LRUCache.OptionsBase.ignoreFetchAbort}
*/
ignoreFetchAbort: boolean;
/** {@link LRUCache.OptionsBase.backgroundFetchSize} */
backgroundFetchSize: number;
/**
* Do not call this method unless you need to inspect the
* inner workings of the cache. If anything returned by this
@@ -952,6 +1029,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
static unsafeExposeInternals<K extends {}, V extends {}, FC extends unknown = unknown>(c: LRUCache<K, V, FC>): {
starts: ZeroArray | undefined;
ttls: ZeroArray | undefined;
autopurgeTimers: (NodeJS.Timeout | undefined)[] | undefined;
sizes: ZeroArray | undefined;
keyMap: Map<K, number>;
keyList: (K | undefined)[];
@@ -961,8 +1039,8 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
readonly head: Index;
readonly tail: Index;
free: StackLike;
isBackgroundFetch: (p: any) => p is BackgroundFetch<V>;
backgroundFetch: (k: K, index: number | undefined, options: LRUCache.FetchOptions<K, V, FC>, context: any) => BackgroundFetch<V>;
isBackgroundFetch: (p: unknown) => p is BackgroundFetch<V>;
backgroundFetch: (k: K, index: number | undefined, options: LRUCache.FetchOptions<K, V, FC>, context: unknown) => BackgroundFetch<V>;
moveToTail: (index: number) => void;
indexes: (options?: {
allowStale: boolean;
@@ -1074,12 +1152,12 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
*
* Does not update age or recenty of use, or iterate over stale values.
*/
forEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any): void;
forEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => unknown, thisp?: unknown): void;
/**
* 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: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any): void;
rforEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => unknown, thisp?: unknown): void;
/**
* Delete any stale entries. Returns true if anything was removed,
* false otherwise.
@@ -1152,7 +1230,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
* If the value is `undefined`, then this is an alias for
* `cache.delete(key)`. `undefined` is never stored in the cache.
*/
set(k: K, v: V | BackgroundFetch<V> | undefined, setOptions?: LRUCache.SetOptions<K, V, FC>): this;
set(k: K, v: V | undefined, setOptions?: LRUCache.SetOptions<K, V, FC>): this;
/**
* Evict the least recently used item, returning its value or
* `undefined` if cache is empty.
@@ -1268,8 +1346,8 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
* the same time, because they're both waiting on the same
* underlying fetchMethod response.
*/
fetch(k: K, fetchOptions: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V> : LRUCache.FetchOptionsWithContext<K, V, FC>): Promise<undefined | V>;
fetch(k: unknown extends FC ? K : FC extends undefined | void ? K : never, fetchOptions?: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V> : never): Promise<undefined | V>;
fetch(k: K, fetchOptions: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V, FC> : LRUCache.FetchOptionsWithContext<K, V, FC>): Promise<undefined | V>;
fetch(k: unknown extends FC ? K : FC extends undefined | void ? K : never, fetchOptions?: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V, FC> : never): Promise<undefined | V>;
/**
* In some cases, `cache.fetch()` may resolve to `undefined`, either because
* a {@link LRUCache.OptionsBase#fetchMethod} was not provided (turning
@@ -1283,8 +1361,8 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
* cumbersome, but testing for `undefined` can also be annoying, this method
* can be used, which will reject if `this.fetch()` resolves to undefined.
*/
forceFetch(k: K, fetchOptions: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V> : LRUCache.FetchOptionsWithContext<K, V, FC>): Promise<V>;
forceFetch(k: unknown extends FC ? K : FC extends undefined | void ? K : never, fetchOptions?: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V> : never): Promise<V>;
forceFetch(k: K, fetchOptions: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V, FC> : LRUCache.FetchOptionsWithContext<K, V, FC>): Promise<V>;
forceFetch(k: unknown extends FC ? K : FC extends undefined | void ? K : never, fetchOptions?: unknown extends FC ? LRUCache.FetchOptions<K, V, FC> : FC extends undefined | void ? LRUCache.FetchOptionsNoContext<K, V, FC> : never): Promise<V>;
/**
* If the key is found in the cache, then this is equivalent to
* {@link LRUCache#get}. If not, in the cache, then calculate the value using
@@ -1299,8 +1377,8 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
* relevant in the course of fetching the data. It is only relevant for the
* course of a single `memo()` operation, and discarded afterwards.
*/
memo(k: K, memoOptions: unknown extends FC ? LRUCache.MemoOptions<K, V, FC> : FC extends undefined | void ? LRUCache.MemoOptionsNoContext<K, V> : LRUCache.MemoOptionsWithContext<K, V, FC>): V;
memo(k: unknown extends FC ? K : FC extends undefined | void ? K : never, memoOptions?: unknown extends FC ? LRUCache.MemoOptions<K, V, FC> : FC extends undefined | void ? LRUCache.MemoOptionsNoContext<K, V> : never): V;
memo(k: K, memoOptions: unknown extends FC ? LRUCache.MemoOptions<K, V, FC> : FC extends undefined | void ? LRUCache.MemoOptionsNoContext<K, V, FC> : LRUCache.MemoOptionsWithContext<K, V, FC>): V;
memo(k: unknown extends FC ? K : FC extends undefined | void ? K : never, memoOptions?: unknown extends FC ? LRUCache.MemoOptions<K, V, FC> : FC extends undefined | void ? LRUCache.MemoOptionsNoContext<K, V, FC> : never): V;
/**
* Return a value from the cache. Will update the recency of the cache
* entry found.
File diff suppressed because one or more lines are too long
+366 -208
View File
@@ -1,75 +1,27 @@
/**
* @module LRUCache
*/
const defaultPerf = (typeof performance === 'object' &&
performance &&
typeof performance.now === 'function') ?
performance
: Date;
import { metrics, tracing } from './diagnostics-channel.js';
import { defaultPerf } from './perf.js';
const hasSubscribers = () => metrics.hasSubscribers || tracing.hasSubscribers;
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 emitWarning = (msg, type, code, fn) => {
if (typeof PROCESS.emitWarning === 'function') {
PROCESS.emitWarning(msg, type, code, fn);
}
else {
//oxlint-disable-next-line no-console
console.error(`[${code}] ${type}: ${msg}`);
}
};
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 */
const isPosInt = (n) => !!n && n === Math.floor(n) && n > 0 && isFinite(n);
// 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,
@@ -78,6 +30,7 @@ const isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n);
// 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.
/* c8 ignore start */
const getUintArray = (max) => !isPosInt(max) ? null
: max <= Math.pow(2, 8) ? Uint8Array
: max <= Math.pow(2, 16) ? Uint16Array
@@ -92,7 +45,9 @@ class ZeroArray extends Array {
}
}
class Stack {
/* c8 ignore start - not sure why this is showing up uncovered?? */
heap;
/* c8 ignore stop */
length;
// private constructor
static #constructing = false;
@@ -212,6 +167,8 @@ export class LRUCache {
* {@link LRUCache.OptionsBase.ignoreFetchAbort}
*/
ignoreFetchAbort;
/** {@link LRUCache.OptionsBase.backgroundFetchSize} */
backgroundFetchSize;
// computed properties
#size;
#calculatedSize;
@@ -227,6 +184,7 @@ export class LRUCache {
#sizes;
#starts;
#ttls;
#autopurgeTimers;
#hasDispose;
#hasFetchMethod;
#hasDisposeAfter;
@@ -245,6 +203,7 @@ export class LRUCache {
// properties
starts: c.#starts,
ttls: c.#ttls,
autopurgeTimers: c.#autopurgeTimers,
sizes: c.#sizes,
keyMap: c.#keyMap,
keyList: c.#keyList,
@@ -320,7 +279,8 @@ export class LRUCache {
return this.#disposeAfter;
}
constructor(options) {
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;
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, backgroundFetchSize = 1, perf, } = options;
this.backgroundFetchSize = backgroundFetchSize;
if (perf !== undefined) {
if (typeof perf?.now !== 'function') {
throw new TypeError('perf option must have a now() method if specified');
@@ -346,20 +306,18 @@ export class LRUCache {
throw new TypeError('sizeCalculation set to non-function');
}
}
if (memoMethod !== undefined &&
typeof memoMethod !== 'function') {
if (memoMethod !== undefined && typeof memoMethod !== 'function') {
throw new TypeError('memoMethod must be a function if defined');
}
this.#memoMethod = memoMethod;
if (fetchMethod !== undefined &&
typeof fetchMethod !== 'function') {
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.#keyList = Array.from({ length: max }).fill(undefined);
this.#valList = Array.from({ length: max }).fill(undefined);
this.#next = new UintArray(max);
this.#prev = new UintArray(max);
this.#head = 0;
@@ -407,9 +365,7 @@ export class LRUCache {
this.updateAgeOnGet = !!updateAgeOnGet;
this.updateAgeOnHas = !!updateAgeOnHas;
this.ttlResolution =
isPosInt(ttlResolution) || ttlResolution === 0 ?
ttlResolution
: 1;
isPosInt(ttlResolution) || ttlResolution === 0 ? ttlResolution : 1;
this.ttlAutopurge = !!ttlAutopurge;
this.ttl = ttl || 0;
if (this.ttl) {
@@ -444,33 +400,56 @@ export class LRUCache {
const starts = new ZeroArray(this.#max);
this.#ttls = ttls;
this.#starts = starts;
const purgeTimers = this.ttlAutopurge ?
Array.from({
length: this.#max,
})
: undefined;
this.#autopurgeTimers = purgeTimers;
this.#setItemTTL = (index, ttl, start = this.#perf.now()) => {
starts[index] = ttl !== 0 ? start : 0;
ttls[index] = ttl;
if (ttl !== 0 && this.ttlAutopurge) {
const t = setTimeout(() => {
if (this.#isStale(index)) {
this.#delete(this.#keyList[index], 'expire');
}
}, ttl + 1);
// unref() not supported on all platforms
/* c8 ignore start */
if (t.unref) {
t.unref();
}
/* c8 ignore stop */
}
setPurgetTimer(index, ttl);
};
this.#updateItemAge = index => {
starts[index] = ttls[index] !== 0 ? this.#perf.now() : 0;
setPurgetTimer(index, ttls[index]);
};
// clear out the purge timer if we're setting TTL to 0, and
// previously had a ttl purge timer running, so it doesn't
// fire unnecessarily. Don't need to do this if we're not doing
// autopurge.
const setPurgetTimer = !this.ttlAutopurge ?
() => { }
: (index, ttl) => {
if (purgeTimers?.[index]) {
clearTimeout(purgeTimers[index]);
purgeTimers[index] = undefined;
}
if (ttl && ttl !== 0 && purgeTimers) {
const t = setTimeout(() => {
if (this.#isStale(index)) {
this.#delete(this.#keyList[index], 'expire');
}
}, ttl + 1);
// unref() not supported on all platforms
/* c8 ignore start */
if (t.unref) {
t.unref();
}
/* c8 ignore stop */
purgeTimers[index] = t;
}
};
this.#statusTTL = (status, index) => {
if (ttls[index]) {
const ttl = ttls[index];
const start = starts[index];
/* c8 ignore next */
if (!ttl || !start)
/* c8 ignore start */
if (!ttl || !start) {
return;
}
/* c8 ignore stop */
status.ttl = ttl;
status.start = start;
status.now = cachedNow || getNow();
@@ -529,12 +508,15 @@ export class LRUCache {
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)) {
// provisionally accept background fetches.
// actual value size will be checked when they return.
if (this.#isBackgroundFetch(v)) {
// NB: this cannot occur if v.__staleWhileFetching is set,
// because in that case, it would take on the size of the
// existing entry that it temporarily replaces.
return this.backgroundFetchSize;
}
if (sizeCalculation) {
if (typeof sizeCalculation !== 'function') {
throw new TypeError('sizeCalculation must be a function');
@@ -577,10 +559,7 @@ export class LRUCache {
};
*#indexes({ allowStale = this.allowStale } = {}) {
if (this.#size) {
for (let i = this.#tail; true;) {
if (!this.#isValidIndex(i)) {
break;
}
for (let i = this.#tail; this.#isValidIndex(i);) {
if (allowStale || !this.#isStale(i)) {
yield i;
}
@@ -595,10 +574,7 @@ export class LRUCache {
}
*#rindexes({ allowStale = this.allowStale } = {}) {
if (this.#size) {
for (let i = this.#head; true;) {
if (!this.#isValidIndex(i)) {
break;
}
for (let i = this.#head; this.#isValidIndex(i);) {
if (allowStale || !this.#isStale(i)) {
yield i;
}
@@ -650,8 +626,7 @@ export class LRUCache {
*keys() {
for (const i of this.#indexes()) {
const k = this.#keyList[i];
if (k !== undefined &&
!this.#isBackgroundFetch(this.#valList[i])) {
if (k !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
yield k;
}
}
@@ -665,8 +640,7 @@ export class LRUCache {
*rkeys() {
for (const i of this.#rindexes()) {
const k = this.#keyList[i];
if (k !== undefined &&
!this.#isBackgroundFetch(this.#valList[i])) {
if (k !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
yield k;
}
}
@@ -678,8 +652,7 @@ export class LRUCache {
*values() {
for (const i of this.#indexes()) {
const v = this.#valList[i];
if (v !== undefined &&
!this.#isBackgroundFetch(this.#valList[i])) {
if (v !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
yield this.#valList[i];
}
}
@@ -693,8 +666,7 @@ export class LRUCache {
*rvalues() {
for (const i of this.#rindexes()) {
const v = this.#valList[i];
if (v !== undefined &&
!this.#isBackgroundFetch(this.#valList[i])) {
if (v !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
yield this.#valList[i];
}
}
@@ -723,7 +695,7 @@ export class LRUCache {
if (value === undefined)
continue;
if (fn(value, this.#keyList[i], this)) {
return this.get(this.#keyList[i], getOptions);
return this.#get(this.#keyList[i], getOptions);
}
}
}
@@ -796,7 +768,7 @@ export class LRUCache {
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
return undefined;
/* c8 ignore end */
/* c8 ignore stop */
const entry = { value };
if (this.#ttls && this.#starts) {
const ttl = this.#ttls[i];
@@ -870,7 +842,7 @@ export class LRUCache {
const age = Date.now() - entry.start;
entry.start = this.#perf.now() - age;
}
this.set(key, entry.value, entry);
this.#set(key, entry.value, entry);
}
}
/**
@@ -904,22 +876,43 @@ export class LRUCache {
* `cache.delete(key)`. `undefined` is never stored in the cache.
*/
set(k, v, setOptions = {}) {
const { status = metrics.hasSubscribers ? {} : undefined } = setOptions;
setOptions.status = status;
if (status) {
status.op = 'set';
status.key = k;
if (v !== undefined)
status.value = v;
status.cache = this;
}
const result = this.#set(k, v, setOptions);
if (status && metrics.hasSubscribers) {
metrics.publish(status);
}
return result;
}
#set(k, v, setOptions, bf) {
const { ttl = this.ttl, start, noDisposeOnSet = this.noDisposeOnSet, sizeCalculation = this.sizeCalculation, status, } = setOptions;
const isBF = this.#isBackgroundFetch(v);
if (v === undefined) {
if (status)
status.set = 'deleted';
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 (status && !isBF)
status.value = v;
const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation, status);
// if the item doesn't fit, don't do anything
// NB: maxEntrySize set to maxSize by default
if (this.maxEntrySize && size > this.maxEntrySize) {
// have to delete, in case something is there already.
this.#delete(k, 'set');
if (status) {
status.set = 'miss';
status.maxEntrySizeExceeded = true;
}
// have to delete, in case something is there already.
this.#delete(k, 'set');
return this;
}
let index = this.#size === 0 ? undefined : this.#keyMap.get(k);
@@ -940,52 +933,68 @@ export class LRUCache {
if (status)
status.set = 'add';
noUpdateTTL = false;
if (this.#hasOnInsert) {
if (this.#hasOnInsert && !isBF) {
this.#onInsert?.(v, k, 'add');
}
}
else {
// update
// might be updating a background fetch!
this.#moveToTail(index);
const oldVal = this.#valList[index];
if (v !== oldVal) {
if (this.#hasFetchMethod && this.#isBackgroundFetch(oldVal)) {
oldVal.__abortController.abort(new Error('replaced'));
const { __staleWhileFetching: s } = oldVal;
if (s !== undefined && !noDisposeOnSet) {
if (!noDisposeOnSet) {
if (this.#isBackgroundFetch(oldVal)) {
if (oldVal !== bf) {
// setting over a background fetch, not merely resolving it.
oldVal.__abortController.abort(new Error('replaced'));
}
const { __staleWhileFetching: s } = oldVal;
if (s !== undefined && s !== v) {
if (this.#hasDispose) {
this.#dispose?.(s, k, 'set');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([s, k, 'set']);
}
}
}
else {
if (this.#hasDispose) {
this.#dispose?.(s, k, 'set');
this.#dispose?.(oldVal, k, 'set');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([s, k, 'set']);
this.#disposed?.push([oldVal, k, 'set']);
}
}
}
else if (!noDisposeOnSet) {
if (this.#hasDispose) {
this.#dispose?.(oldVal, k, 'set');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([oldVal, k, 'set']);
}
}
this.#removeItemSize(index);
this.#addItemSize(index, size, status);
this.#valList[index] = v;
if (status) {
status.set = 'replace';
if (!isBF) {
const oldValue = oldVal && this.#isBackgroundFetch(oldVal) ?
oldVal.__staleWhileFetching
: oldVal;
if (oldValue !== undefined)
status.oldValue = oldValue;
const setType = oldValue === undefined ? 'add'
: v !== oldValue ? 'replace'
: 'update';
if (status) {
status.set = setType;
if (oldValue !== undefined)
status.oldValue = oldValue;
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, setType);
}
}
}
else if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, v === oldVal ? 'update' : 'replace');
else if (!isBF) {
if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, 'update');
}
}
}
if (ttl !== 0 && !this.#ttls) {
@@ -1040,18 +1049,25 @@ export class LRUCache {
const head = this.#head;
const k = this.#keyList[head];
const v = this.#valList[head];
if (this.#hasFetchMethod && this.#isBackgroundFetch(v)) {
const isBF = this.#isBackgroundFetch(v);
if (isBF) {
v.__abortController.abort(new Error('evicted'));
}
else if (this.#hasDispose || this.#hasDisposeAfter) {
const oldValue = isBF ? v.__staleWhileFetching : v;
if ((this.#hasDispose || this.#hasDisposeAfter) &&
oldValue !== undefined) {
if (this.#hasDispose) {
this.#dispose?.(v, k, 'evict');
this.#dispose?.(oldValue, k, 'evict');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([v, k, 'evict']);
this.#disposed?.push([oldValue, k, 'evict']);
}
}
this.#removeItemSize(head);
if (this.#autopurgeTimers?.[head]) {
clearTimeout(this.#autopurgeTimers[head]);
this.#autopurgeTimers[head] = undefined;
}
// if we aren't about to use the index, then null these out
if (free) {
this.#keyList[head] = undefined;
@@ -1086,6 +1102,19 @@ export class LRUCache {
* {@link LRUCache.OptionsBase.updateAgeOnHas} is set.
*/
has(k, hasOptions = {}) {
const { status = metrics.hasSubscribers ? {} : undefined } = hasOptions;
hasOptions.status = status;
if (status) {
status.op = 'has';
status.key = k;
status.cache = this;
}
const result = this.#has(k, hasOptions);
if (metrics.hasSubscribers)
metrics.publish(status);
return result;
}
#has(k, hasOptions = {}) {
const { updateAgeOnHas = this.updateAgeOnHas, status } = hasOptions;
const index = this.#keyMap.get(k);
if (index !== undefined) {
@@ -1122,22 +1151,46 @@ export class LRUCache {
* {@link LRUCache.OptionsBase.allowStale} is set.
*/
peek(k, peekOptions = {}) {
const { allowStale = this.allowStale } = peekOptions;
const { status = hasSubscribers() ? {} : undefined } = peekOptions;
if (status) {
status.op = 'peek';
status.key = k;
status.cache = this;
}
peekOptions.status = status;
const result = this.#peek(k, peekOptions);
if (metrics.hasSubscribers) {
metrics.publish(status);
}
return result;
}
#peek(k, peekOptions) {
const { status, allowStale = this.allowStale } = peekOptions;
const index = this.#keyMap.get(k);
if (index === undefined ||
(!allowStale && this.#isStale(index))) {
return;
if (index === undefined || (!allowStale && this.#isStale(index))) {
if (status)
status.peek = index === undefined ? 'miss' : 'stale';
return undefined;
}
const v = this.#valList[index];
// either stale and allowed, or forcing a refresh of non-stale value
return this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
const val = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (status) {
if (val !== undefined) {
status.peek = 'hit';
status.value = val;
}
else {
status.peek = 'miss';
}
}
return val;
}
#backgroundFetch(k, index, options, context) {
const v = index === undefined ? undefined : this.#valList[index];
if (this.#isBackgroundFetch(v)) {
return v;
}
const ac = new AC();
const ac = new AbortController();
const { signal } = options;
// when/if our AC signals, then stop listening to theirs.
signal?.addEventListener('abort', () => ac.abort(signal.reason), {
@@ -1151,6 +1204,8 @@ export class LRUCache {
const cb = (v, updateCache = false) => {
const { aborted } = ac.signal;
const ignoreAbort = options.ignoreFetchAbort && v !== undefined;
const proceed = options.ignoreFetchAbort ||
!!(options.allowStaleOnFetchAbort && v !== undefined);
if (options.status) {
if (aborted && !updateCache) {
options.status.fetchAborted = true;
@@ -1163,7 +1218,7 @@ export class LRUCache {
}
}
if (aborted && !ignoreAbort && !updateCache) {
return fetchFail(ac.signal.reason);
return fetchFail(ac.signal.reason, proceed);
}
// either we didn't abort, and are still here, or we did, and ignored
const bf = p;
@@ -1171,7 +1226,7 @@ export class LRUCache {
// cache and ignore the abort, or if it's still pending on this specific
// background request, then write it to the cache.
const vl = this.#valList[index];
if (vl === p || ignoreAbort && updateCache && vl === undefined) {
if (vl === p || (vl === undefined && ignoreAbort && updateCache)) {
if (v === undefined) {
if (bf.__staleWhileFetching !== undefined) {
this.#valList[index] = bf.__staleWhileFetching;
@@ -1183,7 +1238,7 @@ export class LRUCache {
else {
if (options.status)
options.status.fetchUpdated = true;
this.set(k, v, fetchOpts.options);
this.#set(k, v, fetchOpts.options, bf);
}
}
return v;
@@ -1193,9 +1248,10 @@ export class LRUCache {
options.status.fetchRejected = true;
options.status.fetchError = er;
}
return fetchFail(er);
// do not pass go, do not collect $200
return fetchFail(er, false);
};
const fetchFail = (er) => {
const fetchFail = (er, proceed) => {
const { aborted } = ac.signal;
const allowStaleAborted = aborted && options.allowStaleOnFetchAbort;
const allowStale = allowStaleAborted || options.allowStaleOnFetchRejection;
@@ -1204,7 +1260,7 @@ export class LRUCache {
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;
const del = !noDelete || (!proceed && bf.__staleWhileFetching === undefined);
if (del) {
this.#delete(k, 'fetch');
}
@@ -1228,15 +1284,11 @@ export class LRUCache {
};
const pcall = (res, rej) => {
const fmp = this.#fetchMethod?.(k, v, fetchOpts);
if (fmp && fmp instanceof Promise) {
fmp.then(v => res(v === undefined ? undefined : v), rej);
}
// ignored, we go until we finish, regardless.
// defer check until we are actually aborting,
// so fetchMethod can override.
ac.signal.addEventListener('abort', () => {
if (!options.ignoreFetchAbort ||
options.allowStaleOnFetchAbort) {
if (!options.ignoreFetchAbort || options.allowStaleOnFetchAbort) {
res(undefined);
// when it eventually resolves, update the cache.
if (options.allowStaleOnFetchAbort) {
@@ -1244,6 +1296,12 @@ export class LRUCache {
}
}
});
if (fmp && fmp instanceof Promise) {
fmp.then(v => res(v === undefined ? undefined : v), rej);
}
else if (fmp !== undefined) {
res(fmp);
}
};
if (options.status)
options.status.fetchDispatched = true;
@@ -1255,10 +1313,14 @@ export class LRUCache {
});
if (index === undefined) {
// internal, don't expose status.
this.set(k, bf, { ...fetchOpts.options, status: undefined });
this.#set(k, bf, { ...fetchOpts.options, status: undefined });
index = this.#keyMap.get(k);
}
else {
// do not call #set, because we do not want to adjust its place
// in the lru queue, as it has not yet been "used". Also, we don't
// need to worry about evicting for size, because a background fetch
// over a stale value is treated as the same size as its stale value.
this.#valList[index] = bf;
}
return bf;
@@ -1270,9 +1332,23 @@ export class LRUCache {
return (!!b &&
b instanceof Promise &&
b.hasOwnProperty('__staleWhileFetching') &&
b.__abortController instanceof AC);
b.__abortController instanceof AbortController);
}
async fetch(k, fetchOptions = {}) {
fetch(k, fetchOptions = {}) {
const ths = tracing.hasSubscribers;
const { status = hasSubscribers() ? {} : undefined } = fetchOptions;
fetchOptions.status = status;
if (status && fetchOptions.context) {
status.context = fetchOptions.context;
}
const p = this.#fetch(k, fetchOptions);
if (status && ths) {
status.trace = true;
tracing.tracePromise(() => p, status).catch(() => { });
}
return p;
}
async #fetch(k, fetchOptions = {}) {
const {
// get options
allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet,
@@ -1280,10 +1356,17 @@ export class LRUCache {
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 (status) {
status.op = 'fetch';
status.key = k;
if (forceRefresh)
status.forceRefresh = true;
status.cache = this;
}
if (!this.#hasFetchMethod) {
if (status)
status.fetch = 'get';
return this.get(k, {
return this.#get(k, {
allowStale,
updateAgeOnGet,
noDeleteOnStaleGet,
@@ -1352,26 +1435,68 @@ export class LRUCache {
return staleVal ? p.__staleWhileFetching : (p.__returned = p);
}
}
async forceFetch(k, fetchOptions = {}) {
const v = await this.fetch(k, fetchOptions);
forceFetch(k, fetchOptions = {}) {
const ths = tracing.hasSubscribers;
const { status = hasSubscribers() ? {} : undefined } = fetchOptions;
fetchOptions.status = status;
if (status && fetchOptions.context) {
status.context = fetchOptions.context;
}
const p = this.#forceFetch(k, fetchOptions);
if (status && ths) {
status.trace = true;
tracing.tracePromise(() => p, status).catch(() => { });
}
return p;
}
async #forceFetch(k, fetchOptions = {}) {
const v = await this.#fetch(k, fetchOptions);
if (v === undefined)
throw new Error('fetch() returned undefined');
return v;
}
memo(k, memoOptions = {}) {
const { status = metrics.hasSubscribers ? {} : undefined } = memoOptions;
memoOptions.status = status;
if (status) {
status.op = 'memo';
status.key = k;
if (memoOptions.context) {
status.context = memoOptions.context;
}
status.cache = this;
}
const result = this.#memo(k, memoOptions);
if (status)
status.value = result;
if (metrics.hasSubscribers)
metrics.publish(status);
return result;
}
#memo(k, memoOptions = {}) {
const memoMethod = this.#memoMethod;
if (!memoMethod) {
throw new Error('no memoMethod provided to constructor');
}
const { context, forceRefresh, ...options } = memoOptions;
const v = this.get(k, options);
if (!forceRefresh && v !== undefined)
const { context, status, forceRefresh, ...options } = memoOptions;
if (status && forceRefresh)
status.forceRefresh = true;
const v = this.#get(k, options);
const refresh = forceRefresh || v === undefined;
if (status) {
status.memo = refresh ? 'miss' : 'hit';
if (!refresh)
status.value = v;
}
if (!refresh)
return v;
const vv = memoMethod(k, v, {
options,
context,
});
this.set(k, vv, options);
if (status)
status.value = vv;
this.#set(k, vv, options);
return vv;
}
/**
@@ -1381,55 +1506,71 @@ export class LRUCache {
* If the key is not found, get() will return `undefined`.
*/
get(k, getOptions = {}) {
const { status = metrics.hasSubscribers ? {} : undefined } = getOptions;
getOptions.status = status;
if (status) {
status.op = 'get';
status.key = k;
status.cache = this;
}
const result = this.#get(k, getOptions);
if (status) {
if (result !== undefined)
status.value = result;
if (metrics.hasSubscribers)
metrics.publish(status);
}
return result;
}
#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 (index === undefined) {
if (status)
this.#statusTTL(status, index);
if (this.#isStale(index)) {
status.get = 'miss';
return undefined;
}
const value = this.#valList[index];
const fetching = this.#isBackgroundFetch(value);
if (status)
this.#statusTTL(status, index);
if (this.#isStale(index)) {
// delete only if not an in-flight background fetch
if (!fetching) {
if (!noDeleteOnStaleGet) {
this.#delete(k, 'expire');
}
if (status)
status.get = 'stale';
// delete only if not an in-flight background fetch
if (!fetching) {
if (!noDeleteOnStaleGet) {
this.#delete(k, 'expire');
}
if (status && allowStale)
if (allowStale) {
if (status)
status.returnedStale = true;
return allowStale ? value : undefined;
}
else {
if (status &&
allowStale &&
value.__staleWhileFetching !== undefined) {
status.returnedStale = true;
}
return allowStale ? value.__staleWhileFetching : undefined;
return value;
}
return undefined;
}
else {
if (status)
status.get = 'stale-fetching';
if (allowStale && value.__staleWhileFetching !== undefined) {
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;
status.returnedStale = true;
return value.__staleWhileFetching;
}
return undefined;
}
else if (status) {
status.get = 'miss';
// not stale
if (status)
status.get = fetching ? 'fetching' : '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.
this.#moveToTail(index);
if (updateAgeOnGet) {
this.#updateItemAge(index);
}
return fetching ? value.__staleWhileFetching : value;
}
#connect(p, n) {
this.#prev[n] = p;
@@ -1464,10 +1605,22 @@ export class LRUCache {
return this.#delete(k, 'delete');
}
#delete(k, reason) {
if (metrics.hasSubscribers) {
metrics.publish({
op: 'delete',
delete: reason,
key: k,
cache: this,
});
}
let deleted = false;
if (this.#size !== 0) {
const index = this.#keyMap.get(k);
if (index !== undefined) {
if (this.#autopurgeTimers?.[index]) {
clearTimeout(this.#autopurgeTimers?.[index]);
this.#autopurgeTimers[index] = undefined;
}
deleted = true;
if (this.#size === 1) {
this.#clear(reason);
@@ -1538,11 +1691,16 @@ export class LRUCache {
}
}
this.#keyMap.clear();
this.#valList.fill(undefined);
void this.#valList.fill(undefined);
this.#keyList.fill(undefined);
if (this.#ttls && this.#starts) {
this.#ttls.fill(0);
this.#starts.fill(0);
for (const t of this.#autopurgeTimers ?? []) {
if (t !== undefined)
clearTimeout(t);
}
this.#autopurgeTimers?.fill(undefined);
}
if (this.#sizes) {
this.#sizes.fill(0);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long