pax_global_header00006660000000000000000000000064142176271460014524gustar00rootroot0000000000000052 comment=771392878fc0e2325b1172d04260e87afe94c8f7 quick-lru-6.1.1/000077500000000000000000000000001421762714600134455ustar00rootroot00000000000000quick-lru-6.1.1/.editorconfig000066400000000000000000000002571421762714600161260ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 quick-lru-6.1.1/.gitattributes000066400000000000000000000000231421762714600163330ustar00rootroot00000000000000* text=auto eol=lf quick-lru-6.1.1/.github/000077500000000000000000000000001421762714600150055ustar00rootroot00000000000000quick-lru-6.1.1/.github/funding.yml000066400000000000000000000001631421762714600171620ustar00rootroot00000000000000github: sindresorhus open_collective: sindresorhus custom: https://sindresorhus.com/donate tidelift: npm/quick-lru quick-lru-6.1.1/.github/security.md000066400000000000000000000002631421762714600171770ustar00rootroot00000000000000# Security Policy To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. quick-lru-6.1.1/.github/workflows/000077500000000000000000000000001421762714600170425ustar00rootroot00000000000000quick-lru-6.1.1/.github/workflows/main.yml000066400000000000000000000011331421762714600205070ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: test: name: Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: - 14 - 12 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm test # TODO: Disabled until `nyc` supports ESM. # - uses: codecov/codecov-action@v1 # if: matrix.node-version == 14 # with: # fail_ci_if_error: true quick-lru-6.1.1/.gitignore000066400000000000000000000000431421762714600154320ustar00rootroot00000000000000node_modules yarn.lock .nyc_output quick-lru-6.1.1/.npmrc000066400000000000000000000000231421762714600145600ustar00rootroot00000000000000package-lock=false quick-lru-6.1.1/index.d.ts000066400000000000000000000062031421762714600153470ustar00rootroot00000000000000export interface Options { /** The maximum number of milliseconds an item should remain in the cache. @default Infinity By default, `maxAge` will be `Infinity`, which means that items will never expire. Lazy expiration upon the next write or read call. Individual expiration of an item can be specified by the `set(key, value, maxAge)` method. */ readonly maxAge?: number; /** The maximum number of items before evicting the least recently used items. */ readonly maxSize: number; /** Called right before an item is evicted from the cache. Useful for side effects or for items like object URLs that need explicit cleanup (`revokeObjectURL`). */ onEviction?: (key: KeyType, value: ValueType) => void; } export default class QuickLRU extends Map implements Iterable<[KeyType, ValueType]> { /** The stored item count. */ readonly size: number; /** Simple ["Least Recently Used" (LRU) cache](https://en.m.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29). The instance is an [`Iterable`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) of `[key, value]` pairs so you can use it directly in a [`for…of`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of) loop. @example ``` import QuickLRU from 'quick-lru'; const lru = new QuickLRU({maxSize: 1000}); lru.set('🦄', '🌈'); lru.has('🦄'); //=> true lru.get('🦄'); //=> '🌈' ``` */ constructor(options: Options); [Symbol.iterator](): IterableIterator<[KeyType, ValueType]>; /** Set an item. Returns the instance. Individual expiration of an item can be specified with the `maxAge` option. If not specified, the global `maxAge` value will be used in case it is specified in the constructor, otherwise the item will never expire. @returns The list instance. */ set(key: KeyType, value: ValueType, options?: {maxAge?: number}): this; /** Get an item. @returns The stored item or `undefined`. */ get(key: KeyType): ValueType | undefined; /** Check if an item exists. */ has(key: KeyType): boolean; /** Get an item without marking it as recently used. @returns The stored item or `undefined`. */ peek(key: KeyType): ValueType | undefined; /** Delete an item. @returns `true` if the item is removed or `false` if the item doesn't exist. */ delete(key: KeyType): boolean; /** Delete all items. */ clear(): void; /** Update the `maxSize` in-place, discarding items as necessary. Insertion order is mostly preserved, though this is not a strong guarantee. Useful for on-the-fly tuning of cache sizes in live systems. */ resize(maxSize: number): void; /** Iterable for all the keys. */ keys(): IterableIterator; /** Iterable for all the values. */ values(): IterableIterator; /** Iterable for all entries, starting with the oldest (ascending in recency). */ entriesAscending(): IterableIterator<[KeyType, ValueType]>; /** Iterable for all entries, starting with the newest (descending in recency). */ entriesDescending(): IterableIterator<[KeyType, ValueType]>; } quick-lru-6.1.1/index.js000066400000000000000000000133511421762714600151150ustar00rootroot00000000000000export default class QuickLRU extends Map { constructor(options = {}) { super(); 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'); } // TODO: Use private class fields when ESLint supports them. this.maxSize = options.maxSize; this.maxAge = options.maxAge || Number.POSITIVE_INFINITY; this.onEviction = options.onEviction; this.cache = new Map(); this.oldCache = new Map(); this._size = 0; } // TODO: Use private class methods when targeting Node.js 16. _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} = {}) { const expiry = typeof maxAge === 'number' && maxAge !== Number.POSITIVE_INFINITY ? Date.now() + maxAge : undefined; if (this.cache.has(key)) { this.cache.set(key, { value, expiry }); } else { this._set(key, {value, expiry}); } } 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); } entries() { return this.entriesAscending(); } forEach(callbackFunction, thisArgument = this) { for (const [key, value] of this.entriesAscending()) { callbackFunction.call(thisArgument, value, key, this); } } get [Symbol.toStringTag]() { return JSON.stringify([...this.entriesAscending()]); } } quick-lru-6.1.1/index.test-d.ts000066400000000000000000000012011421762714600163150ustar00rootroot00000000000000import {expectType} from 'tsd'; import QuickLRU from './index.js'; const lru = new QuickLRU({maxSize: 1000, maxAge: 200}); expectType>(lru.set('🦄', 1).set('🌈', 2)); expectType(lru.get('🦄')); expectType(lru.has('🦄')); expectType(lru.peek('🦄')); expectType(lru.delete('🦄')); expectType(lru.size); for (const [key, value] of lru) { expectType(key); expectType(value); } for (const key of lru.keys()) { expectType(key); } for (const value of lru.values()) { expectType(value); } quick-lru-6.1.1/license000066400000000000000000000021351421762714600150130ustar00rootroot00000000000000MIT License Copyright (c) Sindre Sorhus (https://sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. quick-lru-6.1.1/package.json000066400000000000000000000015201421762714600157310ustar00rootroot00000000000000{ "name": "quick-lru", "version": "6.1.1", "description": "Simple “Least Recently Used” (LRU) cache", "license": "MIT", "repository": "sindresorhus/quick-lru", "funding": "https://github.com/sponsors/sindresorhus", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, "type": "module", "exports": "./index.js", "engines": { "node": ">=12" }, "scripts": { "//test": "xo && nyc ava && tsd", "test": "xo && ava && tsd" }, "files": [ "index.js", "index.d.ts" ], "keywords": [ "lru", "quick", "cache", "caching", "least", "recently", "used", "fast", "map", "hash", "buffer" ], "devDependencies": { "ava": "^3.15.0", "nyc": "^15.1.0", "tsd": "^0.14.0", "xo": "^0.37.1" }, "nyc": { "reporter": [ "text", "lcov" ] } } quick-lru-6.1.1/readme.md000066400000000000000000000101411421762714600152210ustar00rootroot00000000000000# quick-lru [![Coverage Status](https://codecov.io/gh/sindresorhus/quick-lru/branch/main/graph/badge.svg)](https://codecov.io/gh/sindresorhus/quick-lru/branch/main) > Simple [“Least Recently Used” (LRU) cache](https://en.m.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29) Useful when you need to cache something and limit memory usage. Inspired by the [`hashlru` algorithm](https://github.com/dominictarr/hashlru#algorithm), but instead uses [`Map`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map) to support keys of any type, not just strings, and values can be `undefined`. ## Install ``` $ npm install quick-lru ``` ## Usage ```js import QuickLRU from 'quick-lru'; const lru = new QuickLRU({maxSize: 1000}); lru.set('🦄', '🌈'); lru.has('🦄'); //=> true lru.get('🦄'); //=> '🌈' ``` ## API ### new QuickLRU(options?) Returns a new instance. It's a [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) subclass. ### options Type: `object` #### maxSize *Required*\ Type: `number` The maximum number of items before evicting the least recently used items. #### maxAge Type: `number`\ Default: `Infinity` The maximum number of milliseconds an item should remain in cache. By default maxAge will be Infinity, which means that items will never expire. Lazy expiration happens upon the next `write` or `read` call. Individual expiration of an item can be specified by the `set(key, value, options)` method. #### onEviction *Optional*\ Type: `(key, value) => void` Called right before an item is evicted from the cache. Useful for side effects or for items like object URLs that need explicit cleanup (`revokeObjectURL`). ### Instance The instance is an [`Iterable`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) of `[key, value]` pairs so you can use it directly in a [`for…of`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...of) loop. Both `key` and `value` can be of any type. #### .set(key, value, options?) Set an item. Returns the instance. Individual expiration of an item can be specified with the `maxAge` option. If not specified, the global `maxAge` value will be used in case it is specified on the constructor, otherwise the item will never expire. #### .get(key) Get an item. #### .has(key) Check if an item exists. #### .peek(key) Get an item without marking it as recently used. #### .delete(key) Delete an item. Returns `true` if the item is removed or `false` if the item doesn't exist. #### .clear() Delete all items. #### .resize(maxSize) Update the `maxSize`, discarding items as necessary. Insertion order is mostly preserved, though this is not a strong guarantee. Useful for on-the-fly tuning of cache sizes in live systems. #### .keys() Iterable for all the keys. #### .values() Iterable for all the values. #### .entriesAscending() Iterable for all entries, starting with the oldest (ascending in recency). #### .entriesDescending() Iterable for all entries, starting with the newest (descending in recency). #### .entries() Iterable for all entries, starting with the newest (ascending in recency). **This method exists for `Map` compatibility. Prefer [.entriesAscending()](#entriesascending) instead.** #### .forEach(callbackFunction, thisArgument) Loop over entries calling the `callbackFunction` for each entry (ascending in recency). **This method exists for `Map` compatibility. Prefer [.entriesAscending()](#entriesascending) instead.** #### .size The stored item count. ## Related - [yocto-queue](https://github.com/sindresorhus/yocto-queue) - Tiny queue data structure ---
Get professional support for this package with a Tidelift subscription
Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies.
quick-lru-6.1.1/test.js000066400000000000000000000455211421762714600147710ustar00rootroot00000000000000import test from 'ava'; import QuickLRU from './index.js'; const lruWithDuplicates = () => { const lru = new QuickLRU({maxSize: 2}); lru.set('key', 'value'); lru.set('keyDupe', 1); lru.set('keyDupe', 2); return lru; }; // TODO: Use `import {setTimeout as delay} from 'timers/promises';` when targeting Node.js 16. const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); }); test('main', t => { t.throws(() => { new QuickLRU(); // eslint-disable-line no-new }, {message: /maxSize/}); }); test('max age - incorrect value', t => { t.throws(() => { new QuickLRU({maxSize: 10, maxAge: 0}); // eslint-disable-line no-new }, {message: /maxAge/}); }); test('.get() / .set()', t => { const lru = new QuickLRU({maxSize: 100}); lru.set('foo', 1); lru.set('bar', 2); t.is(lru.get('foo'), 1); t.is(lru.size, 2); }); test('.get() - limit', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); t.is(lru.get('1'), 1); t.is(lru.get('3'), undefined); lru.set('3', 3); lru.get('1'); lru.set('4', 4); lru.get('1'); lru.set('5', 5); t.true(lru.has('1')); }); test('.set() - limit', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('foo', 1); lru.set('bar', 2); t.is(lru.get('foo'), 1); t.is(lru.get('bar'), 2); lru.set('baz', 3); lru.set('faz', 4); t.false(lru.has('foo')); t.false(lru.has('bar')); t.true(lru.has('baz')); t.true(lru.has('faz')); t.is(lru.size, 2); }); test('.set() - update item', t => { const lru = new QuickLRU({maxSize: 100}); lru.set('foo', 1); t.is(lru.get('foo'), 1); lru.set('foo', 2); t.is(lru.get('foo'), 2); t.is(lru.size, 1); }); test('.has()', t => { const lru = new QuickLRU({maxSize: 100}); lru.set('foo', 1); t.true(lru.has('foo')); }); test('.peek()', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); t.is(lru.peek('1'), 1); lru.set('2', 2); t.is(lru.peek('1'), 1); t.is(lru.peek('3'), undefined); lru.set('3', 3); lru.set('4', 4); t.false(lru.has('1')); }); test('.delete()', t => { const lru = new QuickLRU({maxSize: 100}); lru.set('foo', 1); lru.set('bar', 2); t.true(lru.delete('foo')); t.false(lru.has('foo')); t.true(lru.has('bar')); t.false(lru.delete('foo')); t.is(lru.size, 1); }); test('.delete() - limit', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('foo', 1); lru.set('bar', 2); t.is(lru.size, 2); t.true(lru.delete('foo')); t.false(lru.has('foo')); t.true(lru.has('bar')); lru.delete('bar'); t.is(lru.size, 0); }); test('.clear()', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('foo', 1); lru.set('bar', 2); lru.set('baz', 3); lru.clear(); t.is(lru.size, 0); }); test('.keys()', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); t.deepEqual([...lru.keys()].sort(), ['1', '2', '3']); }); test('.keys() - accounts for duplicates', t => { const lru = lruWithDuplicates(); t.deepEqual([...lru.keys()].sort(), ['key', 'keyDupe']); }); test('.values()', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); t.deepEqual([...lru.values()].sort(), [1, 2, 3]); }); test('.values() - accounts for duplicates', t => { const lru = lruWithDuplicates(); t.deepEqual([...lru.values()].sort(), [2, 'value']); }); test('.[Symbol.iterator]()', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); t.deepEqual([...lru].sort(), [['1', 1], ['2', 2], ['3', 3]]); }); test('.[Symbol.iterator]() - accounts for duplicates', t => { const lru = lruWithDuplicates(); t.deepEqual([...lru].sort(), [['key', 'value'], ['keyDupe', 2]]); }); test('.size', t => { const lru = new QuickLRU({maxSize: 100}); lru.set('1', 1); lru.set('2', 2); t.is(lru.size, 2); lru.delete('1'); t.is(lru.size, 1); lru.set('3', 3); t.is(lru.size, 2); }); test('.size - accounts for duplicates', t => { const lru = lruWithDuplicates(); t.is(lru.size, 2); }); test('max size', t => { const lru = new QuickLRU({maxSize: 3}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); t.is(lru.size, 3); lru.set('4', 4); t.is(lru.size, 3); }); test('checks total cache size does not exceed `maxSize`', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); lru.get('1'); t.is(lru.oldCache.has('1'), false); }); test('`onEviction` option method is called after `maxSize` is exceeded', t => { const expectKey = '1'; const expectValue = 1; let isCalled = false; let actualKey; let actualValue; const onEviction = (key, value) => { actualKey = key; actualValue = value; isCalled = true; }; const lru = new QuickLRU({maxSize: 1, onEviction}); lru.set(expectKey, expectValue); lru.set('2', 2); t.is(actualKey, expectKey); t.is(actualValue, expectValue); t.true(isCalled); }); test('set(expiry) - an individual item could have custom expiration', async t => { const lru = new QuickLRU({maxSize: 10}); lru.set('1', 'test', {maxAge: 100}); await delay(200); t.false(lru.has('1')); }); test('set(expiry) - items without expiration will never expired', async t => { const lru = new QuickLRU({maxSize: 10}); lru.set('1', 'test', {maxAge: 100}); lru.set('2', 'boo'); await delay(200); t.false(lru.has('1')); await delay(200); t.true(lru.has('2')); }); test('set(expiry) - not a number expires should not be take in account', async t => { const lru = new QuickLRU({maxSize: 10}); lru.set('1', 'test', 'string'); lru.set('2', 'boo'); await delay(200); t.true(lru.has('1')); await delay(200); t.true(lru.has('2')); }); test('set(expiry) - local expires prevails over the global maxAge', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 1000}); lru.set('1', 'test', {maxAge: 100}); lru.set('2', 'boo'); await delay(300); t.false(lru.has('1')); await delay(200); t.true(lru.has('2')); }); test('set(expiry) - set the same item should update the expiration time', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 150}); lru.set('1', 'test'); await delay(100); lru.set('1', 'test'); await delay(100); t.true(lru.has('1')); }); test('max age - should remove the item if has expired on call `get()` method upon the same key', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 90}); lru.set('1', 'test'); await delay(200); t.is(lru.get('1'), undefined); }); test('max age - a non-recent item can also expire', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', 'test1'); lru.set('2', 'test2'); lru.set('3', 'test4'); await delay(200); t.is(lru.get('1'), undefined); }); test('max age - setting the item again should refresh the expiration time', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', 'test'); await delay(50); lru.set('1', 'test2'); await delay(50); t.is(lru.get('1'), 'test2'); }); test('max age - setting an item with a local expiration date', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', 'test'); lru.set('2', 'test2', {maxAge: 500}); await delay(200); t.true(lru.has('2')); await delay(300); t.false(lru.has('2')); }); test('max age - setting an item with a empty object as options parameter must use the global maxAge', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', 'test'); lru.set('2', 'test2', {}); await delay(200); t.false(lru.has('2')); }); test('max age - once an item expires, the eviction function should be called', async t => { t.timeout(1000); const expectKey = '1'; const expectValue = 'test'; let isCalled = false; let actualKey; let actualValue; const onEviction = (key, value) => { isCalled = true; actualKey = key; actualValue = value; }; const lru = new QuickLRU({ maxSize: 2, maxAge: 100, onEviction }); lru.set(expectKey, expectValue); await delay(200); t.is(lru.get('1'), undefined); t.true(isCalled); t.is(actualKey, expectKey); t.is(actualValue, expectValue); }); test('max age - once an non-recent item expires, the eviction function should be called', async t => { t.timeout(1000); const expectKeys = ['1', '2']; const expectValues = ['test', 'test2']; let isCalled = false; const actualKeys = []; const actualValues = []; const onEviction = (key, value) => { isCalled = true; actualKeys.push(key); actualValues.push(value); }; const lru = new QuickLRU({ maxSize: 2, maxAge: 100, onEviction }); lru.set('1', 'test'); lru.set('2', 'test2'); lru.set('3', 'test3'); lru.set('4', 'test4'); lru.set('5', 'test5'); await delay(200); t.is(lru.get('1'), undefined); t.true(isCalled); t.deepEqual(actualKeys, expectKeys); t.deepEqual(actualValues, expectValues); }); test('max age - on resize, max aged items should also be evicted', async t => { t.timeout(1000); const expectKeys = ['1', '2', '3']; const expectValues = ['test', 'test2', 'test3']; let isCalled = false; const actualKeys = []; const actualValues = []; const onEviction = (key, value) => { isCalled = true; actualKeys.push(key); actualValues.push(value); }; const lru = new QuickLRU({ maxSize: 3, maxAge: 100, onEviction }); lru.set('1', 'test'); lru.set('2', 'test2'); lru.set('3', 'test3'); lru.set('4', 'test4'); lru.set('5', 'test5'); lru.resize(2); await delay(200); t.false(lru.has('1')); t.true(isCalled); t.deepEqual(actualKeys, expectKeys); t.deepEqual(actualValues, expectValues); }); test('max age - an item that is not expired can also be peek', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 400}); lru.set('1', 'test'); await delay(200); t.is(lru.peek('1'), 'test'); }); test('max age - peeking the item should also remove the item if it has expired', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 100}); lru.set('1', 'test'); await delay(200); t.is(lru.peek('1'), undefined); }); test('max age - peeking the item should also remove expired items that are not recent', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', 'test'); lru.set('2', 'test'); lru.set('3', 'test'); await delay(200); t.is(lru.peek('1'), undefined); }); test('max age - non-recent items that are not expired are also valid', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 200}); lru.set('1', 'test'); lru.set('2', 'test2'); lru.set('3', 'test4'); await delay(100); t.is(lru.get('1'), 'test'); }); test('max age - has method should delete the item if expired and return false', async t => { const lru = new QuickLRU({maxSize: 4, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test'); lru.set('3', 'test'); await delay(200); t.false(lru.has('1')); }); test('max age - has method should return the item if is not expired', t => { const lru = new QuickLRU({maxSize: 4, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test'); lru.set('3', 'test'); t.true(lru.has('1')); }); test('max age - has method should return true for undefined value that has expiration time', t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test'); lru.set('3', 'test'); t.true(lru.has('1')); }); test('max age - `.keys()` should return keys that are not expirated', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('4', 'loco'); t.deepEqual([...lru.keys()].sort(), ['4']); }); test('max age - `.keys()` should return an empty list if all items has expired', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); t.deepEqual([...lru.keys()].sort(), []); }); test('max age - `.values()` should return an empty if all items has expired', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); t.deepEqual([...lru.values()].sort(), []); }); test('max age - `.values()` should return the values that are not expired', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('5', 'loco'); t.deepEqual([...lru.values()].sort(), ['loco']); }); test('max age - `entriesDescending()` should not return expired entries', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('4', 'coco'); lru.set('5', 'loco'); t.deepEqual([...lru.entriesDescending()], [['5', 'loco'], ['4', 'coco']]); }); test('max age - `entriesDescending()` should not return expired entries from old cache', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('4', 'coco'); lru.set('5', 'loco'); t.deepEqual([...lru.entriesDescending()], [['5', 'loco'], ['4', 'coco']]); }); test('max age - `entriesDescending()` should return all entries in desc order if are not expired', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 5000}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('4', 'coco'); lru.set('5', 'loco'); t.deepEqual([...lru.entriesDescending()], [['5', 'loco'], ['4', 'coco'], ['3', 'test3'], ['2', 'test2'], ['1', undefined]]); }); test('max age - `entriesAscending()` should not return expired entries', async t => { const lru = new QuickLRU({maxSize: 5, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('4', 'coco'); lru.set('5', 'loco'); t.deepEqual([...lru.entriesAscending()], [['4', 'coco'], ['5', 'loco']]); }); test('max age - `entriesAscending() should not return expired entries even if are not recent', async t => { const lru = new QuickLRU({maxSize: 3, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('4', 'coco'); lru.set('5', 'loco'); t.deepEqual([...lru.entriesAscending()], [['4', 'coco'], ['5', 'loco']]); }); test('max age - `entriesAscending()` should return the entries that are not expired', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); await delay(200); lru.set('3', 'test3'); lru.set('4', 'coco'); lru.set('5', 'loco'); t.deepEqual([...lru.entriesAscending()], [['3', 'test3'], ['4', 'coco'], ['5', 'loco']]); }); test('max age - `entries()` should return the entries that are not expired', async t => { const lru = new QuickLRU({maxSize: 10, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); await delay(200); lru.set('3', 'test3'); lru.set('4', 'coco'); lru.set('5', 'loco'); t.deepEqual([...lru.entries()], [['3', 'test3'], ['4', 'coco'], ['5', 'loco']]); }); test('max age - `forEach()` should not return expired entries', async t => { const lru = new QuickLRU({maxSize: 5, maxAge: 100}); lru.set('1', undefined); lru.set('2', 'test2'); lru.set('3', 'test3'); await delay(200); lru.set('4', 'coco'); lru.set('5', 'loco'); const entries = []; lru.forEach((value, key) => { entries.push([key, value]); }); t.deepEqual(entries, [['4', 'coco'], ['5', 'loco']]); }); test('max age - `.[Symbol.iterator]()` should not return expired items', async t => { const lru = new QuickLRU({maxSize: 2, maxAge: 100}); lru.set('key', 'value'); lru.set('key3', 1); await delay(200); lru.set('key4', 2); t.deepEqual([...lru].sort(), [['key4', 2]]); }); test('max age - `.[Symbol.iterator]()` should not return expired items that are old', async t => { const lru = new QuickLRU({maxSize: 1, maxAge: 100}); lru.set('keyunique', 'value'); lru.set('key3unique', 1); lru.set('key4unique', 2); await delay(200); t.deepEqual([...lru].sort(), []); }); test('entriesAscending enumerates cache items oldest-first', t => { const lru = new QuickLRU({maxSize: 3}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); lru.set('3', 7); lru.set('2', 8); t.deepEqual([...lru.entriesAscending()], [['1', 1], ['3', 7], ['2', 8]]); }); test('entriesDescending enumerates cache items newest-first', t => { const lru = new QuickLRU({maxSize: 3}); lru.set('t', 1); lru.set('q', 2); lru.set('a', 8); lru.set('t', 4); lru.set('v', 3); t.deepEqual([...lru.entriesDescending()], [['v', 3], ['t', 4], ['a', 8], ['q', 2]]); }); test('entries enumerates cache items oldest-first', t => { const lru = new QuickLRU({maxSize: 3}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); lru.set('3', 7); lru.set('2', 8); t.deepEqual([...lru.entries()], [['1', 1], ['3', 7], ['2', 8]]); }); test('forEach calls the cb function for each cache item oldest-first', t => { const lru = new QuickLRU({maxSize: 3}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); lru.set('3', 7); lru.set('2', 8); const entries = []; lru.forEach((value, key) => { entries.push([key, value]); }); t.deepEqual(entries, [['1', 1], ['3', 7], ['2', 8]]); }); test('resize removes older items', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); lru.resize(1); t.is(lru.peek('1'), undefined); t.is(lru.peek('3'), 3); lru.set('3', 4); t.is(lru.peek('3'), 4); lru.set('4', 5); t.is(lru.peek('4'), 5); t.is(lru.peek('2'), undefined); }); test('resize omits evictions', t => { const calls = []; const onEviction = (...args) => calls.push(args); const lru = new QuickLRU({maxSize: 2, onEviction}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); lru.resize(1); t.true(calls.length > 0); t.true(calls.some(([key]) => key === '1')); }); test('resize increases capacity', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); lru.resize(3); lru.set('3', 3); lru.set('4', 4); lru.set('5', 5); t.deepEqual([...lru.entriesAscending()], [['1', 1], ['2', 2], ['3', 3], ['4', 4], ['5', 5]]); }); test('resize does not conflict with the same number of items', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); lru.set('3', 3); lru.resize(3); lru.set('4', 4); lru.set('5', 5); t.deepEqual([...lru.entriesAscending()], [['1', 1], ['2', 2], ['3', 3], ['4', 4], ['5', 5]]); }); test('resize checks parameter bounds', t => { const lru = new QuickLRU({maxSize: 2}); t.throws(() => { lru.resize(-1); }, {message: /maxSize/}); }); test('function value', t => { const lru = new QuickLRU({maxSize: 1}); let isCalled = false; lru.set('fn', () => { isCalled = true; }); lru.get('fn')(); t.true(isCalled); }); test('[Symbol.toStringTag] converts the cache items to a string in ascending order', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); t.is(lru[Symbol.toStringTag], '[["1",1],["2",2]]'); }); test('toString() works as expected', t => { const lru = new QuickLRU({maxSize: 2}); lru.set('1', 1); lru.set('2', 2); t.is(lru.toString(), '[object [["1",1],["2",2]]]'); });