index.js 8.5 KB

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