Spaces:
Sleeping
Sleeping
| ; | |
| 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; | |