Skip to content

Commit eea9aa9

Browse files
committed
Swap quick-lru with lru-cache
The motivation here is that unlike `quick-lru` `lru-cache` comes in 2 formats we're interested in: cjs and esm. Plus, DHT already uses `lru-cache` so it already fits the stack.
1 parent 4752355 commit eea9aa9

File tree

6 files changed

+43
-23
lines changed

6 files changed

+43
-23
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sdk/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"idb": "^8.0.0",
101101
"lit-siwe": "^1.1.8",
102102
"lodash": "^4.17.21",
103+
"lru-cache": "^11.0.2",
103104
"node-fetch": "^2.7.0",
104105
"p-limit": "^3.1.0",
105106
"p-memoize": "4.0.4",

packages/sdk/src/utils/CachingMap.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MapKey } from '@streamr/utils'
22
import pMemoize from 'p-memoize'
3-
import LRU from '../../vendor/quick-lru'
3+
import { LRUCache } from 'lru-cache'
44

55
interface Options<P, K> {
66
maxSize: number
@@ -23,16 +23,16 @@ interface Options<P, K> {
2323
export class CachingMap<K extends MapKey, V, P extends any[]> {
2424

2525
private readonly cachedFn: (...args: P) => Promise<V>
26-
private readonly cache: LRU<K, { data: V, maxAge: number }>
26+
private readonly cache: LRUCache<K, { data: V, maxAge: number }>
2727
private readonly opts: Options<P, K>
2828

2929
constructor(
3030
asyncFn: (...args: P) => Promise<V>,
3131
opts: Options<P, K>
3232
) {
33-
this.cache = new LRU<K, { data: V, maxAge: number }>({
34-
maxSize: opts.maxSize,
35-
maxAge: opts.maxAge
33+
this.cache = new LRUCache<K, { data: V, maxAge: number }>({
34+
max: opts.maxSize,
35+
ttl: opts.maxAge
3636
})
3737
this.cachedFn = pMemoize(asyncFn, {
3838
cachePromiseRejection: false,

packages/sdk/src/utils/Mapping.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { LRUCache } from 'lru-cache'
12
import { formLookupKey } from './utils'
2-
import LRU from '../../vendor/quick-lru'
33

44
type KeyType = (string | number | symbol)[]
55

@@ -19,14 +19,22 @@ interface Item<K, V> {
1919
value: V
2020
}
2121

22+
interface LRU<K extends string, V> {
23+
get(key: K): V | undefined
24+
set(key: K, value: V): void
25+
delete(key: K): void
26+
values(): Iterable<V>
27+
entries(): Iterable<[K, V]>
28+
}
29+
2230
/*
2331
* A map that lazily creates values. The factory function is called only when a key
2432
* is accessed for the first time. Subsequent calls to `get()` return the cached value
2533
* unless it has been evicted due to `maxSize` or `maxAge` limits.
2634
*/
2735
export class Mapping<K extends KeyType, V> {
2836

29-
private readonly delegate: Map<string, Item<K, V>>
37+
private readonly delegate: LRU<string, Item<K, V>>
3038
private readonly pendingPromises: Map<string, Promise<V>> = new Map()
3139
private readonly opts: CacheMapOptions<K, V> | LazyMapOptions<K, V>
3240

@@ -37,9 +45,9 @@ export class Mapping<K extends KeyType, V> {
3745
**/
3846
constructor(opts: CacheMapOptions<K, V> | LazyMapOptions<K, V>) {
3947
if ('maxSize' in opts) {
40-
this.delegate = new LRU<string, Item<K, V>>({
41-
maxSize: opts.maxSize,
42-
maxAge: opts.maxAge
48+
this.delegate = new LRUCache<string, Item<K, V>>({
49+
max: opts.maxSize,
50+
ttl: opts.maxAge
4351
})
4452
} else {
4553
this.delegate = new Map<string, Item<K, V>>()

packages/sdk/src/utils/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ContractTransactionReceipt } from 'ethers'
88
import compact from 'lodash/compact'
99
import fetch, { Response } from 'node-fetch'
1010
import { Readable } from 'stream'
11-
import LRU from '../../vendor/quick-lru'
11+
import { LRUCache } from 'lru-cache'
1212
import { NetworkNodeType, NetworkPeerDescriptor, StrictStreamrClientConfig } from '../Config'
1313
import { StreamrClientEventEmitter } from '../events'
1414
import { WebStreamToNodeStream } from './WebStreamToNodeStream'
@@ -92,12 +92,12 @@ export function formStorageNodeAssignmentStreamId(clusterAddress: string): Strea
9292
return toStreamID('/assignments', toEthereumAddress(clusterAddress))
9393
}
9494

95-
export class MaxSizedSet<T> {
95+
export class MaxSizedSet<T extends string> {
9696

97-
private readonly delegate: LRU<T, true>
97+
private readonly delegate: LRUCache<T, true>
9898

9999
constructor(maxSize: number) {
100-
this.delegate = new LRU<T, true>({ maxSize })
100+
this.delegate = new LRUCache<T, true>({ maxSize })
101101
}
102102

103103
add(value: T): void {

packages/sdk/test/unit/Mapping.test.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,25 +80,35 @@ describe('Mapping', () => {
8080
})
8181

8282
it('max size', async () => {
83-
const SIZE = 3
83+
const maxSize = 3
8484
const valueFactory = jest.fn().mockImplementation(async (p1: string, p2: number) => {
8585
return `${p1}${p2}`
8686
})
87-
const mapping = createCacheMap({ valueFactory, maxSize: 3 })
88-
const ids = range(SIZE)
87+
const mapping = createCacheMap({ valueFactory, maxSize })
88+
const ids = range(maxSize)
89+
/**
90+
* Each call to `get` is considered usage. The following populates the cache with
91+
* entries for: foo0, foo1, foo2 (3 in total).
92+
*/
8993
for (const id of ids) {
9094
await mapping.get('foo', id)
9195
}
92-
expect(valueFactory).toHaveBeenCalledTimes(3)
93-
// add a value which is not in cache
96+
expect(valueFactory).toHaveBeenCalledTimes(maxSize)
97+
/**
98+
* Calling `get` on a key that's not in the cache causes usage. It will discard
99+
* an item associated to `foo0` key (least recently used).
100+
*/
94101
await mapping.get('foo', -1)
95-
expect(valueFactory).toHaveBeenCalledTimes(4)
96-
// one of the items was removed from cache when -1 was added, now we is re-add that
97-
// (we don't know which item it was)
102+
expect(valueFactory).toHaveBeenCalledTimes(maxSize + 1)
103+
/**
104+
* The current list of keys (most-to-least recently used) goes as follows: foo-1,
105+
* foo2, foo1. Going through all ids now will reconstruct the initial collection
106+
* causing 3 hits.
107+
*/
98108
for (const id of ids) {
99109
await mapping.get('foo', id)
100110
}
101-
expect(valueFactory).toHaveBeenCalledTimes(5)
111+
expect(valueFactory).toHaveBeenCalledTimes(maxSize + 1 + 3)
102112
})
103113

104114
it('max age', async () => {

0 commit comments

Comments
 (0)