lru-cache
Advanced tools
Comparing version 7.9.1 to 7.10.0
336
index.js
@@ -1,3 +0,7 @@ | ||
const perf = typeof performance === 'object' && performance && | ||
typeof performance.now === 'function' ? performance : Date | ||
const perf = | ||
typeof performance === 'object' && | ||
performance && | ||
typeof performance.now === 'function' | ||
? performance | ||
: Date | ||
@@ -10,16 +14,21 @@ const hasAbortController = typeof AbortController === 'function' | ||
// our purposes, and if used properly, behaves the same. | ||
const AC = hasAbortController ? AbortController : Object.assign( | ||
class AbortController { | ||
constructor () { this.signal = new AC.AbortSignal } | ||
abort () { | ||
this.signal.dispatchEvent('abort') | ||
const AC = hasAbortController | ||
? AbortController | ||
: class AbortController { | ||
constructor() { | ||
this.signal = new AS() | ||
} | ||
abort() { | ||
this.signal.dispatchEvent('abort') | ||
} | ||
} | ||
}, | ||
{ | ||
AbortSignal: class AbortSignal { | ||
constructor () { | ||
const AS = hasAbortController | ||
? AbortSignal | ||
: class AbortSignal { | ||
constructor() { | ||
this.aborted = false | ||
this._listeners = [] | ||
} | ||
dispatchEvent (type) { | ||
dispatchEvent(type) { | ||
if (type === 'abort') { | ||
@@ -32,4 +41,4 @@ this.aborted = true | ||
} | ||
onabort () {} | ||
addEventListener (ev, fn) { | ||
onabort() {} | ||
addEventListener(ev, fn) { | ||
if (ev === 'abort') { | ||
@@ -39,3 +48,3 @@ this._listeners.push(fn) | ||
} | ||
removeEventListener (ev, fn) { | ||
removeEventListener(ev, fn) { | ||
if (ev === 'abort') { | ||
@@ -46,4 +55,2 @@ this._listeners = this._listeners.filter(f => f !== fn) | ||
} | ||
} | ||
) | ||
@@ -76,6 +83,6 @@ const warned = new Set() | ||
typeof process === 'object' && | ||
process && | ||
typeof process.emitWarning === 'function' | ||
? process.emitWarning(...a) | ||
: console.error(...a) | ||
process && | ||
typeof process.emitWarning === 'function' | ||
? process.emitWarning(...a) | ||
: console.error(...a) | ||
} | ||
@@ -101,11 +108,17 @@ | ||
* Maybe in the future, these limits will have expanded. */ | ||
const getUintArray = max => !isPosInt(max) ? null | ||
: max <= Math.pow(2, 8) ? Uint8Array | ||
: max <= Math.pow(2, 16) ? Uint16Array | ||
: max <= Math.pow(2, 32) ? Uint32Array | ||
: max <= Number.MAX_SAFE_INTEGER ? ZeroArray | ||
: null | ||
const getUintArray = max => | ||
!isPosInt(max) | ||
? null | ||
: max <= Math.pow(2, 8) | ||
? Uint8Array | ||
: max <= Math.pow(2, 16) | ||
? Uint16Array | ||
: max <= Math.pow(2, 32) | ||
? Uint32Array | ||
: max <= Number.MAX_SAFE_INTEGER | ||
? ZeroArray | ||
: null | ||
class ZeroArray extends Array { | ||
constructor (size) { | ||
constructor(size) { | ||
super(size) | ||
@@ -117,3 +130,3 @@ this.fill(0) | ||
class Stack { | ||
constructor (max) { | ||
constructor(max) { | ||
if (max === 0) { | ||
@@ -126,6 +139,6 @@ return [] | ||
} | ||
push (n) { | ||
push(n) { | ||
this.heap[this.length++] = n | ||
} | ||
pop () { | ||
pop() { | ||
return this.heap[--this.length] | ||
@@ -136,3 +149,3 @@ } | ||
class LRUCache { | ||
constructor (options = {}) { | ||
constructor(options = {}) { | ||
const { | ||
@@ -153,2 +166,3 @@ max = 0, | ||
fetchMethod, | ||
noDeleteOnFetchRejection, | ||
} = options | ||
@@ -158,7 +172,4 @@ | ||
// the thing being passed in is another LRUCache we're copying. | ||
const { | ||
length, | ||
maxAge, | ||
stale, | ||
} = options instanceof LRUCache ? {} : options | ||
const { length, maxAge, stale } = | ||
options instanceof LRUCache ? {} : options | ||
@@ -179,3 +190,5 @@ if (max !== 0 && !isPosInt(max)) { | ||
if (!this.maxSize) { | ||
throw new TypeError('cannot set sizeCalculation without setting maxSize') | ||
throw new TypeError( | ||
'cannot set sizeCalculation without setting maxSize' | ||
) | ||
} | ||
@@ -189,3 +202,5 @@ if (typeof this.sizeCalculation !== 'function') { | ||
if (this.fetchMethod && typeof this.fetchMethod !== 'function') { | ||
throw new TypeError('fetchMethod must be a function if specified') | ||
throw new TypeError( | ||
'fetchMethod must be a function if specified' | ||
) | ||
} | ||
@@ -216,6 +231,9 @@ | ||
this.noUpdateTTL = !!noUpdateTTL | ||
this.noDeleteOnFetchRejection = !!noDeleteOnFetchRejection | ||
if (this.maxSize !== 0) { | ||
if (!isPosInt(this.maxSize)) { | ||
throw new TypeError('maxSize must be a positive integer if specified') | ||
throw new TypeError( | ||
'maxSize must be a positive integer if specified' | ||
) | ||
} | ||
@@ -228,4 +246,6 @@ this.initializeSizeTracking() | ||
this.updateAgeOnHas = !!updateAgeOnHas | ||
this.ttlResolution = isPosInt(ttlResolution) || ttlResolution === 0 | ||
? ttlResolution : 1 | ||
this.ttlResolution = | ||
isPosInt(ttlResolution) || ttlResolution === 0 | ||
? ttlResolution | ||
: 1 | ||
this.ttlAutopurge = !!ttlAutopurge | ||
@@ -235,3 +255,5 @@ this.ttl = ttl || maxAge || 0 | ||
if (!isPosInt(this.ttl)) { | ||
throw new TypeError('ttl must be a positive integer if specified') | ||
throw new TypeError( | ||
'ttl must be a positive integer if specified' | ||
) | ||
} | ||
@@ -243,3 +265,5 @@ this.initializeTTLTracking() | ||
if (this.max === 0 && this.ttl === 0 && this.maxSize === 0) { | ||
throw new TypeError('At least one of max, maxSize, or ttl is required') | ||
throw new TypeError( | ||
'At least one of max, maxSize, or ttl is required' | ||
) | ||
} | ||
@@ -250,3 +274,4 @@ if (!this.ttlAutopurge && !this.max && !this.maxSize) { | ||
warned.add(code) | ||
const msg = 'TTL caching without ttlAutopurge, max, or maxSize can ' + | ||
const msg = | ||
'TTL caching without ttlAutopurge, max, or maxSize can ' + | ||
'result in unbounded memory consumption.' | ||
@@ -268,7 +293,7 @@ emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache) | ||
getRemainingTTL (key) { | ||
getRemainingTTL(key) { | ||
return this.has(key, { updateAgeOnHas: false }) ? Infinity : 0 | ||
} | ||
initializeTTLTracking () { | ||
initializeTTLTracking() { | ||
this.ttls = new ZeroArray(this.max) | ||
@@ -293,3 +318,3 @@ this.starts = new ZeroArray(this.max) | ||
this.updateItemAge = (index) => { | ||
this.updateItemAge = index => { | ||
this.starts[index] = this.ttls[index] !== 0 ? perf.now() : 0 | ||
@@ -305,3 +330,6 @@ } | ||
cachedNow = n | ||
const t = setTimeout(() => cachedNow = 0, this.ttlResolution) | ||
const t = setTimeout( | ||
() => (cachedNow = 0), | ||
this.ttlResolution | ||
) | ||
/* istanbul ignore else - not available on all platforms */ | ||
@@ -315,3 +343,3 @@ if (t.unref) { | ||
this.getRemainingTTL = (key) => { | ||
this.getRemainingTTL = key => { | ||
const index = this.keyMap.get(key) | ||
@@ -321,19 +349,29 @@ if (index === undefined) { | ||
} | ||
return this.ttls[index] === 0 || this.starts[index] === 0 ? Infinity | ||
: ((this.starts[index] + this.ttls[index]) - (cachedNow || getNow())) | ||
return this.ttls[index] === 0 || this.starts[index] === 0 | ||
? Infinity | ||
: this.starts[index] + | ||
this.ttls[index] - | ||
(cachedNow || getNow()) | ||
} | ||
this.isStale = (index) => { | ||
return this.ttls[index] !== 0 && this.starts[index] !== 0 && | ||
((cachedNow || getNow()) - this.starts[index] > this.ttls[index]) | ||
this.isStale = index => { | ||
return ( | ||
this.ttls[index] !== 0 && | ||
this.starts[index] !== 0 && | ||
(cachedNow || getNow()) - this.starts[index] > | ||
this.ttls[index] | ||
) | ||
} | ||
} | ||
updateItemAge (index) {} | ||
setItemTTL (index, ttl) {} | ||
isStale (index) { return false } | ||
updateItemAge(index) {} | ||
setItemTTL(index, ttl) {} | ||
isStale(index) { | ||
return false | ||
} | ||
initializeSizeTracking () { | ||
initializeSizeTracking() { | ||
this.calculatedSize = 0 | ||
this.sizes = new ZeroArray(this.max) | ||
this.removeItemSize = index => this.calculatedSize -= this.sizes[index] | ||
this.removeItemSize = index => | ||
(this.calculatedSize -= this.sizes[index]) | ||
this.requireSize = (k, v, size, sizeCalculation) => { | ||
@@ -347,6 +385,10 @@ if (!isPosInt(size)) { | ||
if (!isPosInt(size)) { | ||
throw new TypeError('sizeCalculation return invalid (expect positive integer)') | ||
throw new TypeError( | ||
'sizeCalculation return invalid (expect positive integer)' | ||
) | ||
} | ||
} else { | ||
throw new TypeError('invalid size value (must be positive integer)') | ||
throw new TypeError( | ||
'invalid size value (must be positive integer)' | ||
) | ||
} | ||
@@ -365,11 +407,13 @@ } | ||
} | ||
removeItemSize (index) {} | ||
addItemSize (index, v, k, size) {} | ||
requireSize (k, v, size, sizeCalculation) { | ||
removeItemSize(index) {} | ||
addItemSize(index, v, k, size) {} | ||
requireSize(k, v, size, sizeCalculation) { | ||
if (size || sizeCalculation) { | ||
throw new TypeError('cannot set size without setting maxSize on cache') | ||
throw new TypeError( | ||
'cannot set size without setting maxSize on cache' | ||
) | ||
} | ||
} | ||
*indexes ({ allowStale = this.allowStale } = {}) { | ||
*indexes({ allowStale = this.allowStale } = {}) { | ||
if (this.size) { | ||
@@ -392,3 +436,3 @@ for (let i = this.tail; true; ) { | ||
*rindexes ({ allowStale = this.allowStale } = {}) { | ||
*rindexes({ allowStale = this.allowStale } = {}) { | ||
if (this.size) { | ||
@@ -411,7 +455,7 @@ for (let i = this.head; true; ) { | ||
isValidIndex (index) { | ||
isValidIndex(index) { | ||
return this.keyMap.get(this.keyList[index]) === index | ||
} | ||
*entries () { | ||
*entries() { | ||
for (const i of this.indexes()) { | ||
@@ -421,3 +465,3 @@ yield [this.keyList[i], this.valList[i]] | ||
} | ||
*rentries () { | ||
*rentries() { | ||
for (const i of this.rindexes()) { | ||
@@ -428,3 +472,3 @@ yield [this.keyList[i], this.valList[i]] | ||
*keys () { | ||
*keys() { | ||
for (const i of this.indexes()) { | ||
@@ -434,3 +478,3 @@ yield this.keyList[i] | ||
} | ||
*rkeys () { | ||
*rkeys() { | ||
for (const i of this.rindexes()) { | ||
@@ -441,3 +485,3 @@ yield this.keyList[i] | ||
*values () { | ||
*values() { | ||
for (const i of this.indexes()) { | ||
@@ -447,3 +491,3 @@ yield this.valList[i] | ||
} | ||
*rvalues () { | ||
*rvalues() { | ||
for (const i of this.rindexes()) { | ||
@@ -454,7 +498,7 @@ yield this.valList[i] | ||
[Symbol.iterator] () { | ||
[Symbol.iterator]() { | ||
return this.entries() | ||
} | ||
find (fn, getOptions = {}) { | ||
find(fn, getOptions = {}) { | ||
for (const i of this.indexes()) { | ||
@@ -467,3 +511,3 @@ if (fn(this.valList[i], this.keyList[i], this)) { | ||
forEach (fn, thisp = this) { | ||
forEach(fn, thisp = this) { | ||
for (const i of this.indexes()) { | ||
@@ -474,3 +518,3 @@ fn.call(thisp, this.valList[i], this.keyList[i], this) | ||
rforEach (fn, thisp = this) { | ||
rforEach(fn, thisp = this) { | ||
for (const i of this.rindexes()) { | ||
@@ -481,3 +525,3 @@ fn.call(thisp, this.valList[i], this.keyList[i], this) | ||
get prune () { | ||
get prune() { | ||
deprecatedMethod('prune', 'purgeStale') | ||
@@ -487,3 +531,3 @@ return this.purgeStale | ||
purgeStale () { | ||
purgeStale() { | ||
let deleted = false | ||
@@ -499,3 +543,3 @@ for (const i of this.rindexes({ allowStale: true })) { | ||
dump () { | ||
dump() { | ||
const arr = [] | ||
@@ -517,3 +561,3 @@ for (const i of this.indexes()) { | ||
load (arr) { | ||
load(arr) { | ||
this.clear() | ||
@@ -525,11 +569,15 @@ for (const [key, entry] of arr) { | ||
dispose (v, k, reason) {} | ||
dispose(v, k, reason) {} | ||
set (k, v, { | ||
ttl = this.ttl, | ||
noDisposeOnSet = this.noDisposeOnSet, | ||
size = 0, | ||
sizeCalculation = this.sizeCalculation, | ||
noUpdateTTL = this.noUpdateTTL, | ||
} = {}) { | ||
set( | ||
k, | ||
v, | ||
{ | ||
ttl = this.ttl, | ||
noDisposeOnSet = this.noDisposeOnSet, | ||
size = 0, | ||
sizeCalculation = this.sizeCalculation, | ||
noUpdateTTL = this.noUpdateTTL, | ||
} = {} | ||
) { | ||
size = this.requireSize(k, v, size, sizeCalculation) | ||
@@ -546,3 +594,3 @@ let index = this.size === 0 ? undefined : this.keyMap.get(k) | ||
this.tail = index | ||
this.size ++ | ||
this.size++ | ||
this.addItemSize(index, v, k, size) | ||
@@ -584,3 +632,3 @@ noUpdateTTL = false | ||
newIndex () { | ||
newIndex() { | ||
if (this.size === 0) { | ||
@@ -599,3 +647,3 @@ return this.tail | ||
pop () { | ||
pop() { | ||
if (this.size) { | ||
@@ -608,3 +656,3 @@ const val = this.valList[this.head] | ||
evict (free) { | ||
evict(free) { | ||
const head = this.head | ||
@@ -630,7 +678,7 @@ const k = this.keyList[head] | ||
this.keyMap.delete(k) | ||
this.size -- | ||
this.size-- | ||
return head | ||
} | ||
has (k, { updateAgeOnHas = this.updateAgeOnHas } = {}) { | ||
has(k, { updateAgeOnHas = this.updateAgeOnHas } = {}) { | ||
const index = this.keyMap.get(k) | ||
@@ -649,3 +697,3 @@ if (index !== undefined) { | ||
// like get(), but without any LRU updating or TTL expiration | ||
peek (k, { allowStale = this.allowStale } = {}) { | ||
peek(k, { allowStale = this.allowStale } = {}) { | ||
const index = this.keyMap.get(k) | ||
@@ -657,3 +705,3 @@ if (index !== undefined && (allowStale || !this.isStale(index))) { | ||
backgroundFetch (k, index, options) { | ||
backgroundFetch(k, index, options) { | ||
const v = index === undefined ? undefined : this.valList[index] | ||
@@ -676,3 +724,12 @@ if (this.isBackgroundFetch(v)) { | ||
if (this.valList[index] === p) { | ||
this.delete(k) | ||
const del = | ||
!options.noDeleteOnFetchRejection || | ||
p.__staleWhileFetching === undefined | ||
if (del) { | ||
this.delete(k) | ||
} else { | ||
// still replace the *promise* with the stale value, | ||
// since we are done with the promise at this point. | ||
this.valList[index] = p.__staleWhileFetching | ||
} | ||
} | ||
@@ -697,21 +754,35 @@ if (p.__returned === p) { | ||
isBackgroundFetch (p) { | ||
return p && typeof p === 'object' && typeof p.then === 'function' && | ||
Object.prototype.hasOwnProperty.call(p, '__staleWhileFetching') && | ||
isBackgroundFetch(p) { | ||
return ( | ||
p && | ||
typeof p === 'object' && | ||
typeof p.then === 'function' && | ||
Object.prototype.hasOwnProperty.call( | ||
p, | ||
'__staleWhileFetching' | ||
) && | ||
Object.prototype.hasOwnProperty.call(p, '__returned') && | ||
(p.__returned === p || p.__returned === null) | ||
) | ||
} | ||
// this takes the union of get() and set() opts, because it does both | ||
async fetch (k, { | ||
allowStale = this.allowStale, | ||
updateAgeOnGet = this.updateAgeOnGet, | ||
ttl = this.ttl, | ||
noDisposeOnSet = this.noDisposeOnSet, | ||
size = 0, | ||
sizeCalculation = this.sizeCalculation, | ||
noUpdateTTL = this.noUpdateTTL, | ||
} = {}) { | ||
async fetch( | ||
k, | ||
{ | ||
// get options | ||
allowStale = this.allowStale, | ||
updateAgeOnGet = this.updateAgeOnGet, | ||
// set options | ||
ttl = this.ttl, | ||
noDisposeOnSet = this.noDisposeOnSet, | ||
size = 0, | ||
sizeCalculation = this.sizeCalculation, | ||
noUpdateTTL = this.noUpdateTTL, | ||
// fetch exclusive options | ||
noDeleteOnFetchRejection = this.noDeleteOnFetchRejection, | ||
} = {} | ||
) { | ||
if (!this.fetchMethod) { | ||
return this.get(k, {allowStale, updateAgeOnGet}) | ||
return this.get(k, { allowStale, updateAgeOnGet }) | ||
} | ||
@@ -727,2 +798,3 @@ | ||
noUpdateTTL, | ||
noDeleteOnFetchRejection, | ||
} | ||
@@ -739,3 +811,4 @@ | ||
return allowStale && v.__staleWhileFetching !== undefined | ||
? v.__staleWhileFetching : (v.__returned = v) | ||
? v.__staleWhileFetching | ||
: (v.__returned = v) | ||
} | ||
@@ -755,10 +828,14 @@ | ||
return allowStale && p.__staleWhileFetching !== undefined | ||
? p.__staleWhileFetching : (p.__returned = p) | ||
? p.__staleWhileFetching | ||
: (p.__returned = p) | ||
} | ||
} | ||
get (k, { | ||
allowStale = this.allowStale, | ||
updateAgeOnGet = this.updateAgeOnGet, | ||
} = {}) { | ||
get( | ||
k, | ||
{ | ||
allowStale = this.allowStale, | ||
updateAgeOnGet = this.updateAgeOnGet, | ||
} = {} | ||
) { | ||
const index = this.keyMap.get(k) | ||
@@ -792,3 +869,3 @@ if (index !== undefined) { | ||
connect (p, n) { | ||
connect(p, n) { | ||
this.prev[n] = p | ||
@@ -798,3 +875,3 @@ this.next[p] = n | ||
moveToTail (index) { | ||
moveToTail(index) { | ||
// if tail already, nothing to do | ||
@@ -819,3 +896,3 @@ // if head, move head to next[index] | ||
get del () { | ||
get del() { | ||
deprecatedMethod('del', 'delete') | ||
@@ -825,3 +902,3 @@ return this.delete | ||
delete (k) { | ||
delete(k) { | ||
let deleted = false | ||
@@ -856,3 +933,3 @@ if (this.size !== 0) { | ||
} | ||
this.size -- | ||
this.size-- | ||
this.free.push(index) | ||
@@ -870,3 +947,3 @@ } | ||
clear () { | ||
clear() { | ||
for (const index of this.rindexes({ allowStale: true })) { | ||
@@ -908,3 +985,3 @@ const v = this.valList[index] | ||
get reset () { | ||
get reset() { | ||
deprecatedMethod('reset', 'clear') | ||
@@ -914,3 +991,3 @@ return this.clear | ||
get length () { | ||
get length() { | ||
deprecatedProperty('length', 'size') | ||
@@ -920,7 +997,10 @@ return this.size | ||
static get AbortController () { | ||
static get AbortController() { | ||
return AC | ||
} | ||
static get AbortSignal() { | ||
return AS | ||
} | ||
} | ||
module.exports = LRUCache |
{ | ||
"name": "lru-cache", | ||
"description": "A cache object that deletes the least-recently-used items.", | ||
"version": "7.9.1", | ||
"version": "7.10.0", | ||
"author": "Isaac Z. Schlueter <i@izs.me>", | ||
@@ -13,8 +13,9 @@ "keywords": [ | ||
"build": "", | ||
"size": "size-limit", | ||
"test": "tap", | ||
"snap": "tap", | ||
"size": "size-limit", | ||
"preversion": "npm test", | ||
"postversion": "npm publish", | ||
"prepublishOnly": "git push origin --follow-tags" | ||
"prepublishOnly": "git push origin --follow-tags", | ||
"format": "prettier --write ." | ||
}, | ||
@@ -25,10 +26,19 @@ "main": "index.js", | ||
"@size-limit/preset-small-lib": "^7.0.8", | ||
"@types/node": "^17.0.31", | ||
"@types/tap": "^15.0.6", | ||
"benchmark": "^2.1.4", | ||
"c8": "^7.11.2", | ||
"clock-mock": "^1.0.4", | ||
"eslint-config-prettier": "^8.5.0", | ||
"prettier": "^2.6.2", | ||
"size-limit": "^7.0.8", | ||
"tap": "^15.1.6" | ||
"tap": "^16.0.1", | ||
"ts-node": "^10.7.0", | ||
"tslib": "^2.4.0", | ||
"typescript": "^4.6.4" | ||
}, | ||
"license": "ISC", | ||
"files": [ | ||
"index.js" | ||
"index.js", | ||
"index.d.ts" | ||
], | ||
@@ -38,7 +48,20 @@ "engines": { | ||
}, | ||
"prettier": { | ||
"semi": false, | ||
"printWidth": 70, | ||
"tabWidth": 2, | ||
"useTabs": false, | ||
"singleQuote": true, | ||
"jsxSingleQuote": false, | ||
"bracketSameLine": true, | ||
"arrowParens": "avoid", | ||
"endOfLine": "lf" | ||
}, | ||
"tap": { | ||
"coverage-map": "map.js", | ||
"node-arg": [ | ||
"--expose-gc" | ||
] | ||
"--expose-gc", | ||
"--require", | ||
"ts-node/register" | ||
], | ||
"ts": false | ||
}, | ||
@@ -45,0 +68,0 @@ "size-limit": [ |
@@ -167,2 +167,19 @@ # lru-cache | ||
### `noDeleteOnFetchRejection` | ||
If a `fetchMethod` throws an error or returns a rejected promise, | ||
then by default, any existing stale value will be removed from | ||
the cache. | ||
If `noDeleteOnFetchRejection` is set to `true`, then this | ||
behavior is suppressed, and the stale value remains in the cache | ||
in the case of a rejected `fetchMethod`. | ||
This is important in cases where a `fetchMethod` is _only_ called | ||
as a background update while the stale value is returned, when | ||
`allowStale` is used. | ||
This may be set in calls to `fetch()`, or defaulted on the | ||
constructor. | ||
### `dispose` | ||
@@ -169,0 +186,0 @@ |
68668
5
1376
772
13