index.js 54 KB

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