index.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.createLRU = void 0;
  4. const createLRU = (options) => {
  5. let { max } = options;
  6. if (!(Number.isInteger(max) && max > 0))
  7. throw new TypeError('`max` must be a positive integer');
  8. let size = 0;
  9. let head = 0;
  10. let tail = 0;
  11. let free = [];
  12. const { onEviction } = options;
  13. const keyMap = new Map();
  14. const keyList = new Array(max).fill(undefined);
  15. const valList = new Array(max).fill(undefined);
  16. const next = new Array(max).fill(0);
  17. const prev = new Array(max).fill(0);
  18. const setTail = (index, type) => {
  19. if (index === tail)
  20. return;
  21. const nextIndex = next[index];
  22. const prevIndex = prev[index];
  23. if (index === head)
  24. head = nextIndex;
  25. else if (type === 'get' || prevIndex !== 0)
  26. next[prevIndex] = nextIndex;
  27. if (nextIndex !== 0)
  28. prev[nextIndex] = prevIndex;
  29. next[tail] = index;
  30. prev[index] = tail;
  31. next[index] = 0;
  32. tail = index;
  33. };
  34. const _evict = () => {
  35. const evictHead = head;
  36. const key = keyList[evictHead];
  37. onEviction === null || onEviction === void 0 ? void 0 : onEviction(key, valList[evictHead]);
  38. keyMap.delete(key);
  39. keyList[evictHead] = undefined;
  40. valList[evictHead] = undefined;
  41. head = next[evictHead];
  42. if (head !== 0)
  43. prev[head] = 0;
  44. size--;
  45. if (size === 0)
  46. head = tail = 0;
  47. free.push(evictHead);
  48. return evictHead;
  49. };
  50. return {
  51. /** Adds a key-value pair to the cache. Updates the value if the key already exists. */
  52. set(key, value) {
  53. if (key === undefined)
  54. return;
  55. let index = keyMap.get(key);
  56. if (index === undefined) {
  57. index = size === max ? _evict() : free.length > 0 ? free.pop() : size;
  58. keyMap.set(key, index);
  59. keyList[index] = key;
  60. size++;
  61. }
  62. else
  63. onEviction === null || onEviction === void 0 ? void 0 : onEviction(key, valList[index]);
  64. valList[index] = value;
  65. if (size === 1)
  66. head = tail = index;
  67. else
  68. setTail(index, 'set');
  69. },
  70. /** Retrieves the value for a given key and moves the key to the most recent position. */
  71. get(key) {
  72. const index = keyMap.get(key);
  73. if (index === undefined)
  74. return;
  75. if (index !== tail)
  76. setTail(index, 'get');
  77. return valList[index];
  78. },
  79. /** Retrieves the value for a given key without changing its position. */
  80. peek: (key) => {
  81. const index = keyMap.get(key);
  82. return index !== undefined ? valList[index] : undefined;
  83. },
  84. /** Checks if a key exists in the cache. */
  85. has: (key) => keyMap.has(key),
  86. /** Iterates over all keys in the cache, from most recent to least recent. */
  87. *keys() {
  88. let current = tail;
  89. for (let i = 0; i < size; i++) {
  90. yield keyList[current];
  91. current = prev[current];
  92. }
  93. },
  94. /** Iterates over all values in the cache, from most recent to least recent. */
  95. *values() {
  96. let current = tail;
  97. for (let i = 0; i < size; i++) {
  98. yield valList[current];
  99. current = prev[current];
  100. }
  101. },
  102. /** Iterates over `[key, value]` pairs in the cache, from most recent to least recent. */
  103. *entries() {
  104. let current = tail;
  105. for (let i = 0; i < size; i++) {
  106. yield [keyList[current], valList[current]];
  107. current = prev[current];
  108. }
  109. },
  110. /** Iterates over each value-key pair in the cache, from most recent to least recent. */
  111. forEach: (callback) => {
  112. let current = tail;
  113. for (let i = 0; i < size; i++) {
  114. const key = keyList[current];
  115. const value = valList[current];
  116. callback(value, key);
  117. current = prev[current];
  118. }
  119. },
  120. /** Deletes a key-value pair from the cache. */
  121. delete(key) {
  122. const index = keyMap.get(key);
  123. if (index === undefined)
  124. return false;
  125. onEviction === null || onEviction === void 0 ? void 0 : onEviction(key, valList[index]);
  126. keyMap.delete(key);
  127. free.push(index);
  128. keyList[index] = undefined;
  129. valList[index] = undefined;
  130. const prevIndex = prev[index];
  131. const nextIndex = next[index];
  132. if (prevIndex !== 0)
  133. next[prevIndex] = nextIndex;
  134. if (nextIndex !== 0)
  135. prev[nextIndex] = prevIndex;
  136. if (index === head)
  137. head = nextIndex;
  138. if (index === tail)
  139. tail = prevIndex;
  140. size--;
  141. return true;
  142. },
  143. /** Evicts the oldest item or the specified number of the oldest items from the cache. */
  144. evict: (number) => {
  145. let toPrune = Math.min(number, size);
  146. while (toPrune > 0) {
  147. _evict();
  148. toPrune--;
  149. }
  150. },
  151. /** Clears all key-value pairs from the cache. */
  152. clear() {
  153. if (typeof onEviction === 'function') {
  154. const iterator = keyMap.values();
  155. for (let result = iterator.next(); !result.done; result = iterator.next())
  156. onEviction(keyList[result.value], valList[result.value]);
  157. }
  158. keyMap.clear();
  159. keyList.fill(undefined);
  160. valList.fill(undefined);
  161. free = [];
  162. size = 0;
  163. head = tail = 0;
  164. },
  165. /** Resizes the cache to a new maximum size, evicting items if necessary. */
  166. resize: (newMax) => {
  167. if (!(Number.isInteger(newMax) && newMax > 0))
  168. throw new TypeError('`max` must be a positive integer');
  169. if (newMax === max)
  170. return;
  171. if (newMax < max) {
  172. let current = tail;
  173. const preserve = Math.min(size, newMax);
  174. const remove = size - preserve;
  175. const newKeyList = new Array(newMax);
  176. const newValList = new Array(newMax);
  177. const newNext = new Array(newMax);
  178. const newPrev = new Array(newMax);
  179. for (let i = 1; i <= remove; i++)
  180. onEviction === null || onEviction === void 0 ? void 0 : onEviction(keyList[i], valList[i]);
  181. for (let i = preserve - 1; i >= 0; i--) {
  182. newKeyList[i] = keyList[current];
  183. newValList[i] = valList[current];
  184. newNext[i] = i + 1;
  185. newPrev[i] = i - 1;
  186. keyMap.set(newKeyList[i], i);
  187. current = prev[current];
  188. }
  189. head = 0;
  190. tail = preserve - 1;
  191. size = preserve;
  192. keyList.length = newMax;
  193. valList.length = newMax;
  194. next.length = newMax;
  195. prev.length = newMax;
  196. for (let i = 0; i < preserve; i++) {
  197. keyList[i] = newKeyList[i];
  198. valList[i] = newValList[i];
  199. next[i] = newNext[i];
  200. prev[i] = newPrev[i];
  201. }
  202. free = [];
  203. for (let i = preserve; i < newMax; i++)
  204. free.push(i);
  205. }
  206. else {
  207. const fill = newMax - max;
  208. keyList.push(...new Array(fill).fill(undefined));
  209. valList.push(...new Array(fill).fill(undefined));
  210. next.push(...new Array(fill).fill(0));
  211. prev.push(...new Array(fill).fill(0));
  212. }
  213. max = newMax;
  214. },
  215. /** Returns the maximum number of items that can be stored in the cache. */
  216. get max() {
  217. return max;
  218. },
  219. /** Returns the number of items currently stored in the cache. */
  220. get size() {
  221. return size;
  222. },
  223. /** Returns the number of currently available slots in the cache before reaching the maximum size. */
  224. get available() {
  225. return max - size;
  226. },
  227. };
  228. };
  229. exports.createLRU = createLRU;