Skip to content

Commit ab5e1b0

Browse files
committed
add 'perf' option
This enables the consumer to override Date/performance object usage once at the point of LRU object creation. This is an affordance to make the TTL behavior easier to test in some scenarios, without sacrificing security or performance. It should most likely not be used in production! Re: isaacs#345 PR-URL: isaacs#386 Credit: @isaacs Close: isaacs#386 Reviewed-by: @isaacs
1 parent 673aa5b commit ab5e1b0

File tree

3 files changed

+41
-9
lines changed

3 files changed

+41
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# cringe lorg
22

3+
## 11.2
4+
5+
- Add the `perf` option to specify `performance`, `Date`, or any
6+
other object with a `now()` method that returns a number.
7+
38
## 11.1
49

510
- Add the `onInsert` method

src/index.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// it can be passed in via configuration to override it
88
// for a single LRU object.
99
export type Perf = { now: () => number }
10-
const perf: Perf =
10+
const defaultPerf: Perf =
1111
(
1212
typeof performance === 'object' &&
1313
performance &&
@@ -1166,6 +1166,14 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
11661166
readonly #disposeAfter?: LRUCache.Disposer<K, V>
11671167
readonly #fetchMethod?: LRUCache.Fetcher<K, V, FC>
11681168
readonly #memoMethod?: LRUCache.Memoizer<K, V, FC>
1169+
readonly #perf: Perf
1170+
1171+
/**
1172+
* {@link LRUCache.OptionsBase.perf}
1173+
*/
1174+
get perf() {
1175+
return this.#perf
1176+
}
11691177

11701178
/**
11711179
* {@link LRUCache.OptionsBase.ttl}
@@ -1387,8 +1395,19 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
13871395
allowStaleOnFetchRejection,
13881396
allowStaleOnFetchAbort,
13891397
ignoreFetchAbort,
1398+
perf,
13901399
} = options
13911400

1401+
if (perf !== undefined) {
1402+
if (typeof perf?.now !== 'function') {
1403+
throw new TypeError(
1404+
'perf option must have a now() method if specified',
1405+
)
1406+
}
1407+
}
1408+
1409+
this.#perf = perf ?? defaultPerf
1410+
13921411
if (max !== 0 && !isPosInt(max)) {
13931412
throw new TypeError('max option must be a nonnegative integer')
13941413
}
@@ -1535,7 +1554,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
15351554
this.#ttls = ttls
15361555
this.#starts = starts
15371556

1538-
this.#setItemTTL = (index, ttl, start = perf.now()) => {
1557+
this.#setItemTTL = (index, ttl, start = this.#perf.now()) => {
15391558
starts[index] = ttl !== 0 ? start : 0
15401559
ttls[index] = ttl
15411560
if (ttl !== 0 && this.ttlAutopurge) {
@@ -1554,7 +1573,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
15541573
}
15551574

15561575
this.#updateItemAge = index => {
1557-
starts[index] = ttls[index] !== 0 ? perf.now() : 0
1576+
starts[index] = ttls[index] !== 0 ? this.#perf.now() : 0
15581577
}
15591578

15601579
this.#statusTTL = (status, index) => {
@@ -1575,7 +1594,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
15751594
// that costly call repeatedly.
15761595
let cachedNow = 0
15771596
const getNow = () => {
1578-
const n = perf.now()
1597+
const n = this.#perf.now()
15791598
if (this.ttlResolution > 0) {
15801599
cachedNow = n
15811600
const t = setTimeout(
@@ -1959,7 +1978,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
19591978
const i = this.#keyMap.get(key)
19601979
if (i === undefined) return undefined
19611980
const v = this.#valList[i]
1962-
/* c8 ignore start - this isn't tested for the info function,
1981+
/* c8 ignore start - this isn't tested for the info function,
19631982
* but it's the same logic as found in other places. */
19641983
const value: V | undefined =
19651984
this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v
@@ -1970,7 +1989,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
19701989
const ttl = this.#ttls[i]
19711990
const start = this.#starts[i]
19721991
if (ttl && start) {
1973-
const remain = ttl - (perf.now() - start)
1992+
const remain = ttl - (this.#perf.now() - start)
19741993
entry.ttl = remain
19751994
entry.start = Date.now()
19761995
}
@@ -2007,7 +2026,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
20072026
entry.ttl = this.#ttls[i]
20082027
// always dump the start relative to a portable timestamp
20092028
// it's ok for this to be a bit slow, it's a rare operation.
2010-
const age = perf.now() - (this.#starts[i] as number)
2029+
const age = this.#perf.now() - (this.#starts[i] as number)
20112030
entry.start = Math.floor(Date.now() - age)
20122031
}
20132032
if (this.#sizes) {
@@ -2038,7 +2057,7 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
20382057
//
20392058
// it's ok for this to be a bit slow, it's a rare operation.
20402059
const age = Date.now() - entry.start
2041-
entry.start = perf.now() - age
2060+
entry.start = this.#perf.now() - age
20422061
}
20432062
this.set(key, entry.value, entry)
20442063
}

test/basic.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ t.test('basic operation', t => {
2828
statuses.push(status)
2929
return status
3030
}
31-
const c = new LRU({ max: 10 })
31+
32+
//@ts-expect-error
33+
t.throws(() => new LRU({ max: 10, perf: {} }), {
34+
name: 'TypeError',
35+
message: 'perf option must have a now() method if specified',
36+
})
37+
38+
const c = new LRU({ max: 10, perf: Date })
39+
t.equal(c.perf, Date)
3240
for (let i = 0; i < 5; i++) {
3341
t.equal(c.set(i, i, { status: s() }), c)
3442
}

0 commit comments

Comments
 (0)