123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- 'use strict';
- class QuickLRU {
- constructor(options = {}) {
- if (!(options.maxSize && options.maxSize > 0)) {
- throw new TypeError('`maxSize` must be a number greater than 0');
- }
- if (typeof options.maxAge === 'number' && options.maxAge === 0) {
- throw new TypeError('`maxAge` must be a number greater than 0');
- }
- this.maxSize = options.maxSize;
- this.maxAge = options.maxAge || Infinity;
- this.onEviction = options.onEviction;
- this.cache = new Map();
- this.oldCache = new Map();
- this._size = 0;
- }
- _emitEvictions(cache) {
- if (typeof this.onEviction !== 'function') {
- return;
- }
- for (const [key, item] of cache) {
- this.onEviction(key, item.value);
- }
- }
- _deleteIfExpired(key, item) {
- if (typeof item.expiry === 'number' && item.expiry <= Date.now()) {
- if (typeof this.onEviction === 'function') {
- this.onEviction(key, item.value);
- }
- return this.delete(key);
- }
- return false;
- }
- _getOrDeleteIfExpired(key, item) {
- const deleted = this._deleteIfExpired(key, item);
- if (deleted === false) {
- return item.value;
- }
- }
- _getItemValue(key, item) {
- return item.expiry ? this._getOrDeleteIfExpired(key, item) : item.value;
- }
- _peek(key, cache) {
- const item = cache.get(key);
- return this._getItemValue(key, item);
- }
- _set(key, value) {
- this.cache.set(key, value);
- this._size++;
- if (this._size >= this.maxSize) {
- this._size = 0;
- this._emitEvictions(this.oldCache);
- this.oldCache = this.cache;
- this.cache = new Map();
- }
- }
- _moveToRecent(key, item) {
- this.oldCache.delete(key);
- this._set(key, item);
- }
- * _entriesAscending() {
- for (const item of this.oldCache) {
- const [key, value] = item;
- if (!this.cache.has(key)) {
- const deleted = this._deleteIfExpired(key, value);
- if (deleted === false) {
- yield item;
- }
- }
- }
- for (const item of this.cache) {
- const [key, value] = item;
- const deleted = this._deleteIfExpired(key, value);
- if (deleted === false) {
- yield item;
- }
- }
- }
- get(key) {
- if (this.cache.has(key)) {
- const item = this.cache.get(key);
- return this._getItemValue(key, item);
- }
- if (this.oldCache.has(key)) {
- const item = this.oldCache.get(key);
- if (this._deleteIfExpired(key, item) === false) {
- this._moveToRecent(key, item);
- return item.value;
- }
- }
- }
- set(key, value, {maxAge = this.maxAge === Infinity ? undefined : Date.now() + this.maxAge} = {}) {
- if (this.cache.has(key)) {
- this.cache.set(key, {
- value,
- maxAge
- });
- } else {
- this._set(key, {value, expiry: maxAge});
- }
- }
- has(key) {
- if (this.cache.has(key)) {
- return !this._deleteIfExpired(key, this.cache.get(key));
- }
- if (this.oldCache.has(key)) {
- return !this._deleteIfExpired(key, this.oldCache.get(key));
- }
- return false;
- }
- peek(key) {
- if (this.cache.has(key)) {
- return this._peek(key, this.cache);
- }
- if (this.oldCache.has(key)) {
- return this._peek(key, this.oldCache);
- }
- }
- delete(key) {
- const deleted = this.cache.delete(key);
- if (deleted) {
- this._size--;
- }
- return this.oldCache.delete(key) || deleted;
- }
- clear() {
- this.cache.clear();
- this.oldCache.clear();
- this._size = 0;
- }
-
- resize(newSize) {
- if (!(newSize && newSize > 0)) {
- throw new TypeError('`maxSize` must be a number greater than 0');
- }
- const items = [...this._entriesAscending()];
- const removeCount = items.length - newSize;
- if (removeCount < 0) {
- this.cache = new Map(items);
- this.oldCache = new Map();
- this._size = items.length;
- } else {
- if (removeCount > 0) {
- this._emitEvictions(items.slice(0, removeCount));
- }
- this.oldCache = new Map(items.slice(removeCount));
- this.cache = new Map();
- this._size = 0;
- }
- this.maxSize = newSize;
- }
- * keys() {
- for (const [key] of this) {
- yield key;
- }
- }
- * values() {
- for (const [, value] of this) {
- yield value;
- }
- }
- * [Symbol.iterator]() {
- for (const item of this.cache) {
- const [key, value] = item;
- const deleted = this._deleteIfExpired(key, value);
- if (deleted === false) {
- yield [key, value.value];
- }
- }
- for (const item of this.oldCache) {
- const [key, value] = item;
- if (!this.cache.has(key)) {
- const deleted = this._deleteIfExpired(key, value);
- if (deleted === false) {
- yield [key, value.value];
- }
- }
- }
- }
- * entriesDescending() {
- let items = [...this.cache];
- for (let i = items.length - 1; i >= 0; --i) {
- const item = items[i];
- const [key, value] = item;
- const deleted = this._deleteIfExpired(key, value);
- if (deleted === false) {
- yield [key, value.value];
- }
- }
- items = [...this.oldCache];
- for (let i = items.length - 1; i >= 0; --i) {
- const item = items[i];
- const [key, value] = item;
- if (!this.cache.has(key)) {
- const deleted = this._deleteIfExpired(key, value);
- if (deleted === false) {
- yield [key, value.value];
- }
- }
- }
- }
- * entriesAscending() {
- for (const [key, value] of this._entriesAscending()) {
- yield [key, value.value];
- }
- }
- get size() {
- if (!this._size) {
- return this.oldCache.size;
- }
- let oldCacheSize = 0;
- for (const key of this.oldCache.keys()) {
- if (!this.cache.has(key)) {
- oldCacheSize++;
- }
- }
- return Math.min(this._size + oldCacheSize, this.maxSize);
- }
- }
- module.exports = QuickLRU;
|