You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
2.5 KiB
143 lines
2.5 KiB
'use strict'; |
|
|
|
const Assert = require('@hapi/hoek/lib/assert'); |
|
const Clone = require('@hapi/hoek/lib/clone'); |
|
|
|
const Common = require('./common'); |
|
|
|
|
|
const internals = { |
|
max: 1000, |
|
supported: new Set(['undefined', 'boolean', 'number', 'string']) |
|
}; |
|
|
|
|
|
exports.provider = { |
|
|
|
provision(options) { |
|
|
|
return new internals.Cache(options); |
|
} |
|
}; |
|
|
|
|
|
// Least Recently Used (LRU) Cache |
|
|
|
internals.Cache = class { |
|
|
|
constructor(options = {}) { |
|
|
|
Common.assertOptions(options, ['max']); |
|
Assert(options.max === undefined || options.max && options.max > 0 && isFinite(options.max), 'Invalid max cache size'); |
|
|
|
this._max = options.max || internals.max; |
|
|
|
this._map = new Map(); // Map of nodes by key |
|
this._list = new internals.List(); // List of nodes (most recently used in head) |
|
} |
|
|
|
get length() { |
|
|
|
return this._map.size; |
|
} |
|
|
|
set(key, value) { |
|
|
|
if (key !== null && |
|
!internals.supported.has(typeof key)) { |
|
|
|
return; |
|
} |
|
|
|
let node = this._map.get(key); |
|
if (node) { |
|
node.value = value; |
|
this._list.first(node); |
|
return; |
|
} |
|
|
|
node = this._list.unshift({ key, value }); |
|
this._map.set(key, node); |
|
this._compact(); |
|
} |
|
|
|
get(key) { |
|
|
|
const node = this._map.get(key); |
|
if (node) { |
|
this._list.first(node); |
|
return Clone(node.value); |
|
} |
|
} |
|
|
|
_compact() { |
|
|
|
if (this._map.size > this._max) { |
|
const node = this._list.pop(); |
|
this._map.delete(node.key); |
|
} |
|
} |
|
}; |
|
|
|
|
|
internals.List = class { |
|
|
|
constructor() { |
|
|
|
this.tail = null; |
|
this.head = null; |
|
} |
|
|
|
unshift(node) { |
|
|
|
node.next = null; |
|
node.prev = this.head; |
|
|
|
if (this.head) { |
|
this.head.next = node; |
|
} |
|
|
|
this.head = node; |
|
|
|
if (!this.tail) { |
|
this.tail = node; |
|
} |
|
|
|
return node; |
|
} |
|
|
|
first(node) { |
|
|
|
if (node === this.head) { |
|
return; |
|
} |
|
|
|
this._remove(node); |
|
this.unshift(node); |
|
} |
|
|
|
pop() { |
|
|
|
return this._remove(this.tail); |
|
} |
|
|
|
_remove(node) { |
|
|
|
const { next, prev } = node; |
|
|
|
next.prev = prev; |
|
|
|
if (prev) { |
|
prev.next = next; |
|
} |
|
|
|
if (node === this.tail) { |
|
this.tail = next; |
|
} |
|
|
|
node.prev = null; |
|
node.next = null; |
|
|
|
return node; |
|
} |
|
};
|
|
|