v1.0 with SW PWA enabled

This commit is contained in:
Blomios
2026-01-01 17:40:53 +01:00
parent 1c0e22aac1
commit 3c8bebb2ad
29775 changed files with 2197201 additions and 119080 deletions

View File

@ -1,6 +1,3 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import { LRUCache } from 'lru-cache';
import { posix, win32 } from 'node:path';
import { Minipass } from 'minipass';
@ -193,6 +190,8 @@ export declare abstract class PathBase implements Dirent {
/**
* Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively,
* this property refers to the *parent* path, not the path object itself.
*
* @deprecated
*/
get path(): string;
/**
@ -622,7 +621,7 @@ export declare abstract class PathScurryBase {
*
* @internal
*/
constructor(cwd: string | URL | undefined, pathImpl: typeof win32 | typeof posix, sep: string | RegExp, { nocase, childrenCacheSize, fs, }?: PathScurryOpts);
constructor(cwd: (URL | string) | undefined, pathImpl: typeof win32 | typeof posix, sep: string | RegExp, { nocase, childrenCacheSize, fs, }?: PathScurryOpts);
/**
* Get the depth of a provided path, string, or the cwd
*/

File diff suppressed because one or more lines are too long

View File

@ -99,7 +99,7 @@ const entToType = (s) => s.isFile() ? IFREG
: s.isFIFO() ? IFIFO
: UNKNOWN;
// normalize unicode path names
const normalizeCache = new Map();
const normalizeCache = new lru_cache_1.LRUCache({ max: 2 ** 12 });
const normalize = (s) => {
const c = normalizeCache.get(s);
if (c)
@ -108,7 +108,7 @@ const normalize = (s) => {
normalizeCache.set(s, n);
return n;
};
const normalizeNocaseCache = new Map();
const normalizeNocaseCache = new lru_cache_1.LRUCache({ max: 2 ** 12 });
const normalizeNocase = (s) => {
const c = normalizeNocaseCache.get(s);
if (c)
@ -299,13 +299,17 @@ class PathBase {
get parentPath() {
return (this.parent || this).fullpath();
}
/* c8 ignore start */
/**
* Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively,
* this property refers to the *parent* path, not the path object itself.
*
* @deprecated
*/
get path() {
return this.parentPath;
}
/* c8 ignore stop */
/**
* Do not create new Path objects directly. They should always be accessed
* via the PathScurry class or other methods on the Path class.

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,3 @@
/// <reference types="node" resolution-mode="require"/>
/// <reference types="node" resolution-mode="require"/>
/// <reference types="node" resolution-mode="require"/>
import { LRUCache } from 'lru-cache';
import { posix, win32 } from 'node:path';
import { Minipass } from 'minipass';
@ -193,6 +190,8 @@ export declare abstract class PathBase implements Dirent {
/**
* Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively,
* this property refers to the *parent* path, not the path object itself.
*
* @deprecated
*/
get path(): string;
/**
@ -622,7 +621,7 @@ export declare abstract class PathScurryBase {
*
* @internal
*/
constructor(cwd: string | URL | undefined, pathImpl: typeof win32 | typeof posix, sep: string | RegExp, { nocase, childrenCacheSize, fs, }?: PathScurryOpts);
constructor(cwd: (URL | string) | undefined, pathImpl: typeof win32 | typeof posix, sep: string | RegExp, { nocase, childrenCacheSize, fs, }?: PathScurryOpts);
/**
* Get the depth of a provided path, string, or the cwd
*/

File diff suppressed because one or more lines are too long

View File

@ -73,7 +73,7 @@ const entToType = (s) => s.isFile() ? IFREG
: s.isFIFO() ? IFIFO
: UNKNOWN;
// normalize unicode path names
const normalizeCache = new Map();
const normalizeCache = new LRUCache({ max: 2 ** 12 });
const normalize = (s) => {
const c = normalizeCache.get(s);
if (c)
@ -82,7 +82,7 @@ const normalize = (s) => {
normalizeCache.set(s, n);
return n;
};
const normalizeNocaseCache = new Map();
const normalizeNocaseCache = new LRUCache({ max: 2 ** 12 });
const normalizeNocase = (s) => {
const c = normalizeNocaseCache.get(s);
if (c)
@ -271,13 +271,17 @@ export class PathBase {
get parentPath() {
return (this.parent || this).fullpath();
}
/* c8 ignore start */
/**
* Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively,
* this property refers to the *parent* path, not the path object itself.
*
* @deprecated
*/
get path() {
return this.parentPath;
}
/* c8 ignore stop */
/**
* Do not create new Path objects directly. They should always be accessed
* via the PathScurry class or other methods on the Path class.

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
The ISC License
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -0,0 +1,55 @@
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***

View File

@ -57,10 +57,17 @@ const options = {
// for use when you need to clean up something when objects
// are evicted from the cache
dispose: (value, key) => {
dispose: (value, key, reason) => {
freeFromMemoryOrWhatever(value)
},
// for use when you need to know that an item is being inserted
// note that this does NOT allow you to prevent the insertion,
// it just allows you to know about it.
onInsert: (value, key, reason) => {
logInsertionOrWhatever(key, value)
},
// how long to live in ms
ttl: 1000 * 60 * 5,
@ -72,11 +79,7 @@ const options = {
// async method to use for cache.fetch(), for
// stale-while-revalidate type of behavior
fetchMethod: async (
key,
staleValue,
{ options, signal, context }
) => {},
fetchMethod: async (key, staleValue, { options, signal, context }) => {},
}
const cache = new LRUCache(options)
@ -154,7 +157,7 @@ const cache = {
}
cache.timers.set(
k,
setTimeout(() => cache.delete(k), ttl)
setTimeout(() => cache.delete(k), ttl),
)
cache.data.set(k, v)
},

View File

@ -1,6 +1,9 @@
/**
* @module LRUCache
*/
export type Perf = {
now: () => number;
};
declare const TYPE: unique symbol;
export type PosInt = number & {
[TYPE]: 'Positive Integer';
@ -75,6 +78,20 @@ export declare namespace LRUCache {
* {@link OptionsBase.disposeAfter} options.
*/
type Disposer<K, V> = (value: V, key: K, reason: DisposeReason) => void;
/**
* The reason why an item was added to the cache, passed
* to the {@link Inserter} methods.
*
* - `add`: the item was not found in the cache, and was added
* - `update`: the item was in the cache, with the same value provided
* - `replace`: the item was in the cache, and replaced
*/
type InsertReason = 'add' | 'update' | 'replace';
/**
* A method called upon item insertion, passed as the
* {@link OptionsBase.insert}
*/
type Inserter<K, V> = (value: V, key: K, reason: InsertReason) => void;
/**
* A function that returns the effective calculated size
* of an entry in the cache.
@ -320,7 +337,7 @@ export declare namespace LRUCache {
*
* This is the union of {@link GetOptions} and {@link SetOptions}, plus
* {@link MemoOptions.forceRefresh}, and
* {@link MemoerOptions.context}
* {@link MemoOptions.context}
*
* Any of these may be modified in the {@link OptionsBase.memoMethod}
* function, but the {@link GetOptions} fields will of course have no
@ -555,6 +572,17 @@ export declare namespace LRUCache {
* `cache.clear()`, or `cache.set(key, undefined)`.
*/
dispose?: Disposer<K, V>;
/**
* Function that is called when new items are inserted into the cache,
* as `onInsert(value, key, reason)`.
*
* This can be useful if you need to perform actions when an item is
* added, such as logging or tracking insertions.
*
* Unlike some other options, this may _not_ be overridden by passing
* an option to `set()`, for performance and consistency reasons.
*/
onInsert?: Inserter<K, V>;
/**
* The same as {@link OptionsBase.dispose}, but called *after* the entry
* is completely removed and the cache is once again in a clean state.
@ -796,6 +824,15 @@ export declare namespace LRUCache {
* call to {@link LRUCache#fetch}.
*/
ignoreFetchAbort?: boolean;
/**
* In some cases, you may want to swap out the performance/Date object
* used for TTL tracking. This should almost certainly NOT be done in
* production environments!
*
* This value defaults to `global.performance` if it has a `now()` method,
* or the `global.Date` object otherwise.
*/
perf?: Perf;
}
interface OptionsMaxLimit<K, V, FC> extends OptionsBase<K, V, FC> {
max: Count;
@ -837,8 +874,12 @@ export declare namespace LRUCache {
*
* Changing any of these will alter the defaults for subsequent method calls.
*/
export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implements Map<K, V> {
export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
#private;
/**
* {@link LRUCache.OptionsBase.perf}
*/
get perf(): Perf;
/**
* {@link LRUCache.OptionsBase.ttl}
*/
@ -911,6 +952,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
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)[];
@ -920,7 +962,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
readonly head: Index;
readonly tail: Index;
free: StackLike;
isBackgroundFetch: (p: any) => boolean;
isBackgroundFetch: (p: any) => p is BackgroundFetch<V>;
backgroundFetch: (k: K, index: number | undefined, options: LRUCache.FetchOptions<K, V, FC>, context: any) => BackgroundFetch<V>;
moveToTail: (index: number) => void;
indexes: (options?: {
@ -956,6 +998,10 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* {@link LRUCache.OptionsBase.dispose} (read-only)
*/
get dispose(): LRUCache.Disposer<K, V> | undefined;
/**
* {@link LRUCache.OptionsBase.onInsert} (read-only)
*/
get onInsert(): LRUCache.Inserter<K, V> | undefined;
/**
* {@link LRUCache.OptionsBase.disposeAfter} (read-only)
*/
@ -977,7 +1023,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* Return a generator yielding `[key, value]` pairs,
* in order from least recently used to most recently used.
*/
rentries(): Generator<(K | V | BackgroundFetch<V> | undefined)[], void, unknown>;
rentries(): Generator<(K | V)[], void, unknown>;
/**
* Return a generator yielding the keys in the cache,
* in order from most recently used to least recently used.
@ -1001,7 +1047,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* Return a generator yielding the values in the cache,
* in order from least recently used to most recently used.
*/
rvalues(): Generator<V | BackgroundFetch<V> | undefined, void, unknown>;
rvalues(): Generator<V | undefined, void, unknown>;
/**
* Iterating over the cache itself yields the same results as
* {@link LRUCache.entries}
@ -1055,7 +1101,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
info(key: K): LRUCache.Entry<V> | undefined;
/**
* Return an array of [key, {@link LRUCache.Entry}] tuples which can be
* passed to {@link LRLUCache#load}.
* passed to {@link LRUCache#load}.
*
* The `start` fields are calculated relative to a portable `Date.now()`
* timestamp, even if `performance.now()` is available.
@ -1231,7 +1277,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* `cache.fetch(k)` into just an async wrapper around `cache.get(k)`) or
* because `ignoreFetchAbort` was specified (either to the constructor or
* in the {@link LRUCache.FetchOptions}). Also, the
* {@link OptionsBase.fetchMethod} may return `undefined` or `void`, making
* {@link LRUCache.OptionsBase.fetchMethod} may return `undefined` or `void`, making
* the test even more complicated.
*
* Because inferring the cases where `undefined` might be returned are so

File diff suppressed because one or more lines are too long

View File

@ -4,18 +4,20 @@
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LRUCache = void 0;
const perf = typeof performance === 'object' &&
const defaultPerf = (typeof performance === 'object' &&
performance &&
typeof performance.now === 'function'
? performance
typeof performance.now === 'function') ?
performance
: Date;
const warned = new Set();
/* c8 ignore start */
const PROCESS = (typeof process === 'object' && !!process ? process : {});
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)
typeof PROCESS.emitWarning === 'function' ?
PROCESS.emitWarning(msg, type, code, fn)
: console.error(`[${code}] ${type}: ${msg}`);
};
let AC = globalThis.AbortController;
@ -79,16 +81,11 @@ 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.
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
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 {
@ -147,9 +144,17 @@ class LRUCache {
#max;
#maxSize;
#dispose;
#onInsert;
#disposeAfter;
#fetchMethod;
#memoMethod;
#perf;
/**
* {@link LRUCache.OptionsBase.perf}
*/
get perf() {
return this.#perf;
}
/**
* {@link LRUCache.OptionsBase.ttl}
*/
@ -225,9 +230,11 @@ class LRUCache {
#sizes;
#starts;
#ttls;
#autopurgeTimers;
#hasDispose;
#hasFetchMethod;
#hasDisposeAfter;
#hasOnInsert;
/**
* Do not call this method unless you need to inspect the
* inner workings of the cache. If anything returned by this
@ -242,6 +249,7 @@ class LRUCache {
// properties
starts: c.#starts,
ttls: c.#ttls,
autopurgeTimers: c.#autopurgeTimers,
sizes: c.#sizes,
keyMap: c.#keyMap,
keyList: c.#keyList,
@ -304,6 +312,12 @@ class LRUCache {
get dispose() {
return this.#dispose;
}
/**
* {@link LRUCache.OptionsBase.onInsert} (read-only)
*/
get onInsert() {
return this.#onInsert;
}
/**
* {@link LRUCache.OptionsBase.disposeAfter} (read-only)
*/
@ -311,7 +325,13 @@ class LRUCache {
return this.#disposeAfter;
}
constructor(options) {
const { max = 0, ttl, ttlResolution = 1, ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0, maxEntrySize = 0, sizeCalculation, fetchMethod, memoMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, } = options;
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;
if (perf !== undefined) {
if (typeof perf?.now !== 'function') {
throw new TypeError('perf option must have a now() method if specified');
}
}
this.#perf = perf ?? defaultPerf;
if (max !== 0 && !isPosInt(max)) {
throw new TypeError('max option must be a nonnegative integer');
}
@ -331,13 +351,11 @@ 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;
@ -355,6 +373,9 @@ class LRUCache {
if (typeof dispose === 'function') {
this.#dispose = dispose;
}
if (typeof onInsert === 'function') {
this.#onInsert = onInsert;
}
if (typeof disposeAfter === 'function') {
this.#disposeAfter = disposeAfter;
this.#disposed = [];
@ -364,6 +385,7 @@ class LRUCache {
this.#disposed = undefined;
}
this.#hasDispose = !!this.#dispose;
this.#hasOnInsert = !!this.#onInsert;
this.#hasDisposeAfter = !!this.#disposeAfter;
this.noDisposeOnSet = !!noDisposeOnSet;
this.noUpdateTTL = !!noUpdateTTL;
@ -388,9 +410,7 @@ 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) {
@ -425,10 +445,21 @@ class LRUCache {
const starts = new ZeroArray(this.#max);
this.#ttls = ttls;
this.#starts = starts;
this.#setItemTTL = (index, ttl, start = perf.now()) => {
const purgeTimers = this.ttlAutopurge ?
new Array(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) {
// 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.
if (purgeTimers?.[index]) {
clearTimeout(purgeTimers[index]);
purgeTimers[index] = undefined;
}
if (ttl !== 0 && purgeTimers) {
const t = setTimeout(() => {
if (this.#isStale(index)) {
this.#delete(this.#keyList[index], 'expire');
@ -440,10 +471,11 @@ class LRUCache {
t.unref();
}
/* c8 ignore stop */
purgeTimers[index] = t;
}
};
this.#updateItemAge = index => {
starts[index] = ttls[index] !== 0 ? perf.now() : 0;
starts[index] = ttls[index] !== 0 ? this.#perf.now() : 0;
};
this.#statusTTL = (status, index) => {
if (ttls[index]) {
@ -463,7 +495,7 @@ class LRUCache {
// that costly call repeatedly.
let cachedNow = 0;
const getNow = () => {
const n = perf.now();
const n = this.#perf.now();
if (this.ttlResolution > 0) {
cachedNow = n;
const t = setTimeout(() => (cachedNow = 0), this.ttlResolution);
@ -631,8 +663,7 @@ 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;
}
}
@ -646,8 +677,7 @@ 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;
}
}
@ -659,8 +689,7 @@ 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];
}
}
@ -674,8 +703,7 @@ 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];
}
}
@ -700,9 +728,7 @@ class LRUCache {
find(fn, getOptions = {}) {
for (const i of this.#indexes()) {
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
continue;
if (fn(value, this.#keyList[i], this)) {
@ -724,9 +750,7 @@ class LRUCache {
forEach(fn, thisp = this) {
for (const i of this.#indexes()) {
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
continue;
fn.call(thisp, value, this.#keyList[i], this);
@ -739,9 +763,7 @@ class LRUCache {
rforEach(fn, thisp = this) {
for (const i of this.#rindexes()) {
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
continue;
fn.call(thisp, value, this.#keyList[i], this);
@ -778,17 +800,18 @@ class LRUCache {
if (i === undefined)
return undefined;
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
/* c8 ignore start - this isn't tested for the info function,
* but it's the same logic as found in other places. */
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
return undefined;
/* c8 ignore end */
const entry = { value };
if (this.#ttls && this.#starts) {
const ttl = this.#ttls[i];
const start = this.#starts[i];
if (ttl && start) {
const remain = ttl - (perf.now() - start);
const remain = ttl - (this.#perf.now() - start);
entry.ttl = remain;
entry.start = Date.now();
}
@ -800,7 +823,7 @@ class LRUCache {
}
/**
* Return an array of [key, {@link LRUCache.Entry}] tuples which can be
* passed to {@link LRLUCache#load}.
* passed to {@link LRUCache#load}.
*
* The `start` fields are calculated relative to a portable `Date.now()`
* timestamp, even if `performance.now()` is available.
@ -816,9 +839,7 @@ class LRUCache {
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;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined || key === undefined)
continue;
const entry = { value };
@ -826,7 +847,7 @@ class LRUCache {
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];
const age = this.#perf.now() - this.#starts[i];
entry.start = Math.floor(Date.now() - age);
}
if (this.#sizes) {
@ -856,7 +877,7 @@ class LRUCache {
//
// 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;
entry.start = this.#perf.now() - age;
}
this.set(key, entry.value, entry);
}
@ -913,12 +934,9 @@ class LRUCache {
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)
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;
@ -931,6 +949,9 @@ class LRUCache {
if (status)
status.set = 'add';
noUpdateTTL = false;
if (this.#hasOnInsert) {
this.#onInsert?.(v, k, 'add');
}
}
else {
// update
@ -962,8 +983,8 @@ class LRUCache {
this.#valList[index] = v;
if (status) {
status.set = 'replace';
const oldValue = oldVal && this.#isBackgroundFetch(oldVal)
? oldVal.__staleWhileFetching
const oldValue = oldVal && this.#isBackgroundFetch(oldVal) ?
oldVal.__staleWhileFetching
: oldVal;
if (oldValue !== undefined)
status.oldValue = oldValue;
@ -972,6 +993,9 @@ class LRUCache {
else if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, v === oldVal ? 'update' : 'replace');
}
}
if (ttl !== 0 && !this.#ttls) {
this.#initializeTTLTracking();
@ -1037,6 +1061,10 @@ class LRUCache {
}
}
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;
@ -1109,8 +1137,7 @@ class LRUCache {
peek(k, peekOptions = {}) {
const { allowStale = this.allowStale } = peekOptions;
const index = this.#keyMap.get(k);
if (index === undefined ||
(!allowStale && this.#isStale(index))) {
if (index === undefined || (!allowStale && this.#isStale(index))) {
return;
}
const v = this.#valList[index];
@ -1152,9 +1179,13 @@ class LRUCache {
}
// either we didn't abort, and are still here, or we did, and ignored
const bf = p;
if (this.#valList[index] === p) {
// if nothing else has been written there but we're set to update the
// 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 (v === undefined) {
if (bf.__staleWhileFetching) {
if (bf.__staleWhileFetching !== undefined) {
this.#valList[index] = bf.__staleWhileFetching;
}
else {
@ -1216,8 +1247,7 @@ class LRUCache {
// 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) {
@ -1449,6 +1479,10 @@ class LRUCache {
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);
@ -1524,6 +1558,11 @@ class LRUCache {
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

View File

@ -1,6 +1,9 @@
/**
* @module LRUCache
*/
export type Perf = {
now: () => number;
};
declare const TYPE: unique symbol;
export type PosInt = number & {
[TYPE]: 'Positive Integer';
@ -75,6 +78,20 @@ export declare namespace LRUCache {
* {@link OptionsBase.disposeAfter} options.
*/
type Disposer<K, V> = (value: V, key: K, reason: DisposeReason) => void;
/**
* The reason why an item was added to the cache, passed
* to the {@link Inserter} methods.
*
* - `add`: the item was not found in the cache, and was added
* - `update`: the item was in the cache, with the same value provided
* - `replace`: the item was in the cache, and replaced
*/
type InsertReason = 'add' | 'update' | 'replace';
/**
* A method called upon item insertion, passed as the
* {@link OptionsBase.insert}
*/
type Inserter<K, V> = (value: V, key: K, reason: InsertReason) => void;
/**
* A function that returns the effective calculated size
* of an entry in the cache.
@ -320,7 +337,7 @@ export declare namespace LRUCache {
*
* This is the union of {@link GetOptions} and {@link SetOptions}, plus
* {@link MemoOptions.forceRefresh}, and
* {@link MemoerOptions.context}
* {@link MemoOptions.context}
*
* Any of these may be modified in the {@link OptionsBase.memoMethod}
* function, but the {@link GetOptions} fields will of course have no
@ -555,6 +572,17 @@ export declare namespace LRUCache {
* `cache.clear()`, or `cache.set(key, undefined)`.
*/
dispose?: Disposer<K, V>;
/**
* Function that is called when new items are inserted into the cache,
* as `onInsert(value, key, reason)`.
*
* This can be useful if you need to perform actions when an item is
* added, such as logging or tracking insertions.
*
* Unlike some other options, this may _not_ be overridden by passing
* an option to `set()`, for performance and consistency reasons.
*/
onInsert?: Inserter<K, V>;
/**
* The same as {@link OptionsBase.dispose}, but called *after* the entry
* is completely removed and the cache is once again in a clean state.
@ -796,6 +824,15 @@ export declare namespace LRUCache {
* call to {@link LRUCache#fetch}.
*/
ignoreFetchAbort?: boolean;
/**
* In some cases, you may want to swap out the performance/Date object
* used for TTL tracking. This should almost certainly NOT be done in
* production environments!
*
* This value defaults to `global.performance` if it has a `now()` method,
* or the `global.Date` object otherwise.
*/
perf?: Perf;
}
interface OptionsMaxLimit<K, V, FC> extends OptionsBase<K, V, FC> {
max: Count;
@ -837,8 +874,12 @@ export declare namespace LRUCache {
*
* Changing any of these will alter the defaults for subsequent method calls.
*/
export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implements Map<K, V> {
export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
#private;
/**
* {@link LRUCache.OptionsBase.perf}
*/
get perf(): Perf;
/**
* {@link LRUCache.OptionsBase.ttl}
*/
@ -911,6 +952,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
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)[];
@ -920,7 +962,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
readonly head: Index;
readonly tail: Index;
free: StackLike;
isBackgroundFetch: (p: any) => boolean;
isBackgroundFetch: (p: any) => p is BackgroundFetch<V>;
backgroundFetch: (k: K, index: number | undefined, options: LRUCache.FetchOptions<K, V, FC>, context: any) => BackgroundFetch<V>;
moveToTail: (index: number) => void;
indexes: (options?: {
@ -956,6 +998,10 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* {@link LRUCache.OptionsBase.dispose} (read-only)
*/
get dispose(): LRUCache.Disposer<K, V> | undefined;
/**
* {@link LRUCache.OptionsBase.onInsert} (read-only)
*/
get onInsert(): LRUCache.Inserter<K, V> | undefined;
/**
* {@link LRUCache.OptionsBase.disposeAfter} (read-only)
*/
@ -977,7 +1023,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* Return a generator yielding `[key, value]` pairs,
* in order from least recently used to most recently used.
*/
rentries(): Generator<(K | V | BackgroundFetch<V> | undefined)[], void, unknown>;
rentries(): Generator<(K | V)[], void, unknown>;
/**
* Return a generator yielding the keys in the cache,
* in order from most recently used to least recently used.
@ -1001,7 +1047,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* Return a generator yielding the values in the cache,
* in order from least recently used to most recently used.
*/
rvalues(): Generator<V | BackgroundFetch<V> | undefined, void, unknown>;
rvalues(): Generator<V | undefined, void, unknown>;
/**
* Iterating over the cache itself yields the same results as
* {@link LRUCache.entries}
@ -1055,7 +1101,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
info(key: K): LRUCache.Entry<V> | undefined;
/**
* Return an array of [key, {@link LRUCache.Entry}] tuples which can be
* passed to {@link LRLUCache#load}.
* passed to {@link LRUCache#load}.
*
* The `start` fields are calculated relative to a portable `Date.now()`
* timestamp, even if `performance.now()` is available.
@ -1231,7 +1277,7 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> implemen
* `cache.fetch(k)` into just an async wrapper around `cache.get(k)`) or
* because `ignoreFetchAbort` was specified (either to the constructor or
* in the {@link LRUCache.FetchOptions}). Also, the
* {@link OptionsBase.fetchMethod} may return `undefined` or `void`, making
* {@link LRUCache.OptionsBase.fetchMethod} may return `undefined` or `void`, making
* the test even more complicated.
*
* Because inferring the cases where `undefined` might be returned are so

File diff suppressed because one or more lines are too long

View File

@ -1,18 +1,20 @@
/**
* @module LRUCache
*/
const perf = typeof performance === 'object' &&
const defaultPerf = (typeof performance === 'object' &&
performance &&
typeof performance.now === 'function'
? performance
typeof performance.now === 'function') ?
performance
: Date;
const warned = new Set();
/* c8 ignore start */
const PROCESS = (typeof process === 'object' && !!process ? process : {});
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)
typeof PROCESS.emitWarning === 'function' ?
PROCESS.emitWarning(msg, type, code, fn)
: console.error(`[${code}] ${type}: ${msg}`);
};
let AC = globalThis.AbortController;
@ -76,16 +78,11 @@ 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.
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
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 {
@ -144,9 +141,17 @@ export class LRUCache {
#max;
#maxSize;
#dispose;
#onInsert;
#disposeAfter;
#fetchMethod;
#memoMethod;
#perf;
/**
* {@link LRUCache.OptionsBase.perf}
*/
get perf() {
return this.#perf;
}
/**
* {@link LRUCache.OptionsBase.ttl}
*/
@ -222,9 +227,11 @@ export class LRUCache {
#sizes;
#starts;
#ttls;
#autopurgeTimers;
#hasDispose;
#hasFetchMethod;
#hasDisposeAfter;
#hasOnInsert;
/**
* Do not call this method unless you need to inspect the
* inner workings of the cache. If anything returned by this
@ -239,6 +246,7 @@ export class LRUCache {
// properties
starts: c.#starts,
ttls: c.#ttls,
autopurgeTimers: c.#autopurgeTimers,
sizes: c.#sizes,
keyMap: c.#keyMap,
keyList: c.#keyList,
@ -301,6 +309,12 @@ export class LRUCache {
get dispose() {
return this.#dispose;
}
/**
* {@link LRUCache.OptionsBase.onInsert} (read-only)
*/
get onInsert() {
return this.#onInsert;
}
/**
* {@link LRUCache.OptionsBase.disposeAfter} (read-only)
*/
@ -308,7 +322,13 @@ export class LRUCache {
return this.#disposeAfter;
}
constructor(options) {
const { max = 0, ttl, ttlResolution = 1, ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0, maxEntrySize = 0, sizeCalculation, fetchMethod, memoMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, } = options;
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;
if (perf !== undefined) {
if (typeof perf?.now !== 'function') {
throw new TypeError('perf option must have a now() method if specified');
}
}
this.#perf = perf ?? defaultPerf;
if (max !== 0 && !isPosInt(max)) {
throw new TypeError('max option must be a nonnegative integer');
}
@ -328,13 +348,11 @@ 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;
@ -352,6 +370,9 @@ export class LRUCache {
if (typeof dispose === 'function') {
this.#dispose = dispose;
}
if (typeof onInsert === 'function') {
this.#onInsert = onInsert;
}
if (typeof disposeAfter === 'function') {
this.#disposeAfter = disposeAfter;
this.#disposed = [];
@ -361,6 +382,7 @@ export class LRUCache {
this.#disposed = undefined;
}
this.#hasDispose = !!this.#dispose;
this.#hasOnInsert = !!this.#onInsert;
this.#hasDisposeAfter = !!this.#disposeAfter;
this.noDisposeOnSet = !!noDisposeOnSet;
this.noUpdateTTL = !!noUpdateTTL;
@ -385,9 +407,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) {
@ -422,10 +442,21 @@ export class LRUCache {
const starts = new ZeroArray(this.#max);
this.#ttls = ttls;
this.#starts = starts;
this.#setItemTTL = (index, ttl, start = perf.now()) => {
const purgeTimers = this.ttlAutopurge ?
new Array(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) {
// 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.
if (purgeTimers?.[index]) {
clearTimeout(purgeTimers[index]);
purgeTimers[index] = undefined;
}
if (ttl !== 0 && purgeTimers) {
const t = setTimeout(() => {
if (this.#isStale(index)) {
this.#delete(this.#keyList[index], 'expire');
@ -437,10 +468,11 @@ export class LRUCache {
t.unref();
}
/* c8 ignore stop */
purgeTimers[index] = t;
}
};
this.#updateItemAge = index => {
starts[index] = ttls[index] !== 0 ? perf.now() : 0;
starts[index] = ttls[index] !== 0 ? this.#perf.now() : 0;
};
this.#statusTTL = (status, index) => {
if (ttls[index]) {
@ -460,7 +492,7 @@ export class LRUCache {
// that costly call repeatedly.
let cachedNow = 0;
const getNow = () => {
const n = perf.now();
const n = this.#perf.now();
if (this.ttlResolution > 0) {
cachedNow = n;
const t = setTimeout(() => (cachedNow = 0), this.ttlResolution);
@ -628,8 +660,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;
}
}
@ -643,8 +674,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;
}
}
@ -656,8 +686,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];
}
}
@ -671,8 +700,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];
}
}
@ -697,9 +725,7 @@ export class LRUCache {
find(fn, getOptions = {}) {
for (const i of this.#indexes()) {
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
continue;
if (fn(value, this.#keyList[i], this)) {
@ -721,9 +747,7 @@ export class LRUCache {
forEach(fn, thisp = this) {
for (const i of this.#indexes()) {
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
continue;
fn.call(thisp, value, this.#keyList[i], this);
@ -736,9 +760,7 @@ export class LRUCache {
rforEach(fn, thisp = this) {
for (const i of this.#rindexes()) {
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
continue;
fn.call(thisp, value, this.#keyList[i], this);
@ -775,17 +797,18 @@ export class LRUCache {
if (i === undefined)
return undefined;
const v = this.#valList[i];
const value = this.#isBackgroundFetch(v)
? v.__staleWhileFetching
: v;
/* c8 ignore start - this isn't tested for the info function,
* but it's the same logic as found in other places. */
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
return undefined;
/* c8 ignore end */
const entry = { value };
if (this.#ttls && this.#starts) {
const ttl = this.#ttls[i];
const start = this.#starts[i];
if (ttl && start) {
const remain = ttl - (perf.now() - start);
const remain = ttl - (this.#perf.now() - start);
entry.ttl = remain;
entry.start = Date.now();
}
@ -797,7 +820,7 @@ export class LRUCache {
}
/**
* Return an array of [key, {@link LRUCache.Entry}] tuples which can be
* passed to {@link LRLUCache#load}.
* passed to {@link LRUCache#load}.
*
* The `start` fields are calculated relative to a portable `Date.now()`
* timestamp, even if `performance.now()` is available.
@ -813,9 +836,7 @@ export class LRUCache {
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;
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined || key === undefined)
continue;
const entry = { value };
@ -823,7 +844,7 @@ export class LRUCache {
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];
const age = this.#perf.now() - this.#starts[i];
entry.start = Math.floor(Date.now() - age);
}
if (this.#sizes) {
@ -853,7 +874,7 @@ export class LRUCache {
//
// 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;
entry.start = this.#perf.now() - age;
}
this.set(key, entry.value, entry);
}
@ -910,12 +931,9 @@ export class LRUCache {
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)
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;
@ -928,6 +946,9 @@ export class LRUCache {
if (status)
status.set = 'add';
noUpdateTTL = false;
if (this.#hasOnInsert) {
this.#onInsert?.(v, k, 'add');
}
}
else {
// update
@ -959,8 +980,8 @@ export class LRUCache {
this.#valList[index] = v;
if (status) {
status.set = 'replace';
const oldValue = oldVal && this.#isBackgroundFetch(oldVal)
? oldVal.__staleWhileFetching
const oldValue = oldVal && this.#isBackgroundFetch(oldVal) ?
oldVal.__staleWhileFetching
: oldVal;
if (oldValue !== undefined)
status.oldValue = oldValue;
@ -969,6 +990,9 @@ export class LRUCache {
else if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, v === oldVal ? 'update' : 'replace');
}
}
if (ttl !== 0 && !this.#ttls) {
this.#initializeTTLTracking();
@ -1034,6 +1058,10 @@ export class LRUCache {
}
}
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;
@ -1106,8 +1134,7 @@ export class LRUCache {
peek(k, peekOptions = {}) {
const { allowStale = this.allowStale } = peekOptions;
const index = this.#keyMap.get(k);
if (index === undefined ||
(!allowStale && this.#isStale(index))) {
if (index === undefined || (!allowStale && this.#isStale(index))) {
return;
}
const v = this.#valList[index];
@ -1149,9 +1176,13 @@ export class LRUCache {
}
// either we didn't abort, and are still here, or we did, and ignored
const bf = p;
if (this.#valList[index] === p) {
// if nothing else has been written there but we're set to update the
// 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 (v === undefined) {
if (bf.__staleWhileFetching) {
if (bf.__staleWhileFetching !== undefined) {
this.#valList[index] = bf.__staleWhileFetching;
}
else {
@ -1213,8 +1244,7 @@ export class LRUCache {
// 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) {
@ -1446,6 +1476,10 @@ export class LRUCache {
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);
@ -1521,6 +1555,11 @@ export class LRUCache {
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

View File

@ -1,10 +1,7 @@
{
"name": "lru-cache",
"publishConfig": {
"tag": "legacy-v10"
},
"description": "A cache object that deletes the least-recently-used items.",
"version": "10.4.3",
"version": "11.2.4",
"author": "Isaac Z. Schlueter <i@izs.me>",
"keywords": [
"mru",
@ -52,34 +49,22 @@
"url": "git://github.com/isaacs/node-lru-cache.git"
},
"devDependencies": {
"@types/node": "^20.2.5",
"@types/tap": "^15.0.6",
"@types/node": "^24.3.0",
"benchmark": "^2.1.4",
"esbuild": "^0.17.11",
"eslint-config-prettier": "^8.5.0",
"esbuild": "^0.25.9",
"marked": "^4.2.12",
"mkdirp": "^2.1.5",
"prettier": "^2.6.2",
"tap": "^20.0.3",
"tshy": "^2.0.0",
"tslib": "^2.4.0",
"typedoc": "^0.25.3",
"typescript": "^5.2.2"
"mkdirp": "^3.0.1",
"prettier": "^3.6.2",
"tap": "^21.1.0",
"tshy": "^3.0.2",
"typedoc": "^0.28.12"
},
"license": "ISC",
"license": "BlueOak-1.0.0",
"files": [
"dist"
],
"prettier": {
"semi": false,
"printWidth": 70,
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"jsxSingleQuote": false,
"bracketSameLine": true,
"arrowParens": "avoid",
"endOfLine": "lf"
"engines": {
"node": "20 || >=22"
},
"tap": {
"node-arg": [

View File

@ -1,6 +1,6 @@
{
"name": "path-scurry",
"version": "1.11.1",
"version": "2.0.1",
"description": "walk paths fast and efficiently",
"author": "Isaac Z. Schlueter <i@izs.me> (https://blog.izs.me)",
"main": "./dist/commonjs/index.js",
@ -31,7 +31,7 @@
"presnap": "npm run prepare",
"test": "tap",
"snap": "tap",
"format": "prettier --write . --loglevel warn",
"format": "prettier --write . --log-level warn",
"typedoc": "typedoc --tsconfig tsconfig-esm.json ./src/*.ts",
"bench": "bash ./scripts/bench.sh"
},
@ -48,24 +48,22 @@
"endOfLine": "lf"
},
"devDependencies": {
"@nodelib/fs.walk": "^1.2.8",
"@types/node": "^20.12.11",
"c8": "^7.12.0",
"eslint-config-prettier": "^8.6.0",
"@nodelib/fs.walk": "^2.0.0",
"@types/node": "^20.14.10",
"mkdirp": "^3.0.0",
"prettier": "^3.2.5",
"rimraf": "^5.0.1",
"tap": "^18.7.2",
"prettier": "^3.3.2",
"rimraf": "^5.0.8",
"tap": "^20.0.3",
"ts-node": "^10.9.2",
"tshy": "^1.14.0",
"typedoc": "^0.25.12",
"typescript": "^5.4.3"
"tshy": "^2.0.1",
"typedoc": "^0.26.3",
"typescript": "^5.5.3"
},
"tap": {
"typecheck": true
},
"engines": {
"node": ">=16 || 14 >=14.18"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@ -75,8 +73,8 @@
"url": "git+https://github.com/isaacs/path-scurry"
},
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"tshy": {
"selfLink": false,
@ -85,5 +83,6 @@
".": "./src/index.ts"
}
},
"types": "./dist/commonjs/index.d.ts"
"types": "./dist/commonjs/index.d.ts",
"module": "./dist/esm/index.js"
}