wallet-core/node_modules/lru-cache/index.js

468 lines
10 KiB
JavaScript
Raw Normal View History

2017-08-14 05:01:11 +02:00
'use strict'
2017-05-27 17:36:13 +02:00
module.exports = LRUCache
// This will be a proper iterable 'Map' in engines that support it,
// or a fakey-fake PseudoMap in older versions.
var Map = require('pseudomap')
var util = require('util')
// A linked list to keep track of recently-used-ness
var Yallist = require('yallist')
// use symbols if possible, otherwise just _props
var hasSymbol = typeof Symbol === 'function'
var makeSymbol
if (hasSymbol) {
makeSymbol = function (key) {
return Symbol.for(key)
}
2016-10-10 03:43:44 +02:00
} else {
2017-05-27 17:36:13 +02:00
makeSymbol = function (key) {
return '_' + key
}
2016-10-10 03:43:44 +02:00
}
2017-08-14 05:01:11 +02:00
var MAX = makeSymbol('max')
var LENGTH = makeSymbol('length')
var LENGTH_CALCULATOR = makeSymbol('lengthCalculator')
var ALLOW_STALE = makeSymbol('allowStale')
var MAX_AGE = makeSymbol('maxAge')
var DISPOSE = makeSymbol('dispose')
var NO_DISPOSE_ON_SET = makeSymbol('noDisposeOnSet')
var LRU_LIST = makeSymbol('lruList')
var CACHE = makeSymbol('cache')
2016-10-10 03:43:44 +02:00
function naiveLength () { return 1 }
2017-05-27 17:36:13 +02:00
// lruList is a yallist where the head is the youngest
// item, and the tail is the oldest. the list contains the Hit
// objects as the entries.
// Each Hit object has a reference to its Yallist.Node. This
// never changes.
//
// cache is a Map (or PseudoMap) that matches the keys to
// the Yallist.Node object.
2016-10-10 03:43:44 +02:00
function LRUCache (options) {
2017-05-27 17:36:13 +02:00
if (!(this instanceof LRUCache)) {
2016-10-10 03:43:44 +02:00
return new LRUCache(options)
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
2017-05-27 17:36:13 +02:00
if (typeof options === 'number') {
2016-10-10 03:43:44 +02:00
options = { max: options }
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
2017-05-27 17:36:13 +02:00
if (!options) {
2016-10-10 03:43:44 +02:00
options = {}
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
2017-08-14 05:01:11 +02:00
var max = this[MAX] = options.max
2016-10-10 03:43:44 +02:00
// Kind of weird to have a default max of Infinity, but oh well.
2017-05-27 17:36:13 +02:00
if (!max ||
!(typeof max === 'number') ||
max <= 0) {
2017-08-14 05:01:11 +02:00
this[MAX] = Infinity
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
2017-05-27 17:36:13 +02:00
var lc = options.length || naiveLength
if (typeof lc !== 'function') {
lc = naiveLength
}
2017-08-14 05:01:11 +02:00
this[LENGTH_CALCULATOR] = lc
2016-10-10 03:43:44 +02:00
2017-08-14 05:01:11 +02:00
this[ALLOW_STALE] = options.stale || false
this[MAX_AGE] = options.maxAge || 0
this[DISPOSE] = options.dispose
this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false
2016-10-10 03:43:44 +02:00
this.reset()
}
// resize the cache when the max changes.
2017-05-27 17:36:13 +02:00
Object.defineProperty(LRUCache.prototype, 'max', {
set: function (mL) {
if (!mL || !(typeof mL === 'number') || mL <= 0) {
mL = Infinity
2016-10-10 03:43:44 +02:00
}
2017-08-14 05:01:11 +02:00
this[MAX] = mL
2017-05-27 17:36:13 +02:00
trim(this)
},
get: function () {
2017-08-14 05:01:11 +02:00
return this[MAX]
2017-05-27 17:36:13 +02:00
},
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'allowStale', {
set: function (allowStale) {
2017-08-14 05:01:11 +02:00
this[ALLOW_STALE] = !!allowStale
2017-05-27 17:36:13 +02:00
},
get: function () {
2017-08-14 05:01:11 +02:00
return this[ALLOW_STALE]
2017-05-27 17:36:13 +02:00
},
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'maxAge', {
set: function (mA) {
if (!mA || !(typeof mA === 'number') || mA < 0) {
mA = 0
}
2017-08-14 05:01:11 +02:00
this[MAX_AGE] = mA
2017-05-27 17:36:13 +02:00
trim(this)
},
get: function () {
2017-08-14 05:01:11 +02:00
return this[MAX_AGE]
2017-05-27 17:36:13 +02:00
},
enumerable: true
})
2016-10-10 03:43:44 +02:00
// resize the cache when the lengthCalculator changes.
2017-05-27 17:36:13 +02:00
Object.defineProperty(LRUCache.prototype, 'lengthCalculator', {
set: function (lC) {
if (typeof lC !== 'function') {
lC = naiveLength
2016-10-10 03:43:44 +02:00
}
2017-08-14 05:01:11 +02:00
if (lC !== this[LENGTH_CALCULATOR]) {
this[LENGTH_CALCULATOR] = lC
this[LENGTH] = 0
this[LRU_LIST].forEach(function (hit) {
hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key)
this[LENGTH] += hit.length
2017-05-27 17:36:13 +02:00
}, this)
}
trim(this)
},
2017-08-14 05:01:11 +02:00
get: function () { return this[LENGTH_CALCULATOR] },
2017-05-27 17:36:13 +02:00
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'length', {
2017-08-14 05:01:11 +02:00
get: function () { return this[LENGTH] },
2017-05-27 17:36:13 +02:00
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'itemCount', {
2017-08-14 05:01:11 +02:00
get: function () { return this[LRU_LIST].length },
2017-05-27 17:36:13 +02:00
enumerable: true
})
LRUCache.prototype.rforEach = function (fn, thisp) {
thisp = thisp || this
2017-08-14 05:01:11 +02:00
for (var walker = this[LRU_LIST].tail; walker !== null;) {
2017-05-27 17:36:13 +02:00
var prev = walker.prev
forEachStep(this, fn, walker, thisp)
walker = prev
}
}
2016-10-10 03:43:44 +02:00
2017-05-27 17:36:13 +02:00
function forEachStep (self, fn, node, thisp) {
var hit = node.value
if (isStale(self, hit)) {
del(self, node)
2017-08-14 05:01:11 +02:00
if (!self[ALLOW_STALE]) {
2017-05-27 17:36:13 +02:00
hit = undefined
}
}
if (hit) {
fn.call(thisp, hit.value, hit.key, self)
}
}
2016-10-10 03:43:44 +02:00
LRUCache.prototype.forEach = function (fn, thisp) {
thisp = thisp || this
2017-08-14 05:01:11 +02:00
for (var walker = this[LRU_LIST].head; walker !== null;) {
2017-05-27 17:36:13 +02:00
var next = walker.next
forEachStep(this, fn, walker, thisp)
walker = next
2016-10-10 03:43:44 +02:00
}
}
LRUCache.prototype.keys = function () {
2017-08-14 05:01:11 +02:00
return this[LRU_LIST].toArray().map(function (k) {
2017-05-27 17:36:13 +02:00
return k.key
}, this)
2016-10-10 03:43:44 +02:00
}
LRUCache.prototype.values = function () {
2017-08-14 05:01:11 +02:00
return this[LRU_LIST].toArray().map(function (k) {
2017-05-27 17:36:13 +02:00
return k.value
}, this)
2016-10-10 03:43:44 +02:00
}
LRUCache.prototype.reset = function () {
2017-08-14 05:01:11 +02:00
if (this[DISPOSE] &&
this[LRU_LIST] &&
this[LRU_LIST].length) {
this[LRU_LIST].forEach(function (hit) {
this[DISPOSE](hit.key, hit.value)
2017-05-27 17:36:13 +02:00
}, this)
2016-10-10 03:43:44 +02:00
}
2017-08-14 05:01:11 +02:00
this[CACHE] = new Map() // hash of items by key
this[LRU_LIST] = new Yallist() // list of items in order of use recency
this[LENGTH] = 0 // length of items in the list
2016-10-10 03:43:44 +02:00
}
LRUCache.prototype.dump = function () {
2017-08-14 05:01:11 +02:00
return this[LRU_LIST].map(function (hit) {
2016-10-10 03:43:44 +02:00
if (!isStale(this, hit)) {
2017-05-27 17:36:13 +02:00
return {
2016-10-10 03:43:44 +02:00
k: hit.key,
v: hit.value,
e: hit.now + (hit.maxAge || 0)
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
}
2017-05-27 17:36:13 +02:00
}, this).toArray().filter(function (h) {
return h
})
2016-10-10 03:43:44 +02:00
}
LRUCache.prototype.dumpLru = function () {
2017-08-14 05:01:11 +02:00
return this[LRU_LIST]
2017-05-27 17:36:13 +02:00
}
LRUCache.prototype.inspect = function (n, opts) {
var str = 'LRUCache {'
var extras = false
2017-08-14 05:01:11 +02:00
var as = this[ALLOW_STALE]
2017-05-27 17:36:13 +02:00
if (as) {
str += '\n allowStale: true'
extras = true
}
2017-08-14 05:01:11 +02:00
var max = this[MAX]
2017-05-27 17:36:13 +02:00
if (max && max !== Infinity) {
if (extras) {
str += ','
}
str += '\n max: ' + util.inspect(max, opts)
extras = true
}
2017-08-14 05:01:11 +02:00
var maxAge = this[MAX_AGE]
2017-05-27 17:36:13 +02:00
if (maxAge) {
if (extras) {
str += ','
}
str += '\n maxAge: ' + util.inspect(maxAge, opts)
extras = true
}
2017-08-14 05:01:11 +02:00
var lc = this[LENGTH_CALCULATOR]
2017-05-27 17:36:13 +02:00
if (lc && lc !== naiveLength) {
if (extras) {
str += ','
}
2017-08-14 05:01:11 +02:00
str += '\n length: ' + util.inspect(this[LENGTH], opts)
2017-05-27 17:36:13 +02:00
extras = true
}
var didFirst = false
2017-08-14 05:01:11 +02:00
this[LRU_LIST].forEach(function (item) {
2017-05-27 17:36:13 +02:00
if (didFirst) {
str += ',\n '
} else {
if (extras) {
str += ',\n'
}
didFirst = true
str += '\n '
}
var key = util.inspect(item.key).split('\n').join('\n ')
var val = { value: item.value }
if (item.maxAge !== maxAge) {
val.maxAge = item.maxAge
}
if (lc !== naiveLength) {
val.length = item.length
}
if (isStale(this, item)) {
val.stale = true
}
val = util.inspect(val, opts).split('\n').join('\n ')
str += key + ' => ' + val
})
if (didFirst || extras) {
str += '\n'
}
str += '}'
return str
2016-10-10 03:43:44 +02:00
}
LRUCache.prototype.set = function (key, value, maxAge) {
2017-08-14 05:01:11 +02:00
maxAge = maxAge || this[MAX_AGE]
2016-10-10 03:43:44 +02:00
var now = maxAge ? Date.now() : 0
2017-08-14 05:01:11 +02:00
var len = this[LENGTH_CALCULATOR](value, key)
2016-10-10 03:43:44 +02:00
2017-08-14 05:01:11 +02:00
if (this[CACHE].has(key)) {
if (len > this[MAX]) {
del(this, this[CACHE].get(key))
2016-10-10 03:43:44 +02:00
return false
}
2017-08-14 05:01:11 +02:00
var node = this[CACHE].get(key)
2017-05-27 17:36:13 +02:00
var item = node.value
// dispose of the old one before overwriting
2017-08-14 05:01:11 +02:00
// split out into 2 ifs for better coverage tracking
if (this[DISPOSE]) {
if (!this[NO_DISPOSE_ON_SET]) {
this[DISPOSE](key, item.value)
}
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
2017-05-27 17:36:13 +02:00
item.now = now
item.maxAge = maxAge
item.value = value
2017-08-14 05:01:11 +02:00
this[LENGTH] += len - item.length
2017-05-27 17:36:13 +02:00
item.length = len
this.get(key)
trim(this)
2016-10-10 03:43:44 +02:00
return true
}
2017-05-27 17:36:13 +02:00
var hit = new Entry(key, value, len, now, maxAge)
2016-10-10 03:43:44 +02:00
// oversized objects fall out of cache automatically.
2017-08-14 05:01:11 +02:00
if (hit.length > this[MAX]) {
if (this[DISPOSE]) {
this[DISPOSE](key, value)
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
return false
}
2017-08-14 05:01:11 +02:00
this[LENGTH] += hit.length
this[LRU_LIST].unshift(hit)
this[CACHE].set(key, this[LRU_LIST].head)
2017-05-27 17:36:13 +02:00
trim(this)
2016-10-10 03:43:44 +02:00
return true
}
LRUCache.prototype.has = function (key) {
2017-08-14 05:01:11 +02:00
if (!this[CACHE].has(key)) return false
var hit = this[CACHE].get(key).value
2016-10-10 03:43:44 +02:00
if (isStale(this, hit)) {
return false
}
return true
}
LRUCache.prototype.get = function (key) {
return get(this, key, true)
}
LRUCache.prototype.peek = function (key) {
return get(this, key, false)
}
LRUCache.prototype.pop = function () {
2017-08-14 05:01:11 +02:00
var node = this[LRU_LIST].tail
2017-05-27 17:36:13 +02:00
if (!node) return null
del(this, node)
return node.value
2016-10-10 03:43:44 +02:00
}
LRUCache.prototype.del = function (key) {
2017-08-14 05:01:11 +02:00
del(this, this[CACHE].get(key))
2016-10-10 03:43:44 +02:00
}
LRUCache.prototype.load = function (arr) {
2017-05-27 17:36:13 +02:00
// reset the cache
this.reset()
2016-10-10 03:43:44 +02:00
var now = Date.now()
2017-05-27 17:36:13 +02:00
// A previous serialized cache has the most recent items first
for (var l = arr.length - 1; l >= 0; l--) {
2016-10-10 03:43:44 +02:00
var hit = arr[l]
var expiresAt = hit.e || 0
if (expiresAt === 0) {
2017-05-27 17:36:13 +02:00
// the item was created without expiration in a non aged cache
2016-10-10 03:43:44 +02:00
this.set(hit.k, hit.v)
} else {
var maxAge = expiresAt - now
2017-05-27 17:36:13 +02:00
// dont add already expired items
if (maxAge > 0) {
this.set(hit.k, hit.v, maxAge)
}
2016-10-10 03:43:44 +02:00
}
}
}
2017-05-27 17:36:13 +02:00
LRUCache.prototype.prune = function () {
var self = this
2017-08-14 05:01:11 +02:00
this[CACHE].forEach(function (value, key) {
2017-05-27 17:36:13 +02:00
get(self, key, false)
})
}
2016-10-10 03:43:44 +02:00
function get (self, key, doUse) {
2017-08-14 05:01:11 +02:00
var node = self[CACHE].get(key)
2017-05-27 17:36:13 +02:00
if (node) {
var hit = node.value
2016-10-10 03:43:44 +02:00
if (isStale(self, hit)) {
2017-05-27 17:36:13 +02:00
del(self, node)
2017-08-14 05:01:11 +02:00
if (!self[ALLOW_STALE]) hit = undefined
2016-10-10 03:43:44 +02:00
} else {
2017-05-27 17:36:13 +02:00
if (doUse) {
2017-08-14 05:01:11 +02:00
self[LRU_LIST].unshiftNode(node)
2017-05-27 17:36:13 +02:00
}
2016-10-10 03:43:44 +02:00
}
if (hit) hit = hit.value
}
return hit
}
2017-05-27 17:36:13 +02:00
function isStale (self, hit) {
2017-08-14 05:01:11 +02:00
if (!hit || (!hit.maxAge && !self[MAX_AGE])) {
2017-05-27 17:36:13 +02:00
return false
}
var stale = false
2016-10-10 03:43:44 +02:00
var diff = Date.now() - hit.now
if (hit.maxAge) {
stale = diff > hit.maxAge
} else {
2017-08-14 05:01:11 +02:00
stale = self[MAX_AGE] && (diff > self[MAX_AGE])
2016-10-10 03:43:44 +02:00
}
2017-05-27 17:36:13 +02:00
return stale
2016-10-10 03:43:44 +02:00
}
function trim (self) {
2017-08-14 05:01:11 +02:00
if (self[LENGTH] > self[MAX]) {
for (var walker = self[LRU_LIST].tail;
self[LENGTH] > self[MAX] && walker !== null;) {
2017-05-27 17:36:13 +02:00
// We know that we're about to delete this one, and also
// what the next least recently used key will be, so just
// go ahead and set it now.
var prev = walker.prev
del(self, walker)
walker = prev
}
}
2016-10-10 03:43:44 +02:00
}
2017-05-27 17:36:13 +02:00
function del (self, node) {
if (node) {
var hit = node.value
2017-08-14 05:01:11 +02:00
if (self[DISPOSE]) {
self[DISPOSE](hit.key, hit.value)
2017-05-27 17:36:13 +02:00
}
2017-08-14 05:01:11 +02:00
self[LENGTH] -= hit.length
self[CACHE].delete(hit.key)
self[LRU_LIST].removeNode(node)
2016-10-10 03:43:44 +02:00
}
}
// classy, since V8 prefers predictable objects.
2017-05-27 17:36:13 +02:00
function Entry (key, value, length, now, maxAge) {
2016-10-10 03:43:44 +02:00
this.key = key
this.value = value
this.length = length
this.now = now
2017-05-27 17:36:13 +02:00
this.maxAge = maxAge || 0
2016-10-10 03:43:44 +02:00
}