feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
+2 -2
View File
@@ -110,7 +110,7 @@ hosts.gitlab = {
blobpath: 'tree',
editpath: '-/edit',
tarballtemplate: ({ domain, user, project, committish }) =>
`https://${domain}/api/v4/projects/${maybeEncode(user + '/' + project)}/repository/archive.tar.gz?sha=${maybeEncode(committish || 'HEAD')}`,
`https://${domain}/${user}/${project}/repository/archive.tar.gz?ref=${maybeEncode(committish || 'HEAD')}`,
extract: (url) => {
const path = url.pathname.slice(1)
if (path.includes('/-/') || path.includes('/archive.tar.gz')) {
@@ -198,7 +198,7 @@ hosts.sourcehut = {
filetemplate: ({ domain, user, project, committish, path }) =>
`https://${domain}/${user}/${project}/blob/${maybeEncode(committish) || 'HEAD'}/${path}`,
httpstemplate: ({ domain, user, project, committish }) =>
`https://${domain}/${user}/${project}${maybeJoin('#', committish)}`,
`https://${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
tarballtemplate: ({ domain, user, project, committish }) =>
`https://${domain}/${user}/${project}/archive/${maybeEncode(committish) || 'HEAD'}.tar.gz`,
bugstemplate: () => null,
+15 -146
View File
@@ -79,7 +79,11 @@ 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)
@@ -215,95 +219,9 @@ const myGet = (key, value) => {
}
```
## Tracing and Observability
Most methods can accept a `status` option, which is an
[`LRUCache.Status`](https://isaacs.github.io/node-lru-cache/interfaces/LRUCache.LRUCache.Status.html)
object that will be decorated along the operation with
indications about what was done and why.
Additionally, this library is instrumented using the
[`node:diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html)
module on Node and other platforms that support it. In order to
get diagnostics metrics, listen on the
`channel('lru-cache:metrics')`. To get Tracing Channel traces,
subscribe to the `tracingChannel('lru-cache')`. The
[`LRUCache.Status`](https://isaacs.github.io/node-lru-cache/interfaces/LRUCache.LRUCache.Status.html)
objects will be provided as the message context to those channel
listeners.
For example, you could do the following to get comprehensive
information about every LRUCache instance in your application:
```ts
import { tracingChannel, subscribe } from 'node:diagnostics_channel'
subscribe('lru-cache:metrics', (message, name) => {
// name will always be 'lru-cache:metrics'
// message will be the LRUCache.Status object for whatever
// synchronous operation was performed.
console.error('LRUCache Metrics', message)
})
tracingChannel('lru-cache').subscribe({
start: status => {
// a traced operation is starting
},
asyncStart: status => {
// an async traced operation is starting
},
asyncEnd: status => {
// an async traced operation is ending
}
error: status => {
// a traced operation failed
},
end: status => {
// a traced operation is complete
},
})
```
The async `cache.fetch()` and `cache.forceFetch` methods are
covered by `tracingChannels`. All the other operations are
covered by the `lru-cache:metrics` channel, because they are
strictly synchronous, and thus don't have an asynchronous
lifecycle to track.
Note that using `status` objects or using
`node:diagnostics_channel` listeners _will_ impose a modest
performance penalty. Creating data objects is not ever free; do
not believe anyone who tells you otherwise. But it is as small as
possible.
### Platform Compatibility Caveat
Not all platforms support the `node:diagnostics_channel` module.
Currently, this is only available in Node, Bun, and Deno, and
some edge computing platforms that provide a Node compatibility
layer.
To work around this, if you are loading in a non-Node
environment, the package.json exports will direct your module
loader to pull in a version that starts out with a dummy
implementation, then does a conditional dynamic `import` of the
`node:diagnostics_channel` module, and then swaps out those
dummy objects with the real thing if it succeeds. This means that
cache metrics and tracing channels started in the first load-time
tick of your application will _not_ be covered, except in
environments that load using the `require` import
condition, or both the `node` and `esm` import conditions
together.
Top-level await _could_ be used to remove this caveat, but that
feature is dead on arrival, unfortunately. See
[#397](https://github.com/isaacs/node-lru-cache/issues/397) and
[#398](https://github.com/isaacs/node-lru-cache/issues/398) for
more details.
## Performance
As of April 2026, version 11 of this library is one of the most
As of January 2022, version 7 of this library is one of the most
performant LRU cache implementations in JavaScript.
Benchmarks can be extremely difficult to get right. In
@@ -358,9 +276,9 @@ If performance matters to you:
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache)
which uses an Object as its data store.
2. Failing that, if you can use short non-numeric strings (ie,
less than 256 characters) as your keys, and you do not need
any of the other features of this library, use [mnemonist's
2. Failing that, if at all possible, use short non-numeric
strings (ie, less than 256 characters) as your keys, and use
[mnemonist's
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache).
3. If the types of your keys will be anything else, especially
@@ -372,63 +290,14 @@ If performance matters to you:
(like asynchronous fetching, a variety of TTL staleness
options, and so on), then [mnemonist's
LRUMap](https://yomguithereal.github.io/mnemonist/lru-map) is
also a very good option, and just slightly faster than this
module (since it does considerably less).
a very good option, and just slightly faster than this module
(since it does considerably less).
4. Do not use a `dispose` function, size tracking, or especially
ttl behavior or observability features, unless absolutely
needed. These features are convenient, and necessary in some
use cases, and every attempt has been made to make the
performance impact minimal, but it isn't nothing.
## Testing
When writing tests that involve TTL-related functionality, note
that this module creates an internal reference to the global
`performance` or `Date` objects at import time. If you import it
statically at the top level, those references cannot be mocked or
overridden in your test environment.
To avoid this, dynamically import the package within your tests
so that the references are captured after your mocks are applied.
For example:
```ts
// ❌ Not recommended
import { LRUCache } from 'lru-cache'
// mocking timers, e.g. jest.useFakeTimers()
// ✅ Recommended for TTL tests
// mocking timers, e.g. jest.useFakeTimers()
const { LRUCache } = await import('lru-cache')
```
This ensures that your mocked timers or time sources are
respected when testing TTL behavior.
Additionally, you can pass in a `perf` option when creating your
LRUCache instance. This option accepts any object with a `now`
method that returns a number.
For example, this would be a very bare-bones time-mocking system
you could use in your tests, without any particular test
framework:
```ts
import { LRUCache } from 'lru-cache'
let myClockTime = 0
const cache = new LRUCache<string>({
max: 10,
ttl: 1000,
perf: {
now: () => myClockTime,
},
})
// run tests, updating myClockTime as needed
```
ttl behavior, unless absolutely needed. These features are
convenient, and necessary in some use cases, and every attempt
has been made to make the performance impact minimal, but it
isn't nothing.
## Breaking Changes in Version 7
+30 -108
View File
@@ -1,8 +1,9 @@
/**
* @module LRUCache
*/
import type { Perf } from './perf.js';
export type { Perf } from './perf.js';
export type Perf = {
now: () => number;
};
declare const TYPE: unique symbol;
export type PosInt = number & {
[TYPE]: 'Positive Integer';
@@ -118,16 +119,8 @@ 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<K, V, FC = unknown> {
/**
* The operation being performed
*/
op?: 'get' | 'set' | 'memo' | 'fetch' | 'delete' | 'has' | 'peek';
interface Status<V> {
/**
* The status of a set() operation.
*
@@ -136,37 +129,7 @@ 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' | '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;
set?: 'add' | 'update' | 'replace' | 'miss';
/**
* the ttl stored for the item, or undefined if ttls are not used.
*/
@@ -197,15 +160,8 @@ export declare namespace LRUCache {
*/
maxEntrySizeExceeded?: true;
/**
* 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'`
* The old value, specified in the case of `set:'update'` or
* `set:'replace'`
*/
oldValue?: V;
/**
@@ -231,10 +187,6 @@ 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
*/
@@ -256,7 +208,7 @@ export declare namespace LRUCache {
fetchAborted?: true;
/**
* The abort signal received was ignored, and the fetch was allowed to
* continue in the background.
* continue.
*/
fetchAbortIgnored?: true;
/**
@@ -272,27 +224,15 @@ 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. 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.
* - stale: The item is in the cache, and is stale.
* - hit: the item is in the cache
* - miss: the item is not in the cache
*/
get?: 'stale' | 'hit' | 'miss' | 'fetching' | 'stale-fetching';
get?: 'stale' | 'hit' | 'miss';
/**
* 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
@@ -310,7 +250,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<K, V, FC>;
status?: Status<V>;
size?: Size;
}
/**
@@ -332,7 +272,7 @@ export declare namespace LRUCache {
*/
context?: FC;
signal?: AbortSignal;
status?: Status<K, V, FC>;
status?: Status<V>;
}
/**
* Options provided to {@link LRUCache#fetch} when the FC type is something
@@ -345,7 +285,7 @@ export declare namespace LRUCache {
* Options provided to {@link LRUCache#fetch} when the FC type is
* `undefined` or `void`
*/
interface FetchOptionsNoContext<K, V, FC extends undefined | void = undefined> extends FetchOptions<K, V, FC> {
interface FetchOptionsNoContext<K, V> extends FetchOptions<K, V, undefined> {
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'> {
@@ -363,7 +303,7 @@ export declare namespace LRUCache {
* be required.
*/
context?: FC;
status?: Status<K, V, FC>;
status?: Status<V>;
}
/**
* Options provided to {@link LRUCache#memo} when the FC type is something
@@ -376,7 +316,7 @@ export declare namespace LRUCache {
* Options provided to {@link LRUCache#memo} when the FC type is
* `undefined` or `void`
*/
interface MemoOptionsNoContext<K, V, FC extends undefined | void = undefined> extends MemoOptions<K, V, FC> {
interface MemoOptionsNoContext<K, V> extends MemoOptions<K, V, undefined> {
context?: undefined;
}
/**
@@ -405,7 +345,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<K, V, FC>;
status?: Status<V>;
size?: Size;
start?: Milliseconds;
}
@@ -413,19 +353,18 @@ 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<K, V, FC>;
status?: Status<V>;
}
/**
* 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<K, V, FC>;
status?: Status<V>;
}
/**
* 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.
@@ -446,7 +385,7 @@ export declare namespace LRUCache {
* method is in use.
*/
start?: Milliseconds;
status?: Status<K, V, FC>;
status?: Status<V>;
}
/**
* The type signature for the {@link OptionsBase.fetchMethod} option.
@@ -699,20 +638,6 @@ 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.
*
@@ -1015,8 +940,6 @@ 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
@@ -1029,7 +952,6 @@ 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)[];
@@ -1039,8 +961,8 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
readonly head: Index;
readonly tail: Index;
free: StackLike;
isBackgroundFetch: (p: unknown) => p is BackgroundFetch<V>;
backgroundFetch: (k: K, index: number | undefined, options: LRUCache.FetchOptions<K, V, FC>, context: unknown) => BackgroundFetch<V>;
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?: {
allowStale: boolean;
@@ -1152,12 +1074,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>) => unknown, thisp?: unknown): void;
forEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any): 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>) => unknown, thisp?: unknown): void;
rforEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any): void;
/**
* Delete any stale entries. Returns true if anything was removed,
* false otherwise.
@@ -1230,7 +1152,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 | undefined, setOptions?: LRUCache.SetOptions<K, V, FC>): this;
set(k: K, v: V | BackgroundFetch<V> | undefined, setOptions?: LRUCache.SetOptions<K, V, FC>): this;
/**
* Evict the least recently used item, returning its value or
* `undefined` if cache is empty.
@@ -1346,8 +1268,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, 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>;
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>;
/**
* In some cases, `cache.fetch()` may resolve to `undefined`, either because
* a {@link LRUCache.OptionsBase#fetchMethod} was not provided (turning
@@ -1361,8 +1283,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, 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>;
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>;
/**
* 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
@@ -1377,8 +1299,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, 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;
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;
/**
* 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
+208 -366
View File
@@ -4,27 +4,75 @@
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LRUCache = void 0;
const diagnostics_channel_js_1 = require("./diagnostics-channel.js");
const perf_js_1 = require("./perf.js");
const hasSubscribers = () => diagnostics_channel_js_1.metrics.hasSubscribers || diagnostics_channel_js_1.tracing.hasSubscribers;
const defaultPerf = (typeof performance === 'object' &&
performance &&
typeof performance.now === 'function') ?
performance
: Date;
const warned = new Set();
/* c8 ignore start */
const PROCESS = (typeof process === 'object' && !!process ?
process
: {});
/* c8 ignore stop */
/* c8 ignore start */
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}`);
}
typeof PROCESS.emitWarning === 'function' ?
PROCESS.emitWarning(msg, type, code, fn)
: console.error(`[${code}] ${type}: ${msg}`);
};
let AC = globalThis.AbortController;
let AS = globalThis.AbortSignal;
/* c8 ignore start */
if (typeof AC === 'undefined') {
//@ts-ignore
AS = class AbortSignal {
onabort;
_onabort = [];
reason;
aborted = false;
addEventListener(_, fn) {
this._onabort.push(fn);
}
};
//@ts-ignore
AC = class AbortController {
constructor() {
warnACPolyfill();
}
signal = new AS();
abort(reason) {
if (this.signal.aborted)
return;
//@ts-ignore
this.signal.reason = reason;
//@ts-ignore
this.signal.aborted = true;
//@ts-ignore
for (const fn of this.signal._onabort) {
fn(reason);
}
this.signal.onabort?.(reason);
}
};
let printACPolyfillWarning = PROCESS.env?.LRU_CACHE_IGNORE_AC_WARNING !== '1';
const warnACPolyfill = () => {
if (!printACPolyfillWarning)
return;
printACPolyfillWarning = false;
emitWarning('AbortController is not defined. If using lru-cache in ' +
'node 14, load an AbortController polyfill from the ' +
'`node-abort-controller` package. A minimal polyfill is ' +
'provided for use by LRUCache.fetch(), but it should not be ' +
'relied upon in other contexts (eg, passing it to other APIs that ' +
'use AbortController/AbortSignal might have undesirable effects). ' +
'You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.', 'NO_ABORT_CONTROLLER', 'ENOTSUP', warnACPolyfill);
};
}
/* c8 ignore stop */
const shouldWarn = (code) => !warned.has(code);
const TYPE = Symbol('type');
const isPosInt = (n) => !!n && n === Math.floor(n) && n > 0 && isFinite(n);
const isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n);
/* c8 ignore start */
// This is a little bit ridiculous, tbh.
// The maximum array length is 2^32-1 or thereabouts on most JS impls.
// And well before that point, you're caching the entire world, I mean,
@@ -33,7 +81,6 @@ 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
@@ -48,9 +95,7 @@ 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;
@@ -170,8 +215,6 @@ class LRUCache {
* {@link LRUCache.OptionsBase.ignoreFetchAbort}
*/
ignoreFetchAbort;
/** {@link LRUCache.OptionsBase.backgroundFetchSize} */
backgroundFetchSize;
// computed properties
#size;
#calculatedSize;
@@ -187,7 +230,6 @@ class LRUCache {
#sizes;
#starts;
#ttls;
#autopurgeTimers;
#hasDispose;
#hasFetchMethod;
#hasDisposeAfter;
@@ -206,7 +248,6 @@ class LRUCache {
// properties
starts: c.#starts,
ttls: c.#ttls,
autopurgeTimers: c.#autopurgeTimers,
sizes: c.#sizes,
keyMap: c.#keyMap,
keyList: c.#keyList,
@@ -282,14 +323,13 @@ 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, backgroundFetchSize = 1, perf, } = options;
this.backgroundFetchSize = backgroundFetchSize;
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 ?? perf_js_1.defaultPerf;
this.#perf = perf ?? defaultPerf;
if (max !== 0 && !isPosInt(max)) {
throw new TypeError('max option must be a nonnegative integer');
}
@@ -309,18 +349,20 @@ 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 = Array.from({ length: max }).fill(undefined);
this.#valList = Array.from({ length: max }).fill(undefined);
this.#keyList = new Array(max).fill(undefined);
this.#valList = new Array(max).fill(undefined);
this.#next = new UintArray(max);
this.#prev = new UintArray(max);
this.#head = 0;
@@ -368,7 +410,9 @@ 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) {
@@ -403,56 +447,33 @@ 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;
setPurgetTimer(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 */
}
};
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 start */
if (!ttl || !start) {
/* c8 ignore next */
if (!ttl || !start)
return;
}
/* c8 ignore stop */
status.ttl = ttl;
status.start = start;
status.now = cachedNow || getNow();
@@ -511,15 +532,12 @@ 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');
@@ -562,7 +580,10 @@ class LRUCache {
};
*#indexes({ allowStale = this.allowStale } = {}) {
if (this.#size) {
for (let i = this.#tail; this.#isValidIndex(i);) {
for (let i = this.#tail; true;) {
if (!this.#isValidIndex(i)) {
break;
}
if (allowStale || !this.#isStale(i)) {
yield i;
}
@@ -577,7 +598,10 @@ class LRUCache {
}
*#rindexes({ allowStale = this.allowStale } = {}) {
if (this.#size) {
for (let i = this.#head; this.#isValidIndex(i);) {
for (let i = this.#head; true;) {
if (!this.#isValidIndex(i)) {
break;
}
if (allowStale || !this.#isStale(i)) {
yield i;
}
@@ -629,7 +653,8 @@ 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,7 +668,8 @@ 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;
}
}
@@ -655,7 +681,8 @@ 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];
}
}
@@ -669,7 +696,8 @@ 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];
}
}
@@ -698,7 +726,7 @@ 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);
}
}
}
@@ -771,7 +799,7 @@ class LRUCache {
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
return undefined;
/* c8 ignore stop */
/* c8 ignore end */
const entry = { value };
if (this.#ttls && this.#starts) {
const ttl = this.#ttls[i];
@@ -845,7 +873,7 @@ 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);
}
}
/**
@@ -879,43 +907,22 @@ class LRUCache {
* `cache.delete(key)`. `undefined` is never stored in the cache.
*/
set(k, v, setOptions = {}) {
const { status = diagnostics_channel_js_1.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 && diagnostics_channel_js_1.metrics.hasSubscribers) {
diagnostics_channel_js_1.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;
if (status && !isBF)
status.value = v;
const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation, status);
const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation);
// if the item doesn't fit, don't do anything
// NB: maxEntrySize set to maxSize by default
if (this.maxEntrySize && size > this.maxEntrySize) {
// 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);
@@ -936,68 +943,52 @@ class LRUCache {
if (status)
status.set = 'add';
noUpdateTTL = false;
if (this.#hasOnInsert && !isBF) {
if (this.#hasOnInsert) {
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 (!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.#hasFetchMethod && this.#isBackgroundFetch(oldVal)) {
oldVal.__abortController.abort(new Error('replaced'));
const { __staleWhileFetching: s } = oldVal;
if (s !== undefined && !noDisposeOnSet) {
if (this.#hasDispose) {
this.#dispose?.(oldVal, k, 'set');
this.#dispose?.(s, k, 'set');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([oldVal, k, 'set']);
this.#disposed?.push([s, 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 (!isBF) {
if (status) {
status.set = 'replace';
const oldValue = oldVal && this.#isBackgroundFetch(oldVal) ?
oldVal.__staleWhileFetching
: oldVal;
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);
}
if (oldValue !== undefined)
status.oldValue = oldValue;
}
}
else if (!isBF) {
if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, 'update');
}
else if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, v === oldVal ? 'update' : 'replace');
}
}
if (ttl !== 0 && !this.#ttls) {
@@ -1052,25 +1043,18 @@ class LRUCache {
const head = this.#head;
const k = this.#keyList[head];
const v = this.#valList[head];
const isBF = this.#isBackgroundFetch(v);
if (isBF) {
if (this.#hasFetchMethod && this.#isBackgroundFetch(v)) {
v.__abortController.abort(new Error('evicted'));
}
const oldValue = isBF ? v.__staleWhileFetching : v;
if ((this.#hasDispose || this.#hasDisposeAfter) &&
oldValue !== undefined) {
else if (this.#hasDispose || this.#hasDisposeAfter) {
if (this.#hasDispose) {
this.#dispose?.(oldValue, k, 'evict');
this.#dispose?.(v, k, 'evict');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([oldValue, k, 'evict']);
this.#disposed?.push([v, 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;
@@ -1105,19 +1089,6 @@ class LRUCache {
* {@link LRUCache.OptionsBase.updateAgeOnHas} is set.
*/
has(k, hasOptions = {}) {
const { status = diagnostics_channel_js_1.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 (diagnostics_channel_js_1.metrics.hasSubscribers)
diagnostics_channel_js_1.metrics.publish(status);
return result;
}
#has(k, hasOptions = {}) {
const { updateAgeOnHas = this.updateAgeOnHas, status } = hasOptions;
const index = this.#keyMap.get(k);
if (index !== undefined) {
@@ -1154,46 +1125,22 @@ class LRUCache {
* {@link LRUCache.OptionsBase.allowStale} is set.
*/
peek(k, 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 (diagnostics_channel_js_1.metrics.hasSubscribers) {
diagnostics_channel_js_1.metrics.publish(status);
}
return result;
}
#peek(k, peekOptions) {
const { status, allowStale = this.allowStale } = peekOptions;
const { allowStale = this.allowStale } = peekOptions;
const index = this.#keyMap.get(k);
if (index === undefined || (!allowStale && this.#isStale(index))) {
if (status)
status.peek = index === undefined ? 'miss' : 'stale';
return undefined;
if (index === undefined ||
(!allowStale && this.#isStale(index))) {
return;
}
const v = this.#valList[index];
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;
// either stale and allowed, or forcing a refresh of non-stale value
return this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
}
#backgroundFetch(k, index, options, context) {
const v = index === undefined ? undefined : this.#valList[index];
if (this.#isBackgroundFetch(v)) {
return v;
}
const ac = new AbortController();
const ac = new AC();
const { signal } = options;
// when/if our AC signals, then stop listening to theirs.
signal?.addEventListener('abort', () => ac.abort(signal.reason), {
@@ -1207,8 +1154,6 @@ 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;
@@ -1221,7 +1166,7 @@ class LRUCache {
}
}
if (aborted && !ignoreAbort && !updateCache) {
return fetchFail(ac.signal.reason, proceed);
return fetchFail(ac.signal.reason);
}
// either we didn't abort, and are still here, or we did, and ignored
const bf = p;
@@ -1229,7 +1174,7 @@ 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 || (vl === undefined && ignoreAbort && updateCache)) {
if (vl === p || ignoreAbort && updateCache && vl === undefined) {
if (v === undefined) {
if (bf.__staleWhileFetching !== undefined) {
this.#valList[index] = bf.__staleWhileFetching;
@@ -1241,7 +1186,7 @@ class LRUCache {
else {
if (options.status)
options.status.fetchUpdated = true;
this.#set(k, v, fetchOpts.options, bf);
this.set(k, v, fetchOpts.options);
}
}
return v;
@@ -1251,10 +1196,9 @@ class LRUCache {
options.status.fetchRejected = true;
options.status.fetchError = er;
}
// do not pass go, do not collect $200
return fetchFail(er, false);
return fetchFail(er);
};
const fetchFail = (er, proceed) => {
const fetchFail = (er) => {
const { aborted } = ac.signal;
const allowStaleAborted = aborted && options.allowStaleOnFetchAbort;
const allowStale = allowStaleAborted || options.allowStaleOnFetchRejection;
@@ -1263,7 +1207,7 @@ 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 || (!proceed && bf.__staleWhileFetching === undefined);
const del = !noDelete || bf.__staleWhileFetching === undefined;
if (del) {
this.#delete(k, 'fetch');
}
@@ -1287,11 +1231,15 @@ 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) {
@@ -1299,12 +1247,6 @@ 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;
@@ -1316,14 +1258,10 @@ 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;
@@ -1335,23 +1273,9 @@ class LRUCache {
return (!!b &&
b instanceof Promise &&
b.hasOwnProperty('__staleWhileFetching') &&
b.__abortController instanceof AbortController);
b.__abortController instanceof AC);
}
fetch(k, fetchOptions = {}) {
const ths = diagnostics_channel_js_1.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;
diagnostics_channel_js_1.tracing.tracePromise(() => p, status).catch(() => { });
}
return p;
}
async #fetch(k, fetchOptions = {}) {
async fetch(k, fetchOptions = {}) {
const {
// get options
allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet,
@@ -1359,17 +1283,10 @@ 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,
@@ -1438,68 +1355,26 @@ class LRUCache {
return staleVal ? p.__staleWhileFetching : (p.__returned = p);
}
}
forceFetch(k, fetchOptions = {}) {
const ths = diagnostics_channel_js_1.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;
diagnostics_channel_js_1.tracing.tracePromise(() => p, status).catch(() => { });
}
return p;
}
async #forceFetch(k, fetchOptions = {}) {
const v = await this.#fetch(k, fetchOptions);
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 = diagnostics_channel_js_1.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 (diagnostics_channel_js_1.metrics.hasSubscribers)
diagnostics_channel_js_1.metrics.publish(status);
return result;
}
#memo(k, memoOptions = {}) {
const memoMethod = this.#memoMethod;
if (!memoMethod) {
throw new Error('no memoMethod provided to constructor');
}
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)
const { context, forceRefresh, ...options } = memoOptions;
const v = this.get(k, options);
if (!forceRefresh && v !== undefined)
return v;
const vv = memoMethod(k, v, {
options,
context,
});
if (status)
status.value = vv;
this.#set(k, vv, options);
this.set(k, vv, options);
return vv;
}
/**
@@ -1509,71 +1384,55 @@ class LRUCache {
* If the key is not found, get() will return `undefined`.
*/
get(k, getOptions = {}) {
const { status = diagnostics_channel_js_1.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 (diagnostics_channel_js_1.metrics.hasSubscribers)
diagnostics_channel_js_1.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) {
if (index !== undefined) {
const value = this.#valList[index];
const fetching = this.#isBackgroundFetch(value);
if (status)
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');
}
this.#statusTTL(status, index);
if (this.#isStale(index)) {
if (status)
status.get = 'stale';
if (allowStale) {
if (status)
// delete only if not an in-flight background fetch
if (!fetching) {
if (!noDeleteOnStaleGet) {
this.#delete(k, 'expire');
}
if (status && allowStale)
status.returnedStale = true;
return value;
return allowStale ? value : undefined;
}
else {
if (status &&
allowStale &&
value.__staleWhileFetching !== undefined) {
status.returnedStale = true;
}
return allowStale ? value.__staleWhileFetching : undefined;
}
return undefined;
}
if (status)
status.get = 'stale-fetching';
if (allowStale && value.__staleWhileFetching !== undefined) {
else {
if (status)
status.returnedStale = true;
return value.__staleWhileFetching;
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;
}
return undefined;
}
// 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);
else if (status) {
status.get = 'miss';
}
return fetching ? value.__staleWhileFetching : value;
}
#connect(p, n) {
this.#prev[n] = p;
@@ -1608,22 +1467,10 @@ class LRUCache {
return this.#delete(k, 'delete');
}
#delete(k, reason) {
if (diagnostics_channel_js_1.metrics.hasSubscribers) {
diagnostics_channel_js_1.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);
@@ -1694,16 +1541,11 @@ class LRUCache {
}
}
this.#keyMap.clear();
void this.#valList.fill(undefined);
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
+30 -108
View File
@@ -1,8 +1,9 @@
/**
* @module LRUCache
*/
import type { Perf } from './perf.js';
export type { Perf } from './perf.js';
export type Perf = {
now: () => number;
};
declare const TYPE: unique symbol;
export type PosInt = number & {
[TYPE]: 'Positive Integer';
@@ -118,16 +119,8 @@ 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<K, V, FC = unknown> {
/**
* The operation being performed
*/
op?: 'get' | 'set' | 'memo' | 'fetch' | 'delete' | 'has' | 'peek';
interface Status<V> {
/**
* The status of a set() operation.
*
@@ -136,37 +129,7 @@ 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' | '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;
set?: 'add' | 'update' | 'replace' | 'miss';
/**
* the ttl stored for the item, or undefined if ttls are not used.
*/
@@ -197,15 +160,8 @@ export declare namespace LRUCache {
*/
maxEntrySizeExceeded?: true;
/**
* 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'`
* The old value, specified in the case of `set:'update'` or
* `set:'replace'`
*/
oldValue?: V;
/**
@@ -231,10 +187,6 @@ 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
*/
@@ -256,7 +208,7 @@ export declare namespace LRUCache {
fetchAborted?: true;
/**
* The abort signal received was ignored, and the fetch was allowed to
* continue in the background.
* continue.
*/
fetchAbortIgnored?: true;
/**
@@ -272,27 +224,15 @@ 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. 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.
* - stale: The item is in the cache, and is stale.
* - hit: the item is in the cache
* - miss: the item is not in the cache
*/
get?: 'stale' | 'hit' | 'miss' | 'fetching' | 'stale-fetching';
get?: 'stale' | 'hit' | 'miss';
/**
* 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
@@ -310,7 +250,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<K, V, FC>;
status?: Status<V>;
size?: Size;
}
/**
@@ -332,7 +272,7 @@ export declare namespace LRUCache {
*/
context?: FC;
signal?: AbortSignal;
status?: Status<K, V, FC>;
status?: Status<V>;
}
/**
* Options provided to {@link LRUCache#fetch} when the FC type is something
@@ -345,7 +285,7 @@ export declare namespace LRUCache {
* Options provided to {@link LRUCache#fetch} when the FC type is
* `undefined` or `void`
*/
interface FetchOptionsNoContext<K, V, FC extends undefined | void = undefined> extends FetchOptions<K, V, FC> {
interface FetchOptionsNoContext<K, V> extends FetchOptions<K, V, undefined> {
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'> {
@@ -363,7 +303,7 @@ export declare namespace LRUCache {
* be required.
*/
context?: FC;
status?: Status<K, V, FC>;
status?: Status<V>;
}
/**
* Options provided to {@link LRUCache#memo} when the FC type is something
@@ -376,7 +316,7 @@ export declare namespace LRUCache {
* Options provided to {@link LRUCache#memo} when the FC type is
* `undefined` or `void`
*/
interface MemoOptionsNoContext<K, V, FC extends undefined | void = undefined> extends MemoOptions<K, V, FC> {
interface MemoOptionsNoContext<K, V> extends MemoOptions<K, V, undefined> {
context?: undefined;
}
/**
@@ -405,7 +345,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<K, V, FC>;
status?: Status<V>;
size?: Size;
start?: Milliseconds;
}
@@ -413,19 +353,18 @@ 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<K, V, FC>;
status?: Status<V>;
}
/**
* 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<K, V, FC>;
status?: Status<V>;
}
/**
* 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.
@@ -446,7 +385,7 @@ export declare namespace LRUCache {
* method is in use.
*/
start?: Milliseconds;
status?: Status<K, V, FC>;
status?: Status<V>;
}
/**
* The type signature for the {@link OptionsBase.fetchMethod} option.
@@ -699,20 +638,6 @@ 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.
*
@@ -1015,8 +940,6 @@ 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
@@ -1029,7 +952,6 @@ 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)[];
@@ -1039,8 +961,8 @@ export declare class LRUCache<K extends {}, V extends {}, FC = unknown> {
readonly head: Index;
readonly tail: Index;
free: StackLike;
isBackgroundFetch: (p: unknown) => p is BackgroundFetch<V>;
backgroundFetch: (k: K, index: number | undefined, options: LRUCache.FetchOptions<K, V, FC>, context: unknown) => BackgroundFetch<V>;
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?: {
allowStale: boolean;
@@ -1152,12 +1074,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>) => unknown, thisp?: unknown): void;
forEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any): 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>) => unknown, thisp?: unknown): void;
rforEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any): void;
/**
* Delete any stale entries. Returns true if anything was removed,
* false otherwise.
@@ -1230,7 +1152,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 | undefined, setOptions?: LRUCache.SetOptions<K, V, FC>): this;
set(k: K, v: V | BackgroundFetch<V> | undefined, setOptions?: LRUCache.SetOptions<K, V, FC>): this;
/**
* Evict the least recently used item, returning its value or
* `undefined` if cache is empty.
@@ -1346,8 +1268,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, 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>;
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>;
/**
* In some cases, `cache.fetch()` may resolve to `undefined`, either because
* a {@link LRUCache.OptionsBase#fetchMethod} was not provided (turning
@@ -1361,8 +1283,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, 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>;
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>;
/**
* 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
@@ -1377,8 +1299,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, 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;
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;
/**
* 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
+207 -365
View File
@@ -1,27 +1,75 @@
/**
* @module LRUCache
*/
import { metrics, tracing } from './diagnostics-channel.js';
import { defaultPerf } from './perf.js';
const hasSubscribers = () => metrics.hasSubscribers || tracing.hasSubscribers;
const defaultPerf = (typeof performance === 'object' &&
performance &&
typeof performance.now === 'function') ?
performance
: Date;
const warned = new Set();
/* c8 ignore start */
const PROCESS = (typeof process === 'object' && !!process ?
process
: {});
/* c8 ignore stop */
/* c8 ignore start */
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}`);
}
typeof PROCESS.emitWarning === 'function' ?
PROCESS.emitWarning(msg, type, code, fn)
: console.error(`[${code}] ${type}: ${msg}`);
};
let AC = globalThis.AbortController;
let AS = globalThis.AbortSignal;
/* c8 ignore start */
if (typeof AC === 'undefined') {
//@ts-ignore
AS = class AbortSignal {
onabort;
_onabort = [];
reason;
aborted = false;
addEventListener(_, fn) {
this._onabort.push(fn);
}
};
//@ts-ignore
AC = class AbortController {
constructor() {
warnACPolyfill();
}
signal = new AS();
abort(reason) {
if (this.signal.aborted)
return;
//@ts-ignore
this.signal.reason = reason;
//@ts-ignore
this.signal.aborted = true;
//@ts-ignore
for (const fn of this.signal._onabort) {
fn(reason);
}
this.signal.onabort?.(reason);
}
};
let printACPolyfillWarning = PROCESS.env?.LRU_CACHE_IGNORE_AC_WARNING !== '1';
const warnACPolyfill = () => {
if (!printACPolyfillWarning)
return;
printACPolyfillWarning = false;
emitWarning('AbortController is not defined. If using lru-cache in ' +
'node 14, load an AbortController polyfill from the ' +
'`node-abort-controller` package. A minimal polyfill is ' +
'provided for use by LRUCache.fetch(), but it should not be ' +
'relied upon in other contexts (eg, passing it to other APIs that ' +
'use AbortController/AbortSignal might have undesirable effects). ' +
'You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.', 'NO_ABORT_CONTROLLER', 'ENOTSUP', warnACPolyfill);
};
}
/* c8 ignore stop */
const shouldWarn = (code) => !warned.has(code);
const TYPE = Symbol('type');
const isPosInt = (n) => !!n && n === Math.floor(n) && n > 0 && isFinite(n);
const isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n);
/* c8 ignore start */
// This is a little bit ridiculous, tbh.
// The maximum array length is 2^32-1 or thereabouts on most JS impls.
// And well before that point, you're caching the entire world, I mean,
@@ -30,7 +78,6 @@ 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
@@ -45,9 +92,7 @@ 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;
@@ -167,8 +212,6 @@ export class LRUCache {
* {@link LRUCache.OptionsBase.ignoreFetchAbort}
*/
ignoreFetchAbort;
/** {@link LRUCache.OptionsBase.backgroundFetchSize} */
backgroundFetchSize;
// computed properties
#size;
#calculatedSize;
@@ -184,7 +227,6 @@ export class LRUCache {
#sizes;
#starts;
#ttls;
#autopurgeTimers;
#hasDispose;
#hasFetchMethod;
#hasDisposeAfter;
@@ -203,7 +245,6 @@ export class LRUCache {
// properties
starts: c.#starts,
ttls: c.#ttls,
autopurgeTimers: c.#autopurgeTimers,
sizes: c.#sizes,
keyMap: c.#keyMap,
keyList: c.#keyList,
@@ -279,8 +320,7 @@ 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, backgroundFetchSize = 1, perf, } = options;
this.backgroundFetchSize = backgroundFetchSize;
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');
@@ -306,18 +346,20 @@ 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 = Array.from({ length: max }).fill(undefined);
this.#valList = Array.from({ length: max }).fill(undefined);
this.#keyList = new Array(max).fill(undefined);
this.#valList = new Array(max).fill(undefined);
this.#next = new UintArray(max);
this.#prev = new UintArray(max);
this.#head = 0;
@@ -365,7 +407,9 @@ 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) {
@@ -400,56 +444,33 @@ 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;
setPurgetTimer(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 */
}
};
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 start */
if (!ttl || !start) {
/* c8 ignore next */
if (!ttl || !start)
return;
}
/* c8 ignore stop */
status.ttl = ttl;
status.start = start;
status.now = cachedNow || getNow();
@@ -508,15 +529,12 @@ 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');
@@ -559,7 +577,10 @@ export class LRUCache {
};
*#indexes({ allowStale = this.allowStale } = {}) {
if (this.#size) {
for (let i = this.#tail; this.#isValidIndex(i);) {
for (let i = this.#tail; true;) {
if (!this.#isValidIndex(i)) {
break;
}
if (allowStale || !this.#isStale(i)) {
yield i;
}
@@ -574,7 +595,10 @@ export class LRUCache {
}
*#rindexes({ allowStale = this.allowStale } = {}) {
if (this.#size) {
for (let i = this.#head; this.#isValidIndex(i);) {
for (let i = this.#head; true;) {
if (!this.#isValidIndex(i)) {
break;
}
if (allowStale || !this.#isStale(i)) {
yield i;
}
@@ -626,7 +650,8 @@ 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;
}
}
@@ -640,7 +665,8 @@ 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;
}
}
@@ -652,7 +678,8 @@ 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];
}
}
@@ -666,7 +693,8 @@ 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];
}
}
@@ -695,7 +723,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);
}
}
}
@@ -768,7 +796,7 @@ export class LRUCache {
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
if (value === undefined)
return undefined;
/* c8 ignore stop */
/* c8 ignore end */
const entry = { value };
if (this.#ttls && this.#starts) {
const ttl = this.#ttls[i];
@@ -842,7 +870,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);
}
}
/**
@@ -876,43 +904,22 @@ 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;
if (status && !isBF)
status.value = v;
const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation, status);
const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation);
// if the item doesn't fit, don't do anything
// NB: maxEntrySize set to maxSize by default
if (this.maxEntrySize && size > this.maxEntrySize) {
// 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);
@@ -933,68 +940,52 @@ export class LRUCache {
if (status)
status.set = 'add';
noUpdateTTL = false;
if (this.#hasOnInsert && !isBF) {
if (this.#hasOnInsert) {
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 (!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.#hasFetchMethod && this.#isBackgroundFetch(oldVal)) {
oldVal.__abortController.abort(new Error('replaced'));
const { __staleWhileFetching: s } = oldVal;
if (s !== undefined && !noDisposeOnSet) {
if (this.#hasDispose) {
this.#dispose?.(oldVal, k, 'set');
this.#dispose?.(s, k, 'set');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([oldVal, k, 'set']);
this.#disposed?.push([s, 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 (!isBF) {
if (status) {
status.set = 'replace';
const oldValue = oldVal && this.#isBackgroundFetch(oldVal) ?
oldVal.__staleWhileFetching
: oldVal;
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);
}
if (oldValue !== undefined)
status.oldValue = oldValue;
}
}
else if (!isBF) {
if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, 'update');
}
else if (status) {
status.set = 'update';
}
if (this.#hasOnInsert) {
this.onInsert?.(v, k, v === oldVal ? 'update' : 'replace');
}
}
if (ttl !== 0 && !this.#ttls) {
@@ -1049,25 +1040,18 @@ export class LRUCache {
const head = this.#head;
const k = this.#keyList[head];
const v = this.#valList[head];
const isBF = this.#isBackgroundFetch(v);
if (isBF) {
if (this.#hasFetchMethod && this.#isBackgroundFetch(v)) {
v.__abortController.abort(new Error('evicted'));
}
const oldValue = isBF ? v.__staleWhileFetching : v;
if ((this.#hasDispose || this.#hasDisposeAfter) &&
oldValue !== undefined) {
else if (this.#hasDispose || this.#hasDisposeAfter) {
if (this.#hasDispose) {
this.#dispose?.(oldValue, k, 'evict');
this.#dispose?.(v, k, 'evict');
}
if (this.#hasDisposeAfter) {
this.#disposed?.push([oldValue, k, 'evict']);
this.#disposed?.push([v, 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;
@@ -1102,19 +1086,6 @@ 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) {
@@ -1151,46 +1122,22 @@ export class LRUCache {
* {@link LRUCache.OptionsBase.allowStale} is set.
*/
peek(k, 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 { allowStale = this.allowStale } = peekOptions;
const index = this.#keyMap.get(k);
if (index === undefined || (!allowStale && this.#isStale(index))) {
if (status)
status.peek = index === undefined ? 'miss' : 'stale';
return undefined;
if (index === undefined ||
(!allowStale && this.#isStale(index))) {
return;
}
const v = this.#valList[index];
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;
// either stale and allowed, or forcing a refresh of non-stale value
return this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
}
#backgroundFetch(k, index, options, context) {
const v = index === undefined ? undefined : this.#valList[index];
if (this.#isBackgroundFetch(v)) {
return v;
}
const ac = new AbortController();
const ac = new AC();
const { signal } = options;
// when/if our AC signals, then stop listening to theirs.
signal?.addEventListener('abort', () => ac.abort(signal.reason), {
@@ -1204,8 +1151,6 @@ 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;
@@ -1218,7 +1163,7 @@ export class LRUCache {
}
}
if (aborted && !ignoreAbort && !updateCache) {
return fetchFail(ac.signal.reason, proceed);
return fetchFail(ac.signal.reason);
}
// either we didn't abort, and are still here, or we did, and ignored
const bf = p;
@@ -1226,7 +1171,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 || (vl === undefined && ignoreAbort && updateCache)) {
if (vl === p || ignoreAbort && updateCache && vl === undefined) {
if (v === undefined) {
if (bf.__staleWhileFetching !== undefined) {
this.#valList[index] = bf.__staleWhileFetching;
@@ -1238,7 +1183,7 @@ export class LRUCache {
else {
if (options.status)
options.status.fetchUpdated = true;
this.#set(k, v, fetchOpts.options, bf);
this.set(k, v, fetchOpts.options);
}
}
return v;
@@ -1248,10 +1193,9 @@ export class LRUCache {
options.status.fetchRejected = true;
options.status.fetchError = er;
}
// do not pass go, do not collect $200
return fetchFail(er, false);
return fetchFail(er);
};
const fetchFail = (er, proceed) => {
const fetchFail = (er) => {
const { aborted } = ac.signal;
const allowStaleAborted = aborted && options.allowStaleOnFetchAbort;
const allowStale = allowStaleAborted || options.allowStaleOnFetchRejection;
@@ -1260,7 +1204,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 || (!proceed && bf.__staleWhileFetching === undefined);
const del = !noDelete || bf.__staleWhileFetching === undefined;
if (del) {
this.#delete(k, 'fetch');
}
@@ -1284,11 +1228,15 @@ 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) {
@@ -1296,12 +1244,6 @@ 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;
@@ -1313,14 +1255,10 @@ 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;
@@ -1332,23 +1270,9 @@ export class LRUCache {
return (!!b &&
b instanceof Promise &&
b.hasOwnProperty('__staleWhileFetching') &&
b.__abortController instanceof AbortController);
b.__abortController instanceof AC);
}
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 = {}) {
async fetch(k, fetchOptions = {}) {
const {
// get options
allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet,
@@ -1356,17 +1280,10 @@ 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,
@@ -1435,68 +1352,26 @@ export class LRUCache {
return staleVal ? p.__staleWhileFetching : (p.__returned = p);
}
}
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);
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, 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)
const { context, forceRefresh, ...options } = memoOptions;
const v = this.get(k, options);
if (!forceRefresh && v !== undefined)
return v;
const vv = memoMethod(k, v, {
options,
context,
});
if (status)
status.value = vv;
this.#set(k, vv, options);
this.set(k, vv, options);
return vv;
}
/**
@@ -1506,71 +1381,55 @@ 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) {
if (index !== undefined) {
const value = this.#valList[index];
const fetching = this.#isBackgroundFetch(value);
if (status)
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');
}
this.#statusTTL(status, index);
if (this.#isStale(index)) {
if (status)
status.get = 'stale';
if (allowStale) {
if (status)
// delete only if not an in-flight background fetch
if (!fetching) {
if (!noDeleteOnStaleGet) {
this.#delete(k, 'expire');
}
if (status && allowStale)
status.returnedStale = true;
return value;
return allowStale ? value : undefined;
}
else {
if (status &&
allowStale &&
value.__staleWhileFetching !== undefined) {
status.returnedStale = true;
}
return allowStale ? value.__staleWhileFetching : undefined;
}
return undefined;
}
if (status)
status.get = 'stale-fetching';
if (allowStale && value.__staleWhileFetching !== undefined) {
else {
if (status)
status.returnedStale = true;
return value.__staleWhileFetching;
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;
}
return undefined;
}
// 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);
else if (status) {
status.get = 'miss';
}
return fetching ? value.__staleWhileFetching : value;
}
#connect(p, n) {
this.#prev[n] = p;
@@ -1605,22 +1464,10 @@ 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);
@@ -1691,16 +1538,11 @@ export class LRUCache {
}
}
this.#keyMap.clear();
void this.#valList.fill(undefined);
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
+38 -79
View File
@@ -1,7 +1,7 @@
{
"name": "lru-cache",
"description": "A cache object that deletes the least-recently-used items.",
"version": "11.5.0",
"version": "11.2.2",
"author": "Isaac Z. Schlueter <i@izs.me>",
"keywords": [
"mru",
@@ -11,7 +11,7 @@
"sideEffects": false,
"scripts": {
"build": "npm run prepare",
"prepare": "tshy && bash scripts/build.sh",
"prepare": "tshy && bash fixup.sh",
"pretest": "npm run prepare",
"presnap": "npm run prepare",
"test": "tap",
@@ -25,130 +25,89 @@
"prebenchmark": "npm run prepare",
"benchmark": "make -C benchmark",
"preprofile": "npm run prepare",
"profile": "make -C benchmark profile",
"lint": "oxlint --fix src test",
"postsnap": "npm run lint",
"postlint": "npm run format"
"profile": "make -C benchmark profile"
},
"main": "./dist/commonjs/index.min.js",
"main": "./dist/commonjs/index.js",
"types": "./dist/commonjs/index.d.ts",
"tshy": {
"esmDialects": [
"browser",
"node"
],
"commonjsDialects": [
"browser",
"node"
],
"exports": {
"./raw": "./src/index.ts",
".": {
".": "./src/index.ts",
"./min": {
"import": {
"browser": {
"types": "./dist/esm/browser/index.d.ts",
"default": "./dist/esm/browser/index.min.js"
},
"node": {
"types": "./dist/esm/node/index.d.ts",
"default": "./dist/esm/node/index.min.js"
},
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.min.js"
},
"require": {
"browser": {
"types": "./dist/commonjs/browser/index.d.ts",
"default": "./dist/commonjs/browser/index.min.js"
},
"node": {
"types": "./dist/commonjs/node/index.d.ts",
"default": "./dist/commonjs/node/index.min.js"
},
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.min.js"
}
}
},
"selfLink": false
}
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/isaacs/node-lru-cache.git"
"url": "git://github.com/isaacs/node-lru-cache.git"
},
"devDependencies": {
"@types/node": "^24.3.0",
"benchmark": "^2.1.4",
"esbuild": "^0.28.0",
"esbuild": "^0.25.9",
"marked": "^4.2.12",
"mkdirp": "^3.0.1",
"oxlint": "^1.65.0",
"oxlint-tsgolint": "^0.22.1",
"prettier": "^3.8.3",
"tap": "^21.7.4",
"tshy": "^4.1.2",
"typedoc": "^0.28.19"
"prettier": "^3.6.2",
"tap": "^21.1.0",
"tshy": "^3.0.2",
"typedoc": "^0.28.12"
},
"license": "BlueOak-1.0.0",
"license": "ISC",
"files": [
"dist"
],
"engines": {
"node": "20 || >=22"
},
"prettier": {
"experimentalTernaries": true,
"semi": false,
"printWidth": 70,
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"jsxSingleQuote": false,
"bracketSameLine": true,
"arrowParens": "avoid",
"endOfLine": "lf"
},
"tap": {
"node-arg": [
"--expose-gc"
],
"plugin": [
"@tapjs/clock"
]
},
"exports": {
"./raw": {
".": {
"import": {
"browser": {
"types": "./dist/esm/browser/index.d.ts",
"default": "./dist/esm/browser/index.js"
},
"node": {
"types": "./dist/esm/node/index.d.ts",
"default": "./dist/esm/node/index.js"
},
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"browser": {
"types": "./dist/commonjs/browser/index.d.ts",
"default": "./dist/commonjs/browser/index.js"
},
"node": {
"types": "./dist/commonjs/node/index.d.ts",
"default": "./dist/commonjs/node/index.js"
},
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
},
".": {
"./min": {
"import": {
"browser": {
"types": "./dist/esm/browser/index.d.ts",
"default": "./dist/esm/browser/index.min.js"
},
"node": {
"types": "./dist/esm/node/index.d.ts",
"default": "./dist/esm/node/index.min.js"
},
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.min.js"
},
"require": {
"browser": {
"types": "./dist/commonjs/browser/index.d.ts",
"default": "./dist/commonjs/browser/index.min.js"
},
"node": {
"types": "./dist/commonjs/node/index.d.ts",
"default": "./dist/commonjs/node/index.min.js"
},
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.min.js"
}
}
},
"type": "module",
"module": "./dist/esm/index.min.js"
"module": "./dist/esm/index.js"
}
+17 -13
View File
@@ -1,6 +1,6 @@
{
"name": "hosted-git-info",
"version": "9.0.3",
"version": "9.0.2",
"description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab",
"main": "./lib/index.js",
"repository": {
@@ -21,23 +21,22 @@
"homepage": "https://github.com/npm/hosted-git-info",
"scripts": {
"posttest": "npm run lint",
"snap": "node --test --test-update-snapshots './test/**/*.js'",
"test": "node --test './test/**/*.js'",
"snap": "tap",
"test": "tap",
"test:coverage": "tap --coverage-report=html",
"lint": "npm run eslint",
"postlint": "template-oss-check",
"lintfix": "npm run eslint -- --fix",
"template-oss-apply": "template-oss-apply --force",
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"",
"test:node20": "node --test test",
"test:cover": "node --test --experimental-test-coverage --test-timeout=3000 --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=100 './test/**/*.js'"
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
},
"dependencies": {
"lru-cache": "^11.1.0"
},
"devDependencies": {
"@npmcli/eslint-config": "^6.0.0",
"@npmcli/template-oss": "4.30.0"
"@npmcli/eslint-config": "^5.0.0",
"@npmcli/template-oss": "4.25.1",
"tap": "^16.0.1"
},
"files": [
"bin/",
@@ -46,12 +45,17 @@
"engines": {
"node": "^20.17.0 || >=22.9.0"
},
"tap": {
"color": 1,
"coverage": true,
"nyc-arg": [
"--exclude",
"tap-snapshots/**"
]
},
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.30.0",
"publish": "true",
"testRunner": "node:test",
"latestCiVersion": 24,
"updateNpm": false
"version": "4.25.1",
"publish": "true"
}
}