index.js 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546
  1. "use strict";
  2. /**
  3. * @module LRUCache
  4. */
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.LRUCache = void 0;
  7. const perf = typeof performance === 'object' &&
  8. performance &&
  9. typeof performance.now === 'function'
  10. ? performance
  11. : Date;
  12. const warned = new Set();
  13. /* c8 ignore start */
  14. const PROCESS = (typeof process === 'object' && !!process ? process : {});
  15. /* c8 ignore start */
  16. const emitWarning = (msg, type, code, fn) => {
  17. typeof PROCESS.emitWarning === 'function'
  18. ? PROCESS.emitWarning(msg, type, code, fn)
  19. : console.error(`[${code}] ${type}: ${msg}`);
  20. };
  21. let AC = globalThis.AbortController;
  22. let AS = globalThis.AbortSignal;
  23. /* c8 ignore start */
  24. if (typeof AC === 'undefined') {
  25. //@ts-ignore
  26. AS = class AbortSignal {
  27. onabort;
  28. _onabort = [];
  29. reason;
  30. aborted = false;
  31. addEventListener(_, fn) {
  32. this._onabort.push(fn);
  33. }
  34. };
  35. //@ts-ignore
  36. AC = class AbortController {
  37. constructor() {
  38. warnACPolyfill();
  39. }
  40. signal = new AS();
  41. abort(reason) {
  42. if (this.signal.aborted)
  43. return;
  44. //@ts-ignore
  45. this.signal.reason = reason;
  46. //@ts-ignore
  47. this.signal.aborted = true;
  48. //@ts-ignore
  49. for (const fn of this.signal._onabort) {
  50. fn(reason);
  51. }
  52. this.signal.onabort?.(reason);
  53. }
  54. };
  55. let printACPolyfillWarning = PROCESS.env?.LRU_CACHE_IGNORE_AC_WARNING !== '1';
  56. const warnACPolyfill = () => {
  57. if (!printACPolyfillWarning)
  58. return;
  59. printACPolyfillWarning = false;
  60. emitWarning('AbortController is not defined. If using lru-cache in ' +
  61. 'node 14, load an AbortController polyfill from the ' +
  62. '`node-abort-controller` package. A minimal polyfill is ' +
  63. 'provided for use by LRUCache.fetch(), but it should not be ' +
  64. 'relied upon in other contexts (eg, passing it to other APIs that ' +
  65. 'use AbortController/AbortSignal might have undesirable effects). ' +
  66. 'You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.', 'NO_ABORT_CONTROLLER', 'ENOTSUP', warnACPolyfill);
  67. };
  68. }
  69. /* c8 ignore stop */
  70. const shouldWarn = (code) => !warned.has(code);
  71. const TYPE = Symbol('type');
  72. const isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n);
  73. /* c8 ignore start */
  74. // This is a little bit ridiculous, tbh.
  75. // The maximum array length is 2^32-1 or thereabouts on most JS impls.
  76. // And well before that point, you're caching the entire world, I mean,
  77. // that's ~32GB of just integers for the next/prev links, plus whatever
  78. // else to hold that many keys and values. Just filling the memory with
  79. // zeroes at init time is brutal when you get that big.
  80. // But why not be complete?
  81. // Maybe in the future, these limits will have expanded.
  82. const getUintArray = (max) => !isPosInt(max)
  83. ? null
  84. : max <= Math.pow(2, 8)
  85. ? Uint8Array
  86. : max <= Math.pow(2, 16)
  87. ? Uint16Array
  88. : max <= Math.pow(2, 32)
  89. ? Uint32Array
  90. : max <= Number.MAX_SAFE_INTEGER
  91. ? ZeroArray
  92. : null;
  93. /* c8 ignore stop */
  94. class ZeroArray extends Array {
  95. constructor(size) {
  96. super(size);
  97. this.fill(0);
  98. }
  99. }
  100. class Stack {
  101. heap;
  102. length;
  103. // private constructor
  104. static #constructing = false;
  105. static create(max) {
  106. const HeapCls = getUintArray(max);
  107. if (!HeapCls)
  108. return [];
  109. Stack.#constructing = true;
  110. const s = new Stack(max, HeapCls);
  111. Stack.#constructing = false;
  112. return s;
  113. }
  114. constructor(max, HeapCls) {
  115. /* c8 ignore start */
  116. if (!Stack.#constructing) {
  117. throw new TypeError('instantiate Stack using Stack.create(n)');
  118. }
  119. /* c8 ignore stop */
  120. this.heap = new HeapCls(max);
  121. this.length = 0;
  122. }
  123. push(n) {
  124. this.heap[this.length++] = n;
  125. }
  126. pop() {
  127. return this.heap[--this.length];
  128. }
  129. }
  130. /**
  131. * Default export, the thing you're using this module to get.
  132. *
  133. * The `K` and `V` types define the key and value types, respectively. The
  134. * optional `FC` type defines the type of the `context` object passed to
  135. * `cache.fetch()` and `cache.memo()`.
  136. *
  137. * Keys and values **must not** be `null` or `undefined`.
  138. *
  139. * All properties from the options object (with the exception of `max`,
  140. * `maxSize`, `fetchMethod`, `memoMethod`, `dispose` and `disposeAfter`) are
  141. * added as normal public members. (The listed options are read-only getters.)
  142. *
  143. * Changing any of these will alter the defaults for subsequent method calls.
  144. */
  145. class LRUCache {
  146. // options that cannot be changed without disaster
  147. #max;
  148. #maxSize;
  149. #dispose;
  150. #disposeAfter;
  151. #fetchMethod;
  152. #memoMethod;
  153. /**
  154. * {@link LRUCache.OptionsBase.ttl}
  155. */
  156. ttl;
  157. /**
  158. * {@link LRUCache.OptionsBase.ttlResolution}
  159. */
  160. ttlResolution;
  161. /**
  162. * {@link LRUCache.OptionsBase.ttlAutopurge}
  163. */
  164. ttlAutopurge;
  165. /**
  166. * {@link LRUCache.OptionsBase.updateAgeOnGet}
  167. */
  168. updateAgeOnGet;
  169. /**
  170. * {@link LRUCache.OptionsBase.updateAgeOnHas}
  171. */
  172. updateAgeOnHas;
  173. /**
  174. * {@link LRUCache.OptionsBase.allowStale}
  175. */
  176. allowStale;
  177. /**
  178. * {@link LRUCache.OptionsBase.noDisposeOnSet}
  179. */
  180. noDisposeOnSet;
  181. /**
  182. * {@link LRUCache.OptionsBase.noUpdateTTL}
  183. */
  184. noUpdateTTL;
  185. /**
  186. * {@link LRUCache.OptionsBase.maxEntrySize}
  187. */
  188. maxEntrySize;
  189. /**
  190. * {@link LRUCache.OptionsBase.sizeCalculation}
  191. */
  192. sizeCalculation;
  193. /**
  194. * {@link LRUCache.OptionsBase.noDeleteOnFetchRejection}
  195. */
  196. noDeleteOnFetchRejection;
  197. /**
  198. * {@link LRUCache.OptionsBase.noDeleteOnStaleGet}
  199. */
  200. noDeleteOnStaleGet;
  201. /**
  202. * {@link LRUCache.OptionsBase.allowStaleOnFetchAbort}
  203. */
  204. allowStaleOnFetchAbort;
  205. /**
  206. * {@link LRUCache.OptionsBase.allowStaleOnFetchRejection}
  207. */
  208. allowStaleOnFetchRejection;
  209. /**
  210. * {@link LRUCache.OptionsBase.ignoreFetchAbort}
  211. */
  212. ignoreFetchAbort;
  213. // computed properties
  214. #size;
  215. #calculatedSize;
  216. #keyMap;
  217. #keyList;
  218. #valList;
  219. #next;
  220. #prev;
  221. #head;
  222. #tail;
  223. #free;
  224. #disposed;
  225. #sizes;
  226. #starts;
  227. #ttls;
  228. #hasDispose;
  229. #hasFetchMethod;
  230. #hasDisposeAfter;
  231. /**
  232. * Do not call this method unless you need to inspect the
  233. * inner workings of the cache. If anything returned by this
  234. * object is modified in any way, strange breakage may occur.
  235. *
  236. * These fields are private for a reason!
  237. *
  238. * @internal
  239. */
  240. static unsafeExposeInternals(c) {
  241. return {
  242. // properties
  243. starts: c.#starts,
  244. ttls: c.#ttls,
  245. sizes: c.#sizes,
  246. keyMap: c.#keyMap,
  247. keyList: c.#keyList,
  248. valList: c.#valList,
  249. next: c.#next,
  250. prev: c.#prev,
  251. get head() {
  252. return c.#head;
  253. },
  254. get tail() {
  255. return c.#tail;
  256. },
  257. free: c.#free,
  258. // methods
  259. isBackgroundFetch: (p) => c.#isBackgroundFetch(p),
  260. backgroundFetch: (k, index, options, context) => c.#backgroundFetch(k, index, options, context),
  261. moveToTail: (index) => c.#moveToTail(index),
  262. indexes: (options) => c.#indexes(options),
  263. rindexes: (options) => c.#rindexes(options),
  264. isStale: (index) => c.#isStale(index),
  265. };
  266. }
  267. // Protected read-only members
  268. /**
  269. * {@link LRUCache.OptionsBase.max} (read-only)
  270. */
  271. get max() {
  272. return this.#max;
  273. }
  274. /**
  275. * {@link LRUCache.OptionsBase.maxSize} (read-only)
  276. */
  277. get maxSize() {
  278. return this.#maxSize;
  279. }
  280. /**
  281. * The total computed size of items in the cache (read-only)
  282. */
  283. get calculatedSize() {
  284. return this.#calculatedSize;
  285. }
  286. /**
  287. * The number of items stored in the cache (read-only)
  288. */
  289. get size() {
  290. return this.#size;
  291. }
  292. /**
  293. * {@link LRUCache.OptionsBase.fetchMethod} (read-only)
  294. */
  295. get fetchMethod() {
  296. return this.#fetchMethod;
  297. }
  298. get memoMethod() {
  299. return this.#memoMethod;
  300. }
  301. /**
  302. * {@link LRUCache.OptionsBase.dispose} (read-only)
  303. */
  304. get dispose() {
  305. return this.#dispose;
  306. }
  307. /**
  308. * {@link LRUCache.OptionsBase.disposeAfter} (read-only)
  309. */
  310. get disposeAfter() {
  311. return this.#disposeAfter;
  312. }
  313. constructor(options) {
  314. const { max = 0, ttl, ttlResolution = 1, ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0, maxEntrySize = 0, sizeCalculation, fetchMethod, memoMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, } = options;
  315. if (max !== 0 && !isPosInt(max)) {
  316. throw new TypeError('max option must be a nonnegative integer');
  317. }
  318. const UintArray = max ? getUintArray(max) : Array;
  319. if (!UintArray) {
  320. throw new Error('invalid max value: ' + max);
  321. }
  322. this.#max = max;
  323. this.#maxSize = maxSize;
  324. this.maxEntrySize = maxEntrySize || this.#maxSize;
  325. this.sizeCalculation = sizeCalculation;
  326. if (this.sizeCalculation) {
  327. if (!this.#maxSize && !this.maxEntrySize) {
  328. throw new TypeError('cannot set sizeCalculation without setting maxSize or maxEntrySize');
  329. }
  330. if (typeof this.sizeCalculation !== 'function') {
  331. throw new TypeError('sizeCalculation set to non-function');
  332. }
  333. }
  334. if (memoMethod !== undefined &&
  335. typeof memoMethod !== 'function') {
  336. throw new TypeError('memoMethod must be a function if defined');
  337. }
  338. this.#memoMethod = memoMethod;
  339. if (fetchMethod !== undefined &&
  340. typeof fetchMethod !== 'function') {
  341. throw new TypeError('fetchMethod must be a function if specified');
  342. }
  343. this.#fetchMethod = fetchMethod;
  344. this.#hasFetchMethod = !!fetchMethod;
  345. this.#keyMap = new Map();
  346. this.#keyList = new Array(max).fill(undefined);
  347. this.#valList = new Array(max).fill(undefined);
  348. this.#next = new UintArray(max);
  349. this.#prev = new UintArray(max);
  350. this.#head = 0;
  351. this.#tail = 0;
  352. this.#free = Stack.create(max);
  353. this.#size = 0;
  354. this.#calculatedSize = 0;
  355. if (typeof dispose === 'function') {
  356. this.#dispose = dispose;
  357. }
  358. if (typeof disposeAfter === 'function') {
  359. this.#disposeAfter = disposeAfter;
  360. this.#disposed = [];
  361. }
  362. else {
  363. this.#disposeAfter = undefined;
  364. this.#disposed = undefined;
  365. }
  366. this.#hasDispose = !!this.#dispose;
  367. this.#hasDisposeAfter = !!this.#disposeAfter;
  368. this.noDisposeOnSet = !!noDisposeOnSet;
  369. this.noUpdateTTL = !!noUpdateTTL;
  370. this.noDeleteOnFetchRejection = !!noDeleteOnFetchRejection;
  371. this.allowStaleOnFetchRejection = !!allowStaleOnFetchRejection;
  372. this.allowStaleOnFetchAbort = !!allowStaleOnFetchAbort;
  373. this.ignoreFetchAbort = !!ignoreFetchAbort;
  374. // NB: maxEntrySize is set to maxSize if it's set
  375. if (this.maxEntrySize !== 0) {
  376. if (this.#maxSize !== 0) {
  377. if (!isPosInt(this.#maxSize)) {
  378. throw new TypeError('maxSize must be a positive integer if specified');
  379. }
  380. }
  381. if (!isPosInt(this.maxEntrySize)) {
  382. throw new TypeError('maxEntrySize must be a positive integer if specified');
  383. }
  384. this.#initializeSizeTracking();
  385. }
  386. this.allowStale = !!allowStale;
  387. this.noDeleteOnStaleGet = !!noDeleteOnStaleGet;
  388. this.updateAgeOnGet = !!updateAgeOnGet;
  389. this.updateAgeOnHas = !!updateAgeOnHas;
  390. this.ttlResolution =
  391. isPosInt(ttlResolution) || ttlResolution === 0
  392. ? ttlResolution
  393. : 1;
  394. this.ttlAutopurge = !!ttlAutopurge;
  395. this.ttl = ttl || 0;
  396. if (this.ttl) {
  397. if (!isPosInt(this.ttl)) {
  398. throw new TypeError('ttl must be a positive integer if specified');
  399. }
  400. this.#initializeTTLTracking();
  401. }
  402. // do not allow completely unbounded caches
  403. if (this.#max === 0 && this.ttl === 0 && this.#maxSize === 0) {
  404. throw new TypeError('At least one of max, maxSize, or ttl is required');
  405. }
  406. if (!this.ttlAutopurge && !this.#max && !this.#maxSize) {
  407. const code = 'LRU_CACHE_UNBOUNDED';
  408. if (shouldWarn(code)) {
  409. warned.add(code);
  410. const msg = 'TTL caching without ttlAutopurge, max, or maxSize can ' +
  411. 'result in unbounded memory consumption.';
  412. emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache);
  413. }
  414. }
  415. }
  416. /**
  417. * Return the number of ms left in the item's TTL. If item is not in cache,
  418. * returns `0`. Returns `Infinity` if item is in cache without a defined TTL.
  419. */
  420. getRemainingTTL(key) {
  421. return this.#keyMap.has(key) ? Infinity : 0;
  422. }
  423. #initializeTTLTracking() {
  424. const ttls = new ZeroArray(this.#max);
  425. const starts = new ZeroArray(this.#max);
  426. this.#ttls = ttls;
  427. this.#starts = starts;
  428. this.#setItemTTL = (index, ttl, start = perf.now()) => {
  429. starts[index] = ttl !== 0 ? start : 0;
  430. ttls[index] = ttl;
  431. if (ttl !== 0 && this.ttlAutopurge) {
  432. const t = setTimeout(() => {
  433. if (this.#isStale(index)) {
  434. this.#delete(this.#keyList[index], 'expire');
  435. }
  436. }, ttl + 1);
  437. // unref() not supported on all platforms
  438. /* c8 ignore start */
  439. if (t.unref) {
  440. t.unref();
  441. }
  442. /* c8 ignore stop */
  443. }
  444. };
  445. this.#updateItemAge = index => {
  446. starts[index] = ttls[index] !== 0 ? perf.now() : 0;
  447. };
  448. this.#statusTTL = (status, index) => {
  449. if (ttls[index]) {
  450. const ttl = ttls[index];
  451. const start = starts[index];
  452. /* c8 ignore next */
  453. if (!ttl || !start)
  454. return;
  455. status.ttl = ttl;
  456. status.start = start;
  457. status.now = cachedNow || getNow();
  458. const age = status.now - start;
  459. status.remainingTTL = ttl - age;
  460. }
  461. };
  462. // debounce calls to perf.now() to 1s so we're not hitting
  463. // that costly call repeatedly.
  464. let cachedNow = 0;
  465. const getNow = () => {
  466. const n = perf.now();
  467. if (this.ttlResolution > 0) {
  468. cachedNow = n;
  469. const t = setTimeout(() => (cachedNow = 0), this.ttlResolution);
  470. // not available on all platforms
  471. /* c8 ignore start */
  472. if (t.unref) {
  473. t.unref();
  474. }
  475. /* c8 ignore stop */
  476. }
  477. return n;
  478. };
  479. this.getRemainingTTL = key => {
  480. const index = this.#keyMap.get(key);
  481. if (index === undefined) {
  482. return 0;
  483. }
  484. const ttl = ttls[index];
  485. const start = starts[index];
  486. if (!ttl || !start) {
  487. return Infinity;
  488. }
  489. const age = (cachedNow || getNow()) - start;
  490. return ttl - age;
  491. };
  492. this.#isStale = index => {
  493. const s = starts[index];
  494. const t = ttls[index];
  495. return !!t && !!s && (cachedNow || getNow()) - s > t;
  496. };
  497. }
  498. // conditionally set private methods related to TTL
  499. #updateItemAge = () => { };
  500. #statusTTL = () => { };
  501. #setItemTTL = () => { };
  502. /* c8 ignore stop */
  503. #isStale = () => false;
  504. #initializeSizeTracking() {
  505. const sizes = new ZeroArray(this.#max);
  506. this.#calculatedSize = 0;
  507. this.#sizes = sizes;
  508. this.#removeItemSize = index => {
  509. this.#calculatedSize -= sizes[index];
  510. sizes[index] = 0;
  511. };
  512. this.#requireSize = (k, v, size, sizeCalculation) => {
  513. // provisionally accept background fetches.
  514. // actual value size will be checked when they return.
  515. if (this.#isBackgroundFetch(v)) {
  516. return 0;
  517. }
  518. if (!isPosInt(size)) {
  519. if (sizeCalculation) {
  520. if (typeof sizeCalculation !== 'function') {
  521. throw new TypeError('sizeCalculation must be a function');
  522. }
  523. size = sizeCalculation(v, k);
  524. if (!isPosInt(size)) {
  525. throw new TypeError('sizeCalculation return invalid (expect positive integer)');
  526. }
  527. }
  528. else {
  529. throw new TypeError('invalid size value (must be positive integer). ' +
  530. 'When maxSize or maxEntrySize is used, sizeCalculation ' +
  531. 'or size must be set.');
  532. }
  533. }
  534. return size;
  535. };
  536. this.#addItemSize = (index, size, status) => {
  537. sizes[index] = size;
  538. if (this.#maxSize) {
  539. const maxSize = this.#maxSize - sizes[index];
  540. while (this.#calculatedSize > maxSize) {
  541. this.#evict(true);
  542. }
  543. }
  544. this.#calculatedSize += sizes[index];
  545. if (status) {
  546. status.entrySize = size;
  547. status.totalCalculatedSize = this.#calculatedSize;
  548. }
  549. };
  550. }
  551. #removeItemSize = _i => { };
  552. #addItemSize = (_i, _s, _st) => { };
  553. #requireSize = (_k, _v, size, sizeCalculation) => {
  554. if (size || sizeCalculation) {
  555. throw new TypeError('cannot set size without setting maxSize or maxEntrySize on cache');
  556. }
  557. return 0;
  558. };
  559. *#indexes({ allowStale = this.allowStale } = {}) {
  560. if (this.#size) {
  561. for (let i = this.#tail; true;) {
  562. if (!this.#isValidIndex(i)) {
  563. break;
  564. }
  565. if (allowStale || !this.#isStale(i)) {
  566. yield i;
  567. }
  568. if (i === this.#head) {
  569. break;
  570. }
  571. else {
  572. i = this.#prev[i];
  573. }
  574. }
  575. }
  576. }
  577. *#rindexes({ allowStale = this.allowStale } = {}) {
  578. if (this.#size) {
  579. for (let i = this.#head; true;) {
  580. if (!this.#isValidIndex(i)) {
  581. break;
  582. }
  583. if (allowStale || !this.#isStale(i)) {
  584. yield i;
  585. }
  586. if (i === this.#tail) {
  587. break;
  588. }
  589. else {
  590. i = this.#next[i];
  591. }
  592. }
  593. }
  594. }
  595. #isValidIndex(index) {
  596. return (index !== undefined &&
  597. this.#keyMap.get(this.#keyList[index]) === index);
  598. }
  599. /**
  600. * Return a generator yielding `[key, value]` pairs,
  601. * in order from most recently used to least recently used.
  602. */
  603. *entries() {
  604. for (const i of this.#indexes()) {
  605. if (this.#valList[i] !== undefined &&
  606. this.#keyList[i] !== undefined &&
  607. !this.#isBackgroundFetch(this.#valList[i])) {
  608. yield [this.#keyList[i], this.#valList[i]];
  609. }
  610. }
  611. }
  612. /**
  613. * Inverse order version of {@link LRUCache.entries}
  614. *
  615. * Return a generator yielding `[key, value]` pairs,
  616. * in order from least recently used to most recently used.
  617. */
  618. *rentries() {
  619. for (const i of this.#rindexes()) {
  620. if (this.#valList[i] !== undefined &&
  621. this.#keyList[i] !== undefined &&
  622. !this.#isBackgroundFetch(this.#valList[i])) {
  623. yield [this.#keyList[i], this.#valList[i]];
  624. }
  625. }
  626. }
  627. /**
  628. * Return a generator yielding the keys in the cache,
  629. * in order from most recently used to least recently used.
  630. */
  631. *keys() {
  632. for (const i of this.#indexes()) {
  633. const k = this.#keyList[i];
  634. if (k !== undefined &&
  635. !this.#isBackgroundFetch(this.#valList[i])) {
  636. yield k;
  637. }
  638. }
  639. }
  640. /**
  641. * Inverse order version of {@link LRUCache.keys}
  642. *
  643. * Return a generator yielding the keys in the cache,
  644. * in order from least recently used to most recently used.
  645. */
  646. *rkeys() {
  647. for (const i of this.#rindexes()) {
  648. const k = this.#keyList[i];
  649. if (k !== undefined &&
  650. !this.#isBackgroundFetch(this.#valList[i])) {
  651. yield k;
  652. }
  653. }
  654. }
  655. /**
  656. * Return a generator yielding the values in the cache,
  657. * in order from most recently used to least recently used.
  658. */
  659. *values() {
  660. for (const i of this.#indexes()) {
  661. const v = this.#valList[i];
  662. if (v !== undefined &&
  663. !this.#isBackgroundFetch(this.#valList[i])) {
  664. yield this.#valList[i];
  665. }
  666. }
  667. }
  668. /**
  669. * Inverse order version of {@link LRUCache.values}
  670. *
  671. * Return a generator yielding the values in the cache,
  672. * in order from least recently used to most recently used.
  673. */
  674. *rvalues() {
  675. for (const i of this.#rindexes()) {
  676. const v = this.#valList[i];
  677. if (v !== undefined &&
  678. !this.#isBackgroundFetch(this.#valList[i])) {
  679. yield this.#valList[i];
  680. }
  681. }
  682. }
  683. /**
  684. * Iterating over the cache itself yields the same results as
  685. * {@link LRUCache.entries}
  686. */
  687. [Symbol.iterator]() {
  688. return this.entries();
  689. }
  690. /**
  691. * A String value that is used in the creation of the default string
  692. * description of an object. Called by the built-in method
  693. * `Object.prototype.toString`.
  694. */
  695. [Symbol.toStringTag] = 'LRUCache';
  696. /**
  697. * Find a value for which the supplied fn method returns a truthy value,
  698. * similar to `Array.find()`. fn is called as `fn(value, key, cache)`.
  699. */
  700. find(fn, getOptions = {}) {
  701. for (const i of this.#indexes()) {
  702. const v = this.#valList[i];
  703. const value = this.#isBackgroundFetch(v)
  704. ? v.__staleWhileFetching
  705. : v;
  706. if (value === undefined)
  707. continue;
  708. if (fn(value, this.#keyList[i], this)) {
  709. return this.get(this.#keyList[i], getOptions);
  710. }
  711. }
  712. }
  713. /**
  714. * Call the supplied function on each item in the cache, in order from most
  715. * recently used to least recently used.
  716. *
  717. * `fn` is called as `fn(value, key, cache)`.
  718. *
  719. * If `thisp` is provided, function will be called in the `this`-context of
  720. * the provided object, or the cache if no `thisp` object is provided.
  721. *
  722. * Does not update age or recenty of use, or iterate over stale values.
  723. */
  724. forEach(fn, thisp = this) {
  725. for (const i of this.#indexes()) {
  726. const v = this.#valList[i];
  727. const value = this.#isBackgroundFetch(v)
  728. ? v.__staleWhileFetching
  729. : v;
  730. if (value === undefined)
  731. continue;
  732. fn.call(thisp, value, this.#keyList[i], this);
  733. }
  734. }
  735. /**
  736. * The same as {@link LRUCache.forEach} but items are iterated over in
  737. * reverse order. (ie, less recently used items are iterated over first.)
  738. */
  739. rforEach(fn, thisp = this) {
  740. for (const i of this.#rindexes()) {
  741. const v = this.#valList[i];
  742. const value = this.#isBackgroundFetch(v)
  743. ? v.__staleWhileFetching
  744. : v;
  745. if (value === undefined)
  746. continue;
  747. fn.call(thisp, value, this.#keyList[i], this);
  748. }
  749. }
  750. /**
  751. * Delete any stale entries. Returns true if anything was removed,
  752. * false otherwise.
  753. */
  754. purgeStale() {
  755. let deleted = false;
  756. for (const i of this.#rindexes({ allowStale: true })) {
  757. if (this.#isStale(i)) {
  758. this.#delete(this.#keyList[i], 'expire');
  759. deleted = true;
  760. }
  761. }
  762. return deleted;
  763. }
  764. /**
  765. * Get the extended info about a given entry, to get its value, size, and
  766. * TTL info simultaneously. Returns `undefined` if the key is not present.
  767. *
  768. * Unlike {@link LRUCache#dump}, which is designed to be portable and survive
  769. * serialization, the `start` value is always the current timestamp, and the
  770. * `ttl` is a calculated remaining time to live (negative if expired).
  771. *
  772. * Always returns stale values, if their info is found in the cache, so be
  773. * sure to check for expirations (ie, a negative {@link LRUCache.Entry#ttl})
  774. * if relevant.
  775. */
  776. info(key) {
  777. const i = this.#keyMap.get(key);
  778. if (i === undefined)
  779. return undefined;
  780. const v = this.#valList[i];
  781. const value = this.#isBackgroundFetch(v)
  782. ? v.__staleWhileFetching
  783. : v;
  784. if (value === undefined)
  785. return undefined;
  786. const entry = { value };
  787. if (this.#ttls && this.#starts) {
  788. const ttl = this.#ttls[i];
  789. const start = this.#starts[i];
  790. if (ttl && start) {
  791. const remain = ttl - (perf.now() - start);
  792. entry.ttl = remain;
  793. entry.start = Date.now();
  794. }
  795. }
  796. if (this.#sizes) {
  797. entry.size = this.#sizes[i];
  798. }
  799. return entry;
  800. }
  801. /**
  802. * Return an array of [key, {@link LRUCache.Entry}] tuples which can be
  803. * passed to {@link LRLUCache#load}.
  804. *
  805. * The `start` fields are calculated relative to a portable `Date.now()`
  806. * timestamp, even if `performance.now()` is available.
  807. *
  808. * Stale entries are always included in the `dump`, even if
  809. * {@link LRUCache.OptionsBase.allowStale} is false.
  810. *
  811. * Note: this returns an actual array, not a generator, so it can be more
  812. * easily passed around.
  813. */
  814. dump() {
  815. const arr = [];
  816. for (const i of this.#indexes({ allowStale: true })) {
  817. const key = this.#keyList[i];
  818. const v = this.#valList[i];
  819. const value = this.#isBackgroundFetch(v)
  820. ? v.__staleWhileFetching
  821. : v;
  822. if (value === undefined || key === undefined)
  823. continue;
  824. const entry = { value };
  825. if (this.#ttls && this.#starts) {
  826. entry.ttl = this.#ttls[i];
  827. // always dump the start relative to a portable timestamp
  828. // it's ok for this to be a bit slow, it's a rare operation.
  829. const age = perf.now() - this.#starts[i];
  830. entry.start = Math.floor(Date.now() - age);
  831. }
  832. if (this.#sizes) {
  833. entry.size = this.#sizes[i];
  834. }
  835. arr.unshift([key, entry]);
  836. }
  837. return arr;
  838. }
  839. /**
  840. * Reset the cache and load in the items in entries in the order listed.
  841. *
  842. * The shape of the resulting cache may be different if the same options are
  843. * not used in both caches.
  844. *
  845. * The `start` fields are assumed to be calculated relative to a portable
  846. * `Date.now()` timestamp, even if `performance.now()` is available.
  847. */
  848. load(arr) {
  849. this.clear();
  850. for (const [key, entry] of arr) {
  851. if (entry.start) {
  852. // entry.start is a portable timestamp, but we may be using
  853. // node's performance.now(), so calculate the offset, so that
  854. // we get the intended remaining TTL, no matter how long it's
  855. // been on ice.
  856. //
  857. // it's ok for this to be a bit slow, it's a rare operation.
  858. const age = Date.now() - entry.start;
  859. entry.start = perf.now() - age;
  860. }
  861. this.set(key, entry.value, entry);
  862. }
  863. }
  864. /**
  865. * Add a value to the cache.
  866. *
  867. * Note: if `undefined` is specified as a value, this is an alias for
  868. * {@link LRUCache#delete}
  869. *
  870. * Fields on the {@link LRUCache.SetOptions} options param will override
  871. * their corresponding values in the constructor options for the scope
  872. * of this single `set()` operation.
  873. *
  874. * If `start` is provided, then that will set the effective start
  875. * time for the TTL calculation. Note that this must be a previous
  876. * value of `performance.now()` if supported, or a previous value of
  877. * `Date.now()` if not.
  878. *
  879. * Options object may also include `size`, which will prevent
  880. * calling the `sizeCalculation` function and just use the specified
  881. * number if it is a positive integer, and `noDisposeOnSet` which
  882. * will prevent calling a `dispose` function in the case of
  883. * overwrites.
  884. *
  885. * If the `size` (or return value of `sizeCalculation`) for a given
  886. * entry is greater than `maxEntrySize`, then the item will not be
  887. * added to the cache.
  888. *
  889. * Will update the recency of the entry.
  890. *
  891. * If the value is `undefined`, then this is an alias for
  892. * `cache.delete(key)`. `undefined` is never stored in the cache.
  893. */
  894. set(k, v, setOptions = {}) {
  895. if (v === undefined) {
  896. this.delete(k);
  897. return this;
  898. }
  899. const { ttl = this.ttl, start, noDisposeOnSet = this.noDisposeOnSet, sizeCalculation = this.sizeCalculation, status, } = setOptions;
  900. let { noUpdateTTL = this.noUpdateTTL } = setOptions;
  901. const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation);
  902. // if the item doesn't fit, don't do anything
  903. // NB: maxEntrySize set to maxSize by default
  904. if (this.maxEntrySize && size > this.maxEntrySize) {
  905. if (status) {
  906. status.set = 'miss';
  907. status.maxEntrySizeExceeded = true;
  908. }
  909. // have to delete, in case something is there already.
  910. this.#delete(k, 'set');
  911. return this;
  912. }
  913. let index = this.#size === 0 ? undefined : this.#keyMap.get(k);
  914. if (index === undefined) {
  915. // addition
  916. index = (this.#size === 0
  917. ? this.#tail
  918. : this.#free.length !== 0
  919. ? this.#free.pop()
  920. : this.#size === this.#max
  921. ? this.#evict(false)
  922. : this.#size);
  923. this.#keyList[index] = k;
  924. this.#valList[index] = v;
  925. this.#keyMap.set(k, index);
  926. this.#next[this.#tail] = index;
  927. this.#prev[index] = this.#tail;
  928. this.#tail = index;
  929. this.#size++;
  930. this.#addItemSize(index, size, status);
  931. if (status)
  932. status.set = 'add';
  933. noUpdateTTL = false;
  934. }
  935. else {
  936. // update
  937. this.#moveToTail(index);
  938. const oldVal = this.#valList[index];
  939. if (v !== oldVal) {
  940. if (this.#hasFetchMethod && this.#isBackgroundFetch(oldVal)) {
  941. oldVal.__abortController.abort(new Error('replaced'));
  942. const { __staleWhileFetching: s } = oldVal;
  943. if (s !== undefined && !noDisposeOnSet) {
  944. if (this.#hasDispose) {
  945. this.#dispose?.(s, k, 'set');
  946. }
  947. if (this.#hasDisposeAfter) {
  948. this.#disposed?.push([s, k, 'set']);
  949. }
  950. }
  951. }
  952. else if (!noDisposeOnSet) {
  953. if (this.#hasDispose) {
  954. this.#dispose?.(oldVal, k, 'set');
  955. }
  956. if (this.#hasDisposeAfter) {
  957. this.#disposed?.push([oldVal, k, 'set']);
  958. }
  959. }
  960. this.#removeItemSize(index);
  961. this.#addItemSize(index, size, status);
  962. this.#valList[index] = v;
  963. if (status) {
  964. status.set = 'replace';
  965. const oldValue = oldVal && this.#isBackgroundFetch(oldVal)
  966. ? oldVal.__staleWhileFetching
  967. : oldVal;
  968. if (oldValue !== undefined)
  969. status.oldValue = oldValue;
  970. }
  971. }
  972. else if (status) {
  973. status.set = 'update';
  974. }
  975. }
  976. if (ttl !== 0 && !this.#ttls) {
  977. this.#initializeTTLTracking();
  978. }
  979. if (this.#ttls) {
  980. if (!noUpdateTTL) {
  981. this.#setItemTTL(index, ttl, start);
  982. }
  983. if (status)
  984. this.#statusTTL(status, index);
  985. }
  986. if (!noDisposeOnSet && this.#hasDisposeAfter && this.#disposed) {
  987. const dt = this.#disposed;
  988. let task;
  989. while ((task = dt?.shift())) {
  990. this.#disposeAfter?.(...task);
  991. }
  992. }
  993. return this;
  994. }
  995. /**
  996. * Evict the least recently used item, returning its value or
  997. * `undefined` if cache is empty.
  998. */
  999. pop() {
  1000. try {
  1001. while (this.#size) {
  1002. const val = this.#valList[this.#head];
  1003. this.#evict(true);
  1004. if (this.#isBackgroundFetch(val)) {
  1005. if (val.__staleWhileFetching) {
  1006. return val.__staleWhileFetching;
  1007. }
  1008. }
  1009. else if (val !== undefined) {
  1010. return val;
  1011. }
  1012. }
  1013. }
  1014. finally {
  1015. if (this.#hasDisposeAfter && this.#disposed) {
  1016. const dt = this.#disposed;
  1017. let task;
  1018. while ((task = dt?.shift())) {
  1019. this.#disposeAfter?.(...task);
  1020. }
  1021. }
  1022. }
  1023. }
  1024. #evict(free) {
  1025. const head = this.#head;
  1026. const k = this.#keyList[head];
  1027. const v = this.#valList[head];
  1028. if (this.#hasFetchMethod && this.#isBackgroundFetch(v)) {
  1029. v.__abortController.abort(new Error('evicted'));
  1030. }
  1031. else if (this.#hasDispose || this.#hasDisposeAfter) {
  1032. if (this.#hasDispose) {
  1033. this.#dispose?.(v, k, 'evict');
  1034. }
  1035. if (this.#hasDisposeAfter) {
  1036. this.#disposed?.push([v, k, 'evict']);
  1037. }
  1038. }
  1039. this.#removeItemSize(head);
  1040. // if we aren't about to use the index, then null these out
  1041. if (free) {
  1042. this.#keyList[head] = undefined;
  1043. this.#valList[head] = undefined;
  1044. this.#free.push(head);
  1045. }
  1046. if (this.#size === 1) {
  1047. this.#head = this.#tail = 0;
  1048. this.#free.length = 0;
  1049. }
  1050. else {
  1051. this.#head = this.#next[head];
  1052. }
  1053. this.#keyMap.delete(k);
  1054. this.#size--;
  1055. return head;
  1056. }
  1057. /**
  1058. * Check if a key is in the cache, without updating the recency of use.
  1059. * Will return false if the item is stale, even though it is technically
  1060. * in the cache.
  1061. *
  1062. * Check if a key is in the cache, without updating the recency of
  1063. * use. Age is updated if {@link LRUCache.OptionsBase.updateAgeOnHas} is set
  1064. * to `true` in either the options or the constructor.
  1065. *
  1066. * Will return `false` if the item is stale, even though it is technically in
  1067. * the cache. The difference can be determined (if it matters) by using a
  1068. * `status` argument, and inspecting the `has` field.
  1069. *
  1070. * Will not update item age unless
  1071. * {@link LRUCache.OptionsBase.updateAgeOnHas} is set.
  1072. */
  1073. has(k, hasOptions = {}) {
  1074. const { updateAgeOnHas = this.updateAgeOnHas, status } = hasOptions;
  1075. const index = this.#keyMap.get(k);
  1076. if (index !== undefined) {
  1077. const v = this.#valList[index];
  1078. if (this.#isBackgroundFetch(v) &&
  1079. v.__staleWhileFetching === undefined) {
  1080. return false;
  1081. }
  1082. if (!this.#isStale(index)) {
  1083. if (updateAgeOnHas) {
  1084. this.#updateItemAge(index);
  1085. }
  1086. if (status) {
  1087. status.has = 'hit';
  1088. this.#statusTTL(status, index);
  1089. }
  1090. return true;
  1091. }
  1092. else if (status) {
  1093. status.has = 'stale';
  1094. this.#statusTTL(status, index);
  1095. }
  1096. }
  1097. else if (status) {
  1098. status.has = 'miss';
  1099. }
  1100. return false;
  1101. }
  1102. /**
  1103. * Like {@link LRUCache#get} but doesn't update recency or delete stale
  1104. * items.
  1105. *
  1106. * Returns `undefined` if the item is stale, unless
  1107. * {@link LRUCache.OptionsBase.allowStale} is set.
  1108. */
  1109. peek(k, peekOptions = {}) {
  1110. const { allowStale = this.allowStale } = peekOptions;
  1111. const index = this.#keyMap.get(k);
  1112. if (index === undefined ||
  1113. (!allowStale && this.#isStale(index))) {
  1114. return;
  1115. }
  1116. const v = this.#valList[index];
  1117. // either stale and allowed, or forcing a refresh of non-stale value
  1118. return this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
  1119. }
  1120. #backgroundFetch(k, index, options, context) {
  1121. const v = index === undefined ? undefined : this.#valList[index];
  1122. if (this.#isBackgroundFetch(v)) {
  1123. return v;
  1124. }
  1125. const ac = new AC();
  1126. const { signal } = options;
  1127. // when/if our AC signals, then stop listening to theirs.
  1128. signal?.addEventListener('abort', () => ac.abort(signal.reason), {
  1129. signal: ac.signal,
  1130. });
  1131. const fetchOpts = {
  1132. signal: ac.signal,
  1133. options,
  1134. context,
  1135. };
  1136. const cb = (v, updateCache = false) => {
  1137. const { aborted } = ac.signal;
  1138. const ignoreAbort = options.ignoreFetchAbort && v !== undefined;
  1139. if (options.status) {
  1140. if (aborted && !updateCache) {
  1141. options.status.fetchAborted = true;
  1142. options.status.fetchError = ac.signal.reason;
  1143. if (ignoreAbort)
  1144. options.status.fetchAbortIgnored = true;
  1145. }
  1146. else {
  1147. options.status.fetchResolved = true;
  1148. }
  1149. }
  1150. if (aborted && !ignoreAbort && !updateCache) {
  1151. return fetchFail(ac.signal.reason);
  1152. }
  1153. // either we didn't abort, and are still here, or we did, and ignored
  1154. const bf = p;
  1155. if (this.#valList[index] === p) {
  1156. if (v === undefined) {
  1157. if (bf.__staleWhileFetching) {
  1158. this.#valList[index] = bf.__staleWhileFetching;
  1159. }
  1160. else {
  1161. this.#delete(k, 'fetch');
  1162. }
  1163. }
  1164. else {
  1165. if (options.status)
  1166. options.status.fetchUpdated = true;
  1167. this.set(k, v, fetchOpts.options);
  1168. }
  1169. }
  1170. return v;
  1171. };
  1172. const eb = (er) => {
  1173. if (options.status) {
  1174. options.status.fetchRejected = true;
  1175. options.status.fetchError = er;
  1176. }
  1177. return fetchFail(er);
  1178. };
  1179. const fetchFail = (er) => {
  1180. const { aborted } = ac.signal;
  1181. const allowStaleAborted = aborted && options.allowStaleOnFetchAbort;
  1182. const allowStale = allowStaleAborted || options.allowStaleOnFetchRejection;
  1183. const noDelete = allowStale || options.noDeleteOnFetchRejection;
  1184. const bf = p;
  1185. if (this.#valList[index] === p) {
  1186. // if we allow stale on fetch rejections, then we need to ensure that
  1187. // the stale value is not removed from the cache when the fetch fails.
  1188. const del = !noDelete || bf.__staleWhileFetching === undefined;
  1189. if (del) {
  1190. this.#delete(k, 'fetch');
  1191. }
  1192. else if (!allowStaleAborted) {
  1193. // still replace the *promise* with the stale value,
  1194. // since we are done with the promise at this point.
  1195. // leave it untouched if we're still waiting for an
  1196. // aborted background fetch that hasn't yet returned.
  1197. this.#valList[index] = bf.__staleWhileFetching;
  1198. }
  1199. }
  1200. if (allowStale) {
  1201. if (options.status && bf.__staleWhileFetching !== undefined) {
  1202. options.status.returnedStale = true;
  1203. }
  1204. return bf.__staleWhileFetching;
  1205. }
  1206. else if (bf.__returned === bf) {
  1207. throw er;
  1208. }
  1209. };
  1210. const pcall = (res, rej) => {
  1211. const fmp = this.#fetchMethod?.(k, v, fetchOpts);
  1212. if (fmp && fmp instanceof Promise) {
  1213. fmp.then(v => res(v === undefined ? undefined : v), rej);
  1214. }
  1215. // ignored, we go until we finish, regardless.
  1216. // defer check until we are actually aborting,
  1217. // so fetchMethod can override.
  1218. ac.signal.addEventListener('abort', () => {
  1219. if (!options.ignoreFetchAbort ||
  1220. options.allowStaleOnFetchAbort) {
  1221. res(undefined);
  1222. // when it eventually resolves, update the cache.
  1223. if (options.allowStaleOnFetchAbort) {
  1224. res = v => cb(v, true);
  1225. }
  1226. }
  1227. });
  1228. };
  1229. if (options.status)
  1230. options.status.fetchDispatched = true;
  1231. const p = new Promise(pcall).then(cb, eb);
  1232. const bf = Object.assign(p, {
  1233. __abortController: ac,
  1234. __staleWhileFetching: v,
  1235. __returned: undefined,
  1236. });
  1237. if (index === undefined) {
  1238. // internal, don't expose status.
  1239. this.set(k, bf, { ...fetchOpts.options, status: undefined });
  1240. index = this.#keyMap.get(k);
  1241. }
  1242. else {
  1243. this.#valList[index] = bf;
  1244. }
  1245. return bf;
  1246. }
  1247. #isBackgroundFetch(p) {
  1248. if (!this.#hasFetchMethod)
  1249. return false;
  1250. const b = p;
  1251. return (!!b &&
  1252. b instanceof Promise &&
  1253. b.hasOwnProperty('__staleWhileFetching') &&
  1254. b.__abortController instanceof AC);
  1255. }
  1256. async fetch(k, fetchOptions = {}) {
  1257. const {
  1258. // get options
  1259. allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet,
  1260. // set options
  1261. ttl = this.ttl, noDisposeOnSet = this.noDisposeOnSet, size = 0, sizeCalculation = this.sizeCalculation, noUpdateTTL = this.noUpdateTTL,
  1262. // fetch exclusive options
  1263. noDeleteOnFetchRejection = this.noDeleteOnFetchRejection, allowStaleOnFetchRejection = this.allowStaleOnFetchRejection, ignoreFetchAbort = this.ignoreFetchAbort, allowStaleOnFetchAbort = this.allowStaleOnFetchAbort, context, forceRefresh = false, status, signal, } = fetchOptions;
  1264. if (!this.#hasFetchMethod) {
  1265. if (status)
  1266. status.fetch = 'get';
  1267. return this.get(k, {
  1268. allowStale,
  1269. updateAgeOnGet,
  1270. noDeleteOnStaleGet,
  1271. status,
  1272. });
  1273. }
  1274. const options = {
  1275. allowStale,
  1276. updateAgeOnGet,
  1277. noDeleteOnStaleGet,
  1278. ttl,
  1279. noDisposeOnSet,
  1280. size,
  1281. sizeCalculation,
  1282. noUpdateTTL,
  1283. noDeleteOnFetchRejection,
  1284. allowStaleOnFetchRejection,
  1285. allowStaleOnFetchAbort,
  1286. ignoreFetchAbort,
  1287. status,
  1288. signal,
  1289. };
  1290. let index = this.#keyMap.get(k);
  1291. if (index === undefined) {
  1292. if (status)
  1293. status.fetch = 'miss';
  1294. const p = this.#backgroundFetch(k, index, options, context);
  1295. return (p.__returned = p);
  1296. }
  1297. else {
  1298. // in cache, maybe already fetching
  1299. const v = this.#valList[index];
  1300. if (this.#isBackgroundFetch(v)) {
  1301. const stale = allowStale && v.__staleWhileFetching !== undefined;
  1302. if (status) {
  1303. status.fetch = 'inflight';
  1304. if (stale)
  1305. status.returnedStale = true;
  1306. }
  1307. return stale ? v.__staleWhileFetching : (v.__returned = v);
  1308. }
  1309. // if we force a refresh, that means do NOT serve the cached value,
  1310. // unless we are already in the process of refreshing the cache.
  1311. const isStale = this.#isStale(index);
  1312. if (!forceRefresh && !isStale) {
  1313. if (status)
  1314. status.fetch = 'hit';
  1315. this.#moveToTail(index);
  1316. if (updateAgeOnGet) {
  1317. this.#updateItemAge(index);
  1318. }
  1319. if (status)
  1320. this.#statusTTL(status, index);
  1321. return v;
  1322. }
  1323. // ok, it is stale or a forced refresh, and not already fetching.
  1324. // refresh the cache.
  1325. const p = this.#backgroundFetch(k, index, options, context);
  1326. const hasStale = p.__staleWhileFetching !== undefined;
  1327. const staleVal = hasStale && allowStale;
  1328. if (status) {
  1329. status.fetch = isStale ? 'stale' : 'refresh';
  1330. if (staleVal && isStale)
  1331. status.returnedStale = true;
  1332. }
  1333. return staleVal ? p.__staleWhileFetching : (p.__returned = p);
  1334. }
  1335. }
  1336. async forceFetch(k, fetchOptions = {}) {
  1337. const v = await this.fetch(k, fetchOptions);
  1338. if (v === undefined)
  1339. throw new Error('fetch() returned undefined');
  1340. return v;
  1341. }
  1342. memo(k, memoOptions = {}) {
  1343. const memoMethod = this.#memoMethod;
  1344. if (!memoMethod) {
  1345. throw new Error('no memoMethod provided to constructor');
  1346. }
  1347. const { context, forceRefresh, ...options } = memoOptions;
  1348. const v = this.get(k, options);
  1349. if (!forceRefresh && v !== undefined)
  1350. return v;
  1351. const vv = memoMethod(k, v, {
  1352. options,
  1353. context,
  1354. });
  1355. this.set(k, vv, options);
  1356. return vv;
  1357. }
  1358. /**
  1359. * Return a value from the cache. Will update the recency of the cache
  1360. * entry found.
  1361. *
  1362. * If the key is not found, get() will return `undefined`.
  1363. */
  1364. get(k, getOptions = {}) {
  1365. const { allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet, status, } = getOptions;
  1366. const index = this.#keyMap.get(k);
  1367. if (index !== undefined) {
  1368. const value = this.#valList[index];
  1369. const fetching = this.#isBackgroundFetch(value);
  1370. if (status)
  1371. this.#statusTTL(status, index);
  1372. if (this.#isStale(index)) {
  1373. if (status)
  1374. status.get = 'stale';
  1375. // delete only if not an in-flight background fetch
  1376. if (!fetching) {
  1377. if (!noDeleteOnStaleGet) {
  1378. this.#delete(k, 'expire');
  1379. }
  1380. if (status && allowStale)
  1381. status.returnedStale = true;
  1382. return allowStale ? value : undefined;
  1383. }
  1384. else {
  1385. if (status &&
  1386. allowStale &&
  1387. value.__staleWhileFetching !== undefined) {
  1388. status.returnedStale = true;
  1389. }
  1390. return allowStale ? value.__staleWhileFetching : undefined;
  1391. }
  1392. }
  1393. else {
  1394. if (status)
  1395. status.get = 'hit';
  1396. // if we're currently fetching it, we don't actually have it yet
  1397. // it's not stale, which means this isn't a staleWhileRefetching.
  1398. // If it's not stale, and fetching, AND has a __staleWhileFetching
  1399. // value, then that means the user fetched with {forceRefresh:true},
  1400. // so it's safe to return that value.
  1401. if (fetching) {
  1402. return value.__staleWhileFetching;
  1403. }
  1404. this.#moveToTail(index);
  1405. if (updateAgeOnGet) {
  1406. this.#updateItemAge(index);
  1407. }
  1408. return value;
  1409. }
  1410. }
  1411. else if (status) {
  1412. status.get = 'miss';
  1413. }
  1414. }
  1415. #connect(p, n) {
  1416. this.#prev[n] = p;
  1417. this.#next[p] = n;
  1418. }
  1419. #moveToTail(index) {
  1420. // if tail already, nothing to do
  1421. // if head, move head to next[index]
  1422. // else
  1423. // move next[prev[index]] to next[index] (head has no prev)
  1424. // move prev[next[index]] to prev[index]
  1425. // prev[index] = tail
  1426. // next[tail] = index
  1427. // tail = index
  1428. if (index !== this.#tail) {
  1429. if (index === this.#head) {
  1430. this.#head = this.#next[index];
  1431. }
  1432. else {
  1433. this.#connect(this.#prev[index], this.#next[index]);
  1434. }
  1435. this.#connect(this.#tail, index);
  1436. this.#tail = index;
  1437. }
  1438. }
  1439. /**
  1440. * Deletes a key out of the cache.
  1441. *
  1442. * Returns true if the key was deleted, false otherwise.
  1443. */
  1444. delete(k) {
  1445. return this.#delete(k, 'delete');
  1446. }
  1447. #delete(k, reason) {
  1448. let deleted = false;
  1449. if (this.#size !== 0) {
  1450. const index = this.#keyMap.get(k);
  1451. if (index !== undefined) {
  1452. deleted = true;
  1453. if (this.#size === 1) {
  1454. this.#clear(reason);
  1455. }
  1456. else {
  1457. this.#removeItemSize(index);
  1458. const v = this.#valList[index];
  1459. if (this.#isBackgroundFetch(v)) {
  1460. v.__abortController.abort(new Error('deleted'));
  1461. }
  1462. else if (this.#hasDispose || this.#hasDisposeAfter) {
  1463. if (this.#hasDispose) {
  1464. this.#dispose?.(v, k, reason);
  1465. }
  1466. if (this.#hasDisposeAfter) {
  1467. this.#disposed?.push([v, k, reason]);
  1468. }
  1469. }
  1470. this.#keyMap.delete(k);
  1471. this.#keyList[index] = undefined;
  1472. this.#valList[index] = undefined;
  1473. if (index === this.#tail) {
  1474. this.#tail = this.#prev[index];
  1475. }
  1476. else if (index === this.#head) {
  1477. this.#head = this.#next[index];
  1478. }
  1479. else {
  1480. const pi = this.#prev[index];
  1481. this.#next[pi] = this.#next[index];
  1482. const ni = this.#next[index];
  1483. this.#prev[ni] = this.#prev[index];
  1484. }
  1485. this.#size--;
  1486. this.#free.push(index);
  1487. }
  1488. }
  1489. }
  1490. if (this.#hasDisposeAfter && this.#disposed?.length) {
  1491. const dt = this.#disposed;
  1492. let task;
  1493. while ((task = dt?.shift())) {
  1494. this.#disposeAfter?.(...task);
  1495. }
  1496. }
  1497. return deleted;
  1498. }
  1499. /**
  1500. * Clear the cache entirely, throwing away all values.
  1501. */
  1502. clear() {
  1503. return this.#clear('delete');
  1504. }
  1505. #clear(reason) {
  1506. for (const index of this.#rindexes({ allowStale: true })) {
  1507. const v = this.#valList[index];
  1508. if (this.#isBackgroundFetch(v)) {
  1509. v.__abortController.abort(new Error('deleted'));
  1510. }
  1511. else {
  1512. const k = this.#keyList[index];
  1513. if (this.#hasDispose) {
  1514. this.#dispose?.(v, k, reason);
  1515. }
  1516. if (this.#hasDisposeAfter) {
  1517. this.#disposed?.push([v, k, reason]);
  1518. }
  1519. }
  1520. }
  1521. this.#keyMap.clear();
  1522. this.#valList.fill(undefined);
  1523. this.#keyList.fill(undefined);
  1524. if (this.#ttls && this.#starts) {
  1525. this.#ttls.fill(0);
  1526. this.#starts.fill(0);
  1527. }
  1528. if (this.#sizes) {
  1529. this.#sizes.fill(0);
  1530. }
  1531. this.#head = 0;
  1532. this.#tail = 0;
  1533. this.#free.length = 0;
  1534. this.#calculatedSize = 0;
  1535. this.#size = 0;
  1536. if (this.#hasDisposeAfter && this.#disposed) {
  1537. const dt = this.#disposed;
  1538. let task;
  1539. while ((task = dt?.shift())) {
  1540. this.#disposeAfter?.(...task);
  1541. }
  1542. }
  1543. }
  1544. }
  1545. exports.LRUCache = LRUCache;
  1546. //# sourceMappingURL=index.js.map