record-cache
Version:
Cache optimised for record like things
171 lines (137 loc) • 3.67 kB
JavaScript
const b4a = require('b4a')
var EMPTY = []
module.exports = RecordCache
function RecordSet () {
this.list = []
this.map = new Map()
}
RecordSet.prototype.add = function (record, value) {
var k = toString(record)
var r = this.map.get(k)
if (r) return false
r = {index: this.list.length, record: value || record}
this.list.push(r)
this.map.set(k, r)
return true
}
RecordSet.prototype.remove = function (record) {
var k = toString(record)
var r = this.map.get(k)
if (!r) return false
swap(this.list, r.index, this.list.length - 1)
this.list.pop()
this.map.delete(k)
return true
}
function RecordStore () {
this.records = new Map()
this.size = 0
}
RecordStore.prototype.add = function (name, record, value) {
var r = this.records.get(name)
if (!r) {
r = new RecordSet()
this.records.set(name, r)
}
if (r.add(record, value)) {
this.size++
return true
}
return false
}
RecordStore.prototype.remove = function (name, record, value) {
var r = this.records.get(name)
if (!r) return false
if (r.remove(record, value)) {
this.size--
if (!r.map.size) this.records.delete(name)
return true
}
return false
}
RecordStore.prototype.get = function (name) {
var r = this.records.get(name)
return r ? r.list : EMPTY
}
function RecordCache (opts) {
if (!(this instanceof RecordCache)) return new RecordCache(opts)
if (!opts) opts = {}
this.maxSize = opts.maxSize || Infinity
this.maxAge = opts.maxAge || 0
this._onstale = opts.onStale || opts.onstale || null
this._fresh = new RecordStore()
this._stale = new RecordStore()
this._interval = null
this._gced = false
if (this.maxAge && this.maxAge < Infinity) {
// 2/3 gives us a span of 0.66-1.33 maxAge or avg maxAge
var tick = Math.ceil(2 / 3 * this.maxAge)
this._interval = setInterval(this._gcAuto.bind(this), tick)
if (this._interval.unref) this._interval.unref()
}
}
Object.defineProperty(RecordCache.prototype, 'size', {
get: function () {
return this._fresh.size + this._stale.size
}
})
RecordCache.prototype.add = function (name, record, value) {
this._stale.remove(name, record, value)
if (this._fresh.add(name, record, value) && this._fresh.size > this.maxSize) {
this._gc()
}
}
RecordCache.prototype.remove = function (name, record, value) {
this._fresh.remove(name, record, value)
this._stale.remove(name, record, value)
}
RecordCache.prototype.get = function (name, n) {
var a = this._fresh.get(name)
var b = this._stale.get(name)
var aLen = a.length
var bLen = b.length
var len = aLen + bLen
if (n > len || !n) n = len
var result = new Array(n)
for (var i = 0; i < n; i++) {
var j = Math.floor(Math.random() * (aLen + bLen))
if (j < aLen) {
result[i] = a[j].record
swap(a, j, --aLen)
} else {
j -= aLen
result[i] = b[j].record
swap(b, j, --bLen)
}
}
return result
}
RecordCache.prototype._gcAuto = function () {
if (!this._gced) this._gc()
this._gced = false
}
RecordCache.prototype._gc = function () {
if (this._onstale && this._stale.size > 0) this._onstale(this._stale)
this._stale = this._fresh
this._fresh = new RecordStore()
this._gced = true
}
RecordCache.prototype.clear = function () {
this._gc()
this._gc()
}
RecordCache.prototype.destroy = function () {
this.clear()
clearInterval(this._interval)
this._interval = null
}
function toString (record) {
return b4a.isBuffer(record) ? b4a.toString(record, 'hex') : record
}
function swap (list, a, b) {
var tmp = list[a]
tmp.index = b
list[b].index = a
list[a] = list[b]
list[b] = tmp
}