2022-07-03 03:35:36 +00:00
|
|
|
const STRATEGIES = [
|
2022-07-03 04:08:00 +00:00
|
|
|
'LAST_GET_TIME',
|
|
|
|
'GETS_COUNT'
|
2022-07-03 03:35:36 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
class SimpleDataStore {
|
|
|
|
#store
|
2022-07-06 10:49:13 +00:00
|
|
|
#held
|
2022-07-03 03:35:36 +00:00
|
|
|
#limit
|
|
|
|
#strategy
|
|
|
|
|
|
|
|
constructor (options = {}) {
|
|
|
|
if (typeof options !== 'object') {
|
|
|
|
throw new TypeError('Missing options object.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Number.isFinite(options.limit) || options.limit <= 1) {
|
|
|
|
throw new TypeError('Limit must be a finite number that is at least 2.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!STRATEGIES.includes(options.strategy)) {
|
|
|
|
throw new TypeError(`Strategy must be one of these: ${STRATEGIES.map(s => `"${s}"`).join(', ')}.`)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.#store = new Map()
|
2022-07-06 10:49:13 +00:00
|
|
|
this.#held = new Set()
|
2022-07-03 03:35:36 +00:00
|
|
|
this.#limit = options.limit
|
|
|
|
this.#strategy = options.strategy
|
|
|
|
}
|
|
|
|
|
|
|
|
clear () {
|
2022-07-06 10:14:00 +00:00
|
|
|
this.#store.clear()
|
2022-07-06 10:49:13 +00:00
|
|
|
this.#held.clear()
|
2022-07-03 03:35:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
delete (key) {
|
2022-07-06 10:49:13 +00:00
|
|
|
// If key is in #held, assume is not in #store, thus return early
|
|
|
|
return this.#held.delete(key) || this.#store.delete(key)
|
2022-07-03 03:35:36 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 09:37:54 +00:00
|
|
|
deleteStalest () {
|
|
|
|
const stalest = this.getStalest()
|
|
|
|
if (stalest) {
|
2022-07-06 10:49:13 +00:00
|
|
|
return this.#store.delete(stalest)
|
2022-07-06 09:37:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-03 03:35:36 +00:00
|
|
|
get (key) {
|
2022-07-06 10:49:13 +00:00
|
|
|
// null should be used as an indicator for when key is held but not yet set with value
|
|
|
|
if (this.#held.has(key)) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2022-07-03 03:35:36 +00:00
|
|
|
const entry = this.#store.get(key)
|
2022-07-06 09:37:54 +00:00
|
|
|
if (!entry) return entry
|
2022-07-03 03:35:36 +00:00
|
|
|
|
|
|
|
switch (this.#strategy) {
|
|
|
|
case STRATEGIES[0]:
|
|
|
|
entry.stratval = Date.now()
|
|
|
|
break
|
|
|
|
case STRATEGIES[1]:
|
|
|
|
entry.stratval++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
this.#store.set(key, entry)
|
|
|
|
return entry.value
|
|
|
|
}
|
|
|
|
|
|
|
|
getStalest () {
|
2022-07-03 04:00:48 +00:00
|
|
|
let stalest = [null, { stratval: Infinity }]
|
2022-07-03 03:35:36 +00:00
|
|
|
switch (this.#strategy) {
|
|
|
|
// both "lastGetTime" and "getsCount" simply must find lowest value
|
|
|
|
// to determine the stalest entry
|
|
|
|
case STRATEGIES[0]:
|
|
|
|
case STRATEGIES[1]:
|
|
|
|
for (const entry of this.#store) {
|
2022-07-06 10:49:13 +00:00
|
|
|
if (entry[1].stratval < stalest[1].stratval) {
|
2022-07-03 03:35:36 +00:00
|
|
|
stalest = entry
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// return its key only
|
|
|
|
return stalest[0]
|
|
|
|
}
|
|
|
|
|
2022-07-06 09:37:54 +00:00
|
|
|
hold (key) {
|
2022-07-06 10:49:13 +00:00
|
|
|
return this.#held.add(key) && true
|
2022-07-06 09:37:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
set (key, value) {
|
2022-07-06 10:49:13 +00:00
|
|
|
if (!this.#store.has(key) && this.#store.size >= this.#limit) {
|
2022-07-06 09:37:54 +00:00
|
|
|
this.deleteStalest()
|
2022-07-03 03:35:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let stratval
|
|
|
|
switch (this.#strategy) {
|
|
|
|
case STRATEGIES[0]:
|
|
|
|
stratval = Date.now()
|
|
|
|
break
|
|
|
|
case STRATEGIES[1]:
|
|
|
|
stratval = 0
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2022-07-06 10:14:00 +00:00
|
|
|
if (this.#store.set(key, { value, stratval })) {
|
2022-07-06 10:49:13 +00:00
|
|
|
this.#held.delete(key)
|
2022-07-06 10:14:00 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
2022-07-03 03:35:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get limit () {
|
|
|
|
return this.#limit
|
|
|
|
}
|
|
|
|
|
|
|
|
set limit (_) {
|
|
|
|
throw Error('This property is read-only.')
|
|
|
|
}
|
|
|
|
|
|
|
|
get size () {
|
2022-07-06 10:49:13 +00:00
|
|
|
return this.#store.size
|
2022-07-03 03:35:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
set size (_) {
|
|
|
|
throw Error('This property is read-only.')
|
|
|
|
}
|
|
|
|
|
|
|
|
get strategy () {
|
|
|
|
return this.#strategy
|
|
|
|
}
|
|
|
|
|
|
|
|
set strategy (_) {
|
|
|
|
throw Error('This property is read-only.')
|
|
|
|
}
|
|
|
|
|
2022-07-06 10:49:13 +00:00
|
|
|
// Not advised to use the following functions during production
|
|
|
|
// Mainly intended to "inspect" internal stores when required
|
|
|
|
|
2022-07-03 03:35:36 +00:00
|
|
|
get store () {
|
|
|
|
// return shallow copy
|
|
|
|
return new Map(this.#store)
|
|
|
|
}
|
2022-07-06 10:49:13 +00:00
|
|
|
|
|
|
|
get held () {
|
|
|
|
// return shallow copy
|
|
|
|
return new Set(this.#held)
|
|
|
|
}
|
2022-07-03 03:35:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = SimpleDataStore
|
2022-07-03 04:08:00 +00:00
|
|
|
module.exports.STRATEGIES = STRATEGIES
|