From 2dd5b5246e463b61ce125efe58a6394361370472 Mon Sep 17 00:00:00 2001 From: Javier Blanco Date: Fri, 18 Feb 2022 16:29:54 +0100 Subject: [PATCH] Make `QuickLRU` a `Map` subclass (#37) Co-authored-by: Sindre Sorhus --- index.d.ts | 2 +- index.js | 18 +++++++++++++++- readme.md | 14 ++++++++++++ test.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index ca4455a..a865f3d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -24,7 +24,7 @@ export interface Options { onEviction?: (key: KeyType, value: ValueType) => void; } -export default class QuickLRU implements Iterable<[KeyType, ValueType]> { +export default class QuickLRU extends Map implements Iterable<[KeyType, ValueType]> { /** The stored item count. */ diff --git a/index.js b/index.js index edbafb2..d980a40 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,7 @@ -export default class QuickLRU { +export 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'); } @@ -262,4 +264,18 @@ export default class QuickLRU { 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); + } + } + + [Symbol.toStringTag]() { + return JSON.stringify([...this.entriesAscending()]); + } } diff --git a/readme.md b/readme.md index 803f28a..d4780c8 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,8 @@ lru.get('🦄'); 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` @@ -122,6 +124,18 @@ Iterable for all entries, starting with the oldest (ascending in recency). 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. diff --git a/test.js b/test.js index 65cfb96..624e554 100644 --- a/test.js +++ b/test.js @@ -577,6 +577,35 @@ test('max age - `entriesAscending()` should return the entries that are not expi 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'); @@ -617,6 +646,32 @@ test('entriesDescending enumerates cache items newest-first', t => { 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); @@ -685,3 +740,10 @@ test('function value', t => { 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]]'); +});