Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: isaacs/node-lru-cache
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.7.4
Choose a base ref
...
head repository: isaacs/node-lru-cache
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.8.0
Choose a head ref
  • 7 commits
  • 9 files changed
  • 1 contributor

Commits on Apr 7, 2022

  1. Copy the full SHA
    0e242f7 View commit details
  2. document max better in code example

    Fix: #224
    Close: #226
    isaacs committed Apr 7, 2022
    Copy the full SHA
    74044a7 View commit details
  3. feat: updateAgeOnHas

    isaacs committed Apr 7, 2022
    Copy the full SHA
    44c5ec9 View commit details
  4. Copy the full SHA
    9cf1bc9 View commit details
  5. Copy the full SHA
    90ba4d9 View commit details
  6. changelog for 7.8

    isaacs committed Apr 7, 2022
    Copy the full SHA
    89411e6 View commit details
  7. 7.8.0

    isaacs committed Apr 7, 2022
    Copy the full SHA
    3cca7d2 View commit details
Showing with 188 additions and 22 deletions.
  1. +8 −0 CHANGELOG.md
  2. +18 −5 README.md
  3. +26 −9 index.js
  4. +2 −2 package-lock.json
  5. +1 −1 package.json
  6. +47 −0 tap-snapshots/test/deprecations.js.test.cjs
  7. +9 −2 test/deprecations.js
  8. +66 −0 test/fetch.js
  9. +11 −3 test/ttl.js
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# cringe lorg

## 7.8.0

* add `updateAgeOnHas` option
* warnings sent to `console.error` if `process.emitWarning` unavailable

## 7.7.0

* fetch: provide options and abort signal
@@ -66,6 +71,9 @@ well.
* `reset()` method -> `clear()`
* The objects used by `cache.load()` and `cache.dump()` are incompatible
with previous versions.
* `max` and `maxSize` are now two separate options. (Previously, they were
a single `max` option, which would be based on either count or computed
size.)

## v6 - 2020-07

23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -27,8 +27,10 @@ npm install lru-cache --save
```js
const LRU = require('lru-cache')

// only 'max' is required, the others are optional, but MAY be
// required if certain other fields are set.
// At least one of 'max', 'ttl', or 'maxSize' is required, to prevent
// unsafe unbounded storage.
// In most cases, it's best to specify a max for performance, so all
// the required memory allocation is done up-front.
const options = {
// the number of most recently used items to keep.
// note that we may store fewer items than this if maxSize is hit.
@@ -308,6 +310,15 @@ If you put more stuff in it, then items will fall out.

Boolean, default false, only relevant if `ttl` is set.

* `updateAgeOnHas` - When using time-expiring entries with `ttl`, setting
this to `true` will make each item's age reset to 0 whenever its presence
in the cache is checked with `has()`, causing it to not expire. (It can
still fall out of cache based on recency of use, of course.)

This may be overridden by passing an options object to `cache.has()`.

Boolean, default false, only relevant if `ttl` is set.

## API

* `new LRUCache(options)`
@@ -317,7 +328,7 @@ If you put more stuff in it, then items will fall out.

* `cache.max`, `cache.maxSize`, `cache.allowStale`, `cache.noDisposeOnSet`,
`cache.sizeCalculation`, `cache.dispose`, `cache.maxSize`, `cache.ttl`,
`cache.updateAgeOnGet`
`cache.updateAgeOnGet`, `cache.updateAgeOnHas`

All option names are exposed as public members on the cache object.

@@ -390,9 +401,11 @@ If you put more stuff in it, then items will fall out.
Returns `undefined` if the item is stale, unless `allowStale` is set
either on the cache or in the options object.

* `has(key)`
* `has(key, { updateAgeOnHas } = {}) => Boolean`

Check if a key is in the cache, without updating the recency or age.
Check if a key is in the cache, without updating the recency of use.
Age is updated if `updateAgeOnHas` is set to `true` in either the
options or the constructor.

Will return `false` if the item is stale, even though it is technically
in the cache.
35 changes: 26 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ const perf = typeof performance === 'object' && performance &&

const hasAbortController = typeof AbortController !== 'undefined'

/* istanbul ignore next - minimal backwards compatibility polyfill */
// minimal backwards-compatibility polyfill
const AC = hasAbortController ? AbortController : Object.assign(
class AbortController {
constructor () { this.signal = new AC.AbortSignal }
@@ -36,14 +36,20 @@ const deprecatedProperty = (field, instead) => {
}
}

const shouldWarn = code => typeof process === 'object' &&
process &&
!warned.has(code)
const emitWarning = (...a) => {
typeof process === 'object' &&
process &&
typeof process.emitWarning === 'function'
? process.emitWarning(...a)
: console.error(...a)
}

const shouldWarn = code => !warned.has(code)

const warn = (code, what, instead, fn) => {
warned.add(code)
const msg = `The ${what} is deprecated. Please use ${instead} instead.`
process.emitWarning(msg, 'DeprecationWarning', code, fn)
emitWarning(msg, 'DeprecationWarning', code, fn)
}

const isPosInt = n => n && n === Math.floor(n) && n > 0 && isFinite(n)
@@ -92,6 +98,7 @@ class LRUCache {
ttlResolution = 1,
ttlAutopurge,
updateAgeOnGet,
updateAgeOnHas,
allowStale,
dispose,
disposeAfter,
@@ -170,6 +177,7 @@ class LRUCache {

this.allowStale = !!allowStale || !!stale
this.updateAgeOnGet = !!updateAgeOnGet
this.updateAgeOnHas = !!updateAgeOnHas
this.ttlResolution = isPosInt(ttlResolution) || ttlResolution === 0
? ttlResolution : 1
this.ttlAutopurge = !!ttlAutopurge
@@ -191,7 +199,7 @@ class LRUCache {
warned.add(code)
const msg = 'TTL caching without ttlAutopurge, max, or maxSize can ' +
'result in unbounded memory consumption.'
process.emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache)
emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache)
}
}

@@ -207,7 +215,7 @@ class LRUCache {
}

getRemainingTTL (key) {
return this.has(key) ? Infinity : 0
return this.has(key, { updateAgeOnHas: false }) ? Infinity : 0
}

initializeTTLTracking () {
@@ -549,8 +557,17 @@ class LRUCache {
return head
}

has (k) {
return this.keyMap.has(k) && !this.isStale(this.keyMap.get(k))
has (k, { updateAgeOnHas = this.updateAgeOnHas } = {}) {
const index = this.keyMap.get(k)
if (index !== undefined) {
if (!this.isStale(index)) {
if (updateAgeOnHas) {
this.updateItemAge(index)
}
return true
}
}
return false
}

// like get(), but without any LRU updating or TTL expiration
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "lru-cache",
"description": "A cache object that deletes the least-recently-used items.",
"version": "7.7.3",
"version": "7.8.0",
"author": "Isaac Z. Schlueter <i@izs.me>",
"keywords": [
"mru",
47 changes: 47 additions & 0 deletions tap-snapshots/test/deprecations.js.test.cjs
Original file line number Diff line number Diff line change
@@ -5,6 +5,53 @@
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/deprecations.js TAP does not do deprecation warning without process object > warnings sent to console.error 1`] = `
Array [
Array [
"The stale option is deprecated. Please use options.allowStale instead.",
"DeprecationWarning",
"LRU_CACHE_OPTION_stale",
Function LRUCache(classLRUCache),
],
Array [
"The maxAge option is deprecated. Please use options.ttl instead.",
"DeprecationWarning",
"LRU_CACHE_OPTION_maxAge",
Function LRUCache(classLRUCache),
],
Array [
"The length option is deprecated. Please use options.sizeCalculation instead.",
"DeprecationWarning",
"LRU_CACHE_OPTION_length",
Function LRUCache(classLRUCache),
],
Array [
"The reset method is deprecated. Please use cache.clear() instead.",
"DeprecationWarning",
"LRU_CACHE_METHOD_reset",
Function get reset(),
],
Array [
"The length property is deprecated. Please use cache.size instead.",
"DeprecationWarning",
"LRU_CACHE_PROPERTY_length",
Function get length(),
],
Array [
"The prune method is deprecated. Please use cache.purgeStale() instead.",
"DeprecationWarning",
"LRU_CACHE_METHOD_prune",
Function get prune(),
],
Array [
"The del method is deprecated. Please use cache.delete() instead.",
"DeprecationWarning",
"LRU_CACHE_METHOD_del",
Function get del(),
],
]
`

exports[`test/deprecations.js TAP warns exactly once for a given deprecation > must match snapshot 1`] = `
Array [
Array [
11 changes: 9 additions & 2 deletions test/deprecations.js
Original file line number Diff line number Diff line change
@@ -47,9 +47,15 @@ t.test('warns exactly once for a given deprecation', t => {
t.test('does not do deprecation warning without process object', t => {
// set process to null (emulate a browser)
const proc = process
t.teardown(() => global.process = proc)
const {error} = console
t.teardown(() => {
global.process = proc
console.error = error
})
const consoleErrors = []
console.error = (...a) => consoleErrors.push(a)
global.process = null

const LRU = t.mock('../')
const c = new LRU({
max: 100,
maxSize: 100,
@@ -64,6 +70,7 @@ t.test('does not do deprecation warning without process object', t => {
t.equal(c.del, c.delete)

t.strictSame(warnings, [], 'no process exists')
t.matchSnapshot(consoleErrors, 'warnings sent to console.error')

t.end()
})
66 changes: 66 additions & 0 deletions test/fetch.js
Original file line number Diff line number Diff line change
@@ -157,3 +157,69 @@ t.test('fetch options, signal', async t => {
const v5 = await c.fetch(2, { ttl: 1 })
t.equal(c.getRemainingTTL(2), 25, 'overridden ttl in fetchMethod')
})

t.test('fetch options, signal, with polyfill', async t => {
const {AbortController} = global
t.teardown(() => global.AbortController = AbortController)
global.AbortController = undefined
const LRU = t.mock('../')
let aborted = false
const disposed = []
const disposedAfter = []
const c = new LRU({
max: 3,
ttl: 100,
fetchMethod: async (k, oldVal, { signal, options }) => {
// do something async
await new Promise(res => setImmediate(res))
if (signal.aborted) {
aborted = true
return
}
if (k === 2) {
options.ttl = 25
}
return (oldVal || 0) + 1
},
dispose: (v, k, reason) => {
disposed.push([v, k, reason])
},
disposeAfter: (v, k, reason) => {
disposedAfter.push([v, k, reason])
},
})

const v1 = c.fetch(2)
c.delete(2)
t.equal(await v1, undefined, 'no value returned, aborted by delete')
t.equal(aborted, true)
t.same(disposed, [], 'no disposals for aborted promises')
t.same(disposedAfter, [], 'no disposals for aborted promises')

aborted = false
const v2 = c.fetch(2)
c.set(2, 2)
t.equal(await v2, undefined, 'no value returned, aborted by set')
t.equal(aborted, true)
t.same(disposed, [], 'no disposals for aborted promises')
t.same(disposedAfter, [], 'no disposals for aborted promises')
c.delete(2)
disposed.length = 0
disposedAfter.length = 0

aborted = false
const v3 = c.fetch(2)
c.set(3, 3)
c.set(4, 4)
c.set(5, 5)
t.equal(await v3, undefined, 'no value returned, aborted by evict')
t.equal(aborted, true)
t.same(disposed, [], 'no disposals for aborted promises')
t.same(disposedAfter, [], 'no disposals for aborted promises')

aborted = false
const v4 = await c.fetch(6, { ttl: 1000 })
t.equal(c.getRemainingTTL(6), 1000, 'overridden ttl in fetch() opts')
const v5 = await c.fetch(2, { ttl: 1 })
t.equal(c.getRemainingTTL(2), 25, 'overridden ttl in fetchMethod')
})
14 changes: 11 additions & 3 deletions test/ttl.js
Original file line number Diff line number Diff line change
@@ -198,16 +198,24 @@ const runTests = (LRU, t) => {
t.end()
})

t.test('ttl with updateAgeOnGet', t => {
const c = new LRU({ max: 5, ttl: 10, updateAgeOnGet: true, ttlResolution: 0 })
t.test('ttl with updateAgeOnGet/updateAgeOnHas', t => {
const c = new LRU({
max: 5,
ttl: 10,
updateAgeOnGet: true,
updateAgeOnHas: true,
ttlResolution: 0,
})
c.set(1, 1)
t.equal(c.get(1), 1)
clock.advance(5)
t.equal(c.get(1), 1)
t.equal(c.has(1), true)
clock.advance(5)
t.equal(c.get(1), 1)
clock.advance(1)
t.equal(c.getRemainingTTL(1), 9)
t.equal(c.has(1), true)
t.equal(c.getRemainingTTL(1), 10)
t.equal(c.get(1), 1)
t.equal(c.size, 1)
c.clear()