index.js 56 KB

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