'use strict'
-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' && process.env._nodeLRUCacheForceNoSymbol !== '1'
-var makeSymbol
-if (hasSymbol) {
- makeSymbol = function (key) {
- return Symbol(key)
- }
-} else {
- makeSymbol = function (key) {
- return '_' + key
- }
-}
+const Yallist = require('yallist')
-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')
+const MAX = Symbol('max')
+const LENGTH = Symbol('length')
+const LENGTH_CALCULATOR = Symbol('lengthCalculator')
+const ALLOW_STALE = Symbol('allowStale')
+const MAX_AGE = Symbol('maxAge')
+const DISPOSE = Symbol('dispose')
+const NO_DISPOSE_ON_SET = Symbol('noDisposeOnSet')
+const LRU_LIST = Symbol('lruList')
+const CACHE = Symbol('cache')
+const UPDATE_AGE_ON_GET = Symbol('updateAgeOnGet')
-function naiveLength () { return 1 }
+const naiveLength = () => 1
// lruList is a yallist where the head is the youngest
// item, and the tail is the oldest. the list contains the Hit
//
// cache is a Map (or PseudoMap) that matches the keys to
// the Yallist.Node object.
-function LRUCache (options) {
- if (!(this instanceof LRUCache)) {
- return new LRUCache(options)
+class LRUCache {
+ constructor (options) {
+ if (typeof options === 'number')
+ options = { max: options }
+
+ if (!options)
+ options = {}
+
+ if (options.max && (typeof options.max !== 'number' || options.max < 0))
+ throw new TypeError('max must be a non-negative number')
+ // Kind of weird to have a default max of Infinity, but oh well.
+ const max = this[MAX] = options.max || Infinity
+
+ const lc = options.length || naiveLength
+ this[LENGTH_CALCULATOR] = (typeof lc !== 'function') ? naiveLength : lc
+ this[ALLOW_STALE] = options.stale || false
+ if (options.maxAge && typeof options.maxAge !== 'number')
+ throw new TypeError('maxAge must be a number')
+ this[MAX_AGE] = options.maxAge || 0
+ this[DISPOSE] = options.dispose
+ this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false
+ this[UPDATE_AGE_ON_GET] = options.updateAgeOnGet || false
+ this.reset()
}
- if (typeof options === 'number') {
- options = { max: options }
- }
+ // resize the cache when the max changes.
+ set max (mL) {
+ if (typeof mL !== 'number' || mL < 0)
+ throw new TypeError('max must be a non-negative number')
- if (!options) {
- options = {}
+ this[MAX] = mL || Infinity
+ trim(this)
}
-
- var max = this[MAX] = options.max
- // Kind of weird to have a default max of Infinity, but oh well.
- if (!max ||
- !(typeof max === 'number') ||
- max <= 0) {
- this[MAX] = Infinity
+ get max () {
+ return this[MAX]
}
- var lc = options.length || naiveLength
- if (typeof lc !== 'function') {
- lc = naiveLength
+ set allowStale (allowStale) {
+ this[ALLOW_STALE] = !!allowStale
+ }
+ get allowStale () {
+ return this[ALLOW_STALE]
}
- this[LENGTH_CALCULATOR] = lc
-
- this[ALLOW_STALE] = options.stale || false
- this[MAX_AGE] = options.maxAge || 0
- this[DISPOSE] = options.dispose
- this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false
- this.reset()
-}
-// resize the cache when the max changes.
-Object.defineProperty(LRUCache.prototype, 'max', {
- set: function (mL) {
- if (!mL || !(typeof mL === 'number') || mL <= 0) {
- mL = Infinity
- }
- this[MAX] = mL
- trim(this)
- },
- get: function () {
- return this[MAX]
- },
- enumerable: true
-})
+ set maxAge (mA) {
+ if (typeof mA !== 'number')
+ throw new TypeError('maxAge must be a non-negative number')
-Object.defineProperty(LRUCache.prototype, 'allowStale', {
- set: function (allowStale) {
- this[ALLOW_STALE] = !!allowStale
- },
- get: function () {
- return this[ALLOW_STALE]
- },
- enumerable: true
-})
-
-Object.defineProperty(LRUCache.prototype, 'maxAge', {
- set: function (mA) {
- if (!mA || !(typeof mA === 'number') || mA < 0) {
- mA = 0
- }
this[MAX_AGE] = mA
trim(this)
- },
- get: function () {
+ }
+ get maxAge () {
return this[MAX_AGE]
- },
- enumerable: true
-})
-
-// resize the cache when the lengthCalculator changes.
-Object.defineProperty(LRUCache.prototype, 'lengthCalculator', {
- set: function (lC) {
- if (typeof lC !== 'function') {
+ }
+
+ // resize the cache when the lengthCalculator changes.
+ set lengthCalculator (lC) {
+ if (typeof lC !== 'function')
lC = naiveLength
- }
+
if (lC !== this[LENGTH_CALCULATOR]) {
this[LENGTH_CALCULATOR] = lC
this[LENGTH] = 0
- this[LRU_LIST].forEach(function (hit) {
+ this[LRU_LIST].forEach(hit => {
hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key)
this[LENGTH] += hit.length
- }, this)
+ })
}
trim(this)
- },
- get: function () { return this[LENGTH_CALCULATOR] },
- enumerable: true
-})
-
-Object.defineProperty(LRUCache.prototype, 'length', {
- get: function () { return this[LENGTH] },
- enumerable: true
-})
-
-Object.defineProperty(LRUCache.prototype, 'itemCount', {
- get: function () { return this[LRU_LIST].length },
- enumerable: true
-})
-
-LRUCache.prototype.rforEach = function (fn, thisp) {
- thisp = thisp || this
- for (var walker = this[LRU_LIST].tail; walker !== null;) {
- var prev = walker.prev
- forEachStep(this, fn, walker, thisp)
- walker = prev
}
-}
+ get lengthCalculator () { return this[LENGTH_CALCULATOR] }
-function forEachStep (self, fn, node, thisp) {
- var hit = node.value
- if (isStale(self, hit)) {
- del(self, node)
- if (!self[ALLOW_STALE]) {
- hit = undefined
+ get length () { return this[LENGTH] }
+ get itemCount () { return this[LRU_LIST].length }
+
+ rforEach (fn, thisp) {
+ thisp = thisp || this
+ for (let walker = this[LRU_LIST].tail; walker !== null;) {
+ const prev = walker.prev
+ forEachStep(this, fn, walker, thisp)
+ walker = prev
}
}
- if (hit) {
- fn.call(thisp, hit.value, hit.key, self)
+
+ forEach (fn, thisp) {
+ thisp = thisp || this
+ for (let walker = this[LRU_LIST].head; walker !== null;) {
+ const next = walker.next
+ forEachStep(this, fn, walker, thisp)
+ walker = next
+ }
}
-}
-LRUCache.prototype.forEach = function (fn, thisp) {
- thisp = thisp || this
- for (var walker = this[LRU_LIST].head; walker !== null;) {
- var next = walker.next
- forEachStep(this, fn, walker, thisp)
- walker = next
+ keys () {
+ return this[LRU_LIST].toArray().map(k => k.key)
}
-}
-LRUCache.prototype.keys = function () {
- return this[LRU_LIST].toArray().map(function (k) {
- return k.key
- }, this)
-}
+ values () {
+ return this[LRU_LIST].toArray().map(k => k.value)
+ }
-LRUCache.prototype.values = function () {
- return this[LRU_LIST].toArray().map(function (k) {
- return k.value
- }, this)
-}
+ reset () {
+ if (this[DISPOSE] &&
+ this[LRU_LIST] &&
+ this[LRU_LIST].length) {
+ this[LRU_LIST].forEach(hit => this[DISPOSE](hit.key, hit.value))
+ }
-LRUCache.prototype.reset = function () {
- if (this[DISPOSE] &&
- this[LRU_LIST] &&
- this[LRU_LIST].length) {
- this[LRU_LIST].forEach(function (hit) {
- this[DISPOSE](hit.key, hit.value)
- }, this)
+ 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
}
- 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
-}
-
-LRUCache.prototype.dump = function () {
- return this[LRU_LIST].map(function (hit) {
- if (!isStale(this, hit)) {
- return {
+ dump () {
+ return this[LRU_LIST].map(hit =>
+ isStale(this, hit) ? false : {
k: hit.key,
v: hit.value,
e: hit.now + (hit.maxAge || 0)
- }
- }
- }, this).toArray().filter(function (h) {
- return h
- })
-}
-
-LRUCache.prototype.dumpLru = function () {
- return this[LRU_LIST]
-}
-
-/* istanbul ignore next */
-LRUCache.prototype.inspect = function (n, opts) {
- var str = 'LRUCache {'
- var extras = false
-
- var as = this[ALLOW_STALE]
- if (as) {
- str += '\n allowStale: true'
- extras = true
+ }).toArray().filter(h => h)
}
- var max = this[MAX]
- if (max && max !== Infinity) {
- if (extras) {
- str += ','
- }
- str += '\n max: ' + util.inspect(max, opts)
- extras = true
+ dumpLru () {
+ return this[LRU_LIST]
}
- var maxAge = this[MAX_AGE]
- if (maxAge) {
- if (extras) {
- str += ','
- }
- str += '\n maxAge: ' + util.inspect(maxAge, opts)
- extras = true
- }
+ set (key, value, maxAge) {
+ maxAge = maxAge || this[MAX_AGE]
- var lc = this[LENGTH_CALCULATOR]
- if (lc && lc !== naiveLength) {
- if (extras) {
- str += ','
- }
- str += '\n length: ' + util.inspect(this[LENGTH], opts)
- extras = true
- }
+ if (maxAge && typeof maxAge !== 'number')
+ throw new TypeError('maxAge must be a number')
- var didFirst = false
- this[LRU_LIST].forEach(function (item) {
- if (didFirst) {
- str += ',\n '
- } else {
- if (extras) {
- str += ',\n'
+ const now = maxAge ? Date.now() : 0
+ const len = this[LENGTH_CALCULATOR](value, key)
+
+ if (this[CACHE].has(key)) {
+ if (len > this[MAX]) {
+ del(this, this[CACHE].get(key))
+ return false
}
- 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
- })
+ const node = this[CACHE].get(key)
+ const item = node.value
- if (didFirst || extras) {
- str += '\n'
- }
- str += '}'
+ // dispose of the old one before overwriting
+ // split out into 2 ifs for better coverage tracking
+ if (this[DISPOSE]) {
+ if (!this[NO_DISPOSE_ON_SET])
+ this[DISPOSE](key, item.value)
+ }
- return str
-}
+ item.now = now
+ item.maxAge = maxAge
+ item.value = value
+ this[LENGTH] += len - item.length
+ item.length = len
+ this.get(key)
+ trim(this)
+ return true
+ }
-LRUCache.prototype.set = function (key, value, maxAge) {
- maxAge = maxAge || this[MAX_AGE]
+ const hit = new Entry(key, value, len, now, maxAge)
- var now = maxAge ? Date.now() : 0
- var len = this[LENGTH_CALCULATOR](value, key)
+ // oversized objects fall out of cache automatically.
+ if (hit.length > this[MAX]) {
+ if (this[DISPOSE])
+ this[DISPOSE](key, value)
- if (this[CACHE].has(key)) {
- if (len > this[MAX]) {
- del(this, this[CACHE].get(key))
return false
}
- var node = this[CACHE].get(key)
- var item = node.value
-
- // dispose of the old one before overwriting
- // split out into 2 ifs for better coverage tracking
- if (this[DISPOSE]) {
- if (!this[NO_DISPOSE_ON_SET]) {
- this[DISPOSE](key, item.value)
- }
- }
-
- item.now = now
- item.maxAge = maxAge
- item.value = value
- this[LENGTH] += len - item.length
- item.length = len
- this.get(key)
+ this[LENGTH] += hit.length
+ this[LRU_LIST].unshift(hit)
+ this[CACHE].set(key, this[LRU_LIST].head)
trim(this)
return true
}
- var hit = new Entry(key, value, len, now, maxAge)
-
- // oversized objects fall out of cache automatically.
- if (hit.length > this[MAX]) {
- if (this[DISPOSE]) {
- this[DISPOSE](key, value)
- }
- return false
+ has (key) {
+ if (!this[CACHE].has(key)) return false
+ const hit = this[CACHE].get(key).value
+ return !isStale(this, hit)
}
- this[LENGTH] += hit.length
- this[LRU_LIST].unshift(hit)
- this[CACHE].set(key, this[LRU_LIST].head)
- trim(this)
- return true
-}
-
-LRUCache.prototype.has = function (key) {
- if (!this[CACHE].has(key)) return false
- var hit = this[CACHE].get(key).value
- if (isStale(this, hit)) {
- return false
+ get (key) {
+ return get(this, key, true)
}
- return true
-}
-LRUCache.prototype.get = function (key) {
- return get(this, key, true)
-}
+ peek (key) {
+ return get(this, key, false)
+ }
-LRUCache.prototype.peek = function (key) {
- return get(this, key, false)
-}
+ pop () {
+ const node = this[LRU_LIST].tail
+ if (!node)
+ return null
-LRUCache.prototype.pop = function () {
- var node = this[LRU_LIST].tail
- if (!node) return null
- del(this, node)
- return node.value
-}
+ del(this, node)
+ return node.value
+ }
-LRUCache.prototype.del = function (key) {
- del(this, this[CACHE].get(key))
-}
+ del (key) {
+ del(this, this[CACHE].get(key))
+ }
-LRUCache.prototype.load = function (arr) {
- // reset the cache
- this.reset()
-
- var now = Date.now()
- // A previous serialized cache has the most recent items first
- for (var l = arr.length - 1; l >= 0; l--) {
- var hit = arr[l]
- var expiresAt = hit.e || 0
- if (expiresAt === 0) {
- // the item was created without expiration in a non aged cache
- this.set(hit.k, hit.v)
- } else {
- var maxAge = expiresAt - now
- // dont add already expired items
- if (maxAge > 0) {
- this.set(hit.k, hit.v, maxAge)
+ load (arr) {
+ // reset the cache
+ this.reset()
+
+ const now = Date.now()
+ // A previous serialized cache has the most recent items first
+ for (let l = arr.length - 1; l >= 0; l--) {
+ const hit = arr[l]
+ const expiresAt = hit.e || 0
+ if (expiresAt === 0)
+ // the item was created without expiration in a non aged cache
+ this.set(hit.k, hit.v)
+ else {
+ const maxAge = expiresAt - now
+ // dont add already expired items
+ if (maxAge > 0) {
+ this.set(hit.k, hit.v, maxAge)
+ }
}
}
}
-}
-LRUCache.prototype.prune = function () {
- var self = this
- this[CACHE].forEach(function (value, key) {
- get(self, key, false)
- })
+ prune () {
+ this[CACHE].forEach((value, key) => get(this, key, false))
+ }
}
-function get (self, key, doUse) {
- var node = self[CACHE].get(key)
+const get = (self, key, doUse) => {
+ const node = self[CACHE].get(key)
if (node) {
- var hit = node.value
+ const hit = node.value
if (isStale(self, hit)) {
del(self, node)
- if (!self[ALLOW_STALE]) hit = undefined
+ if (!self[ALLOW_STALE])
+ return undefined
} else {
if (doUse) {
+ if (self[UPDATE_AGE_ON_GET])
+ node.value.now = Date.now()
self[LRU_LIST].unshiftNode(node)
}
}
- if (hit) hit = hit.value
+ return hit.value
}
- return hit
}
-function isStale (self, hit) {
- if (!hit || (!hit.maxAge && !self[MAX_AGE])) {
+const isStale = (self, hit) => {
+ if (!hit || (!hit.maxAge && !self[MAX_AGE]))
return false
- }
- var stale = false
- var diff = Date.now() - hit.now
- if (hit.maxAge) {
- stale = diff > hit.maxAge
- } else {
- stale = self[MAX_AGE] && (diff > self[MAX_AGE])
- }
- return stale
+
+ const diff = Date.now() - hit.now
+ return hit.maxAge ? diff > hit.maxAge
+ : self[MAX_AGE] && (diff > self[MAX_AGE])
}
-function trim (self) {
+const trim = self => {
if (self[LENGTH] > self[MAX]) {
- for (var walker = self[LRU_LIST].tail;
+ for (let walker = self[LRU_LIST].tail;
self[LENGTH] > self[MAX] && walker !== null;) {
// 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
+ const prev = walker.prev
del(self, walker)
walker = prev
}
}
}
-function del (self, node) {
+const del = (self, node) => {
if (node) {
- var hit = node.value
- if (self[DISPOSE]) {
+ const hit = node.value
+ if (self[DISPOSE])
self[DISPOSE](hit.key, hit.value)
- }
+
self[LENGTH] -= hit.length
self[CACHE].delete(hit.key)
self[LRU_LIST].removeNode(node)
}
}
-// classy, since V8 prefers predictable objects.
-function Entry (key, value, length, now, maxAge) {
- this.key = key
- this.value = value
- this.length = length
- this.now = now
- this.maxAge = maxAge || 0
+class Entry {
+ constructor (key, value, length, now, maxAge) {
+ this.key = key
+ this.value = value
+ this.length = length
+ this.now = now
+ this.maxAge = maxAge || 0
+ }
+}
+
+const forEachStep = (self, fn, node, thisp) => {
+ let hit = node.value
+ if (isStale(self, hit)) {
+ del(self, node)
+ if (!self[ALLOW_STALE])
+ hit = undefined
+ }
+ if (hit)
+ fn.call(thisp, hit.value, hit.key, self)
}
+
+module.exports = LRUCache