@linkedmink/multilevel-aging-cache
Version:
Package provides an interface to cache and persist data to Redis, MongoDB, memory
145 lines (144 loc) • 4.56 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FIFOAgedQueue = void 0;
const bintrees_1 = require("bintrees");
const IAgedQueue_1 = require("./IAgedQueue");
const Logger_1 = require("../shared/Logger");
class FIFOAgedQueue {
/**
* @param maxEntries The maximum number of entries to store in the cache, undefined for no max
* @param ageLimit The maximum time to keep entries in minutes
*/
constructor(maxEntries, ageLimit = 200) {
this.maxEntries = maxEntries;
this.logger = Logger_1.Logger.get(FIFOAgedQueue.name);
this.ageTree = new bintrees_1.RBTree(IAgedQueue_1.compareAscending);
this.ageMap = new Map();
this.ageBuckets = new Map();
/**
* @param ageA The first age to compare
* @param ageB The second age to compare
* @return 0 if same order, positive if ageA after ageB, negative if ageA before ageB
*/
this.compare = IAgedQueue_1.compareAscending;
this.ageLimit = ageLimit * 1000 * 60;
}
/**
* @param key The key to add
* @param age The age if explicitly or default if undefined
*/
addOrReplace(key, age) {
const existingAge = this.ageMap.get(key);
const newAge = age ? age : this.getInitialAge(key);
this.ageMap.set(key, newAge);
if (existingAge !== undefined) {
this.deleteBucket(existingAge, key);
}
this.addBucket(newAge, key);
}
/**
* @return The next key in order or null if there is no key
*/
next() {
const minAge = this.ageTree.min();
if (!minAge) {
return null;
}
const minBucket = this.ageBuckets.get(minAge);
if (minBucket === undefined) {
return null;
}
const iterator = minBucket.values().next();
return iterator.value;
}
/**
* @param key The key to delete
*/
delete(key) {
const age = this.ageMap.get(key);
if (age === undefined) {
return;
}
this.ageMap.delete(key);
this.deleteBucket(age, key);
}
/**
* @return True if the next key in order is expired and should be removed
*/
isNextExpired() {
if (!this.maxEntries && !this.ageLimit) {
return false;
}
if (this.maxEntries && this.ageMap.size > this.maxEntries) {
this.logger.debug(`Max Entries Exceeded: ${this.maxEntries}`);
return true;
}
const next = this.next();
if (next === null) {
return false;
}
if (this.ageLimit) {
const age = this.ageMap.get(next);
if (age !== undefined && age + this.ageLimit < Date.now()) {
this.logger.debug(`Age Limit Exceeded: age=${age},limit=${this.ageLimit}`);
return true;
}
}
return false;
}
/**
* @param key The key we want a default for
* @return The default age that will very by algorithm
*/
getInitialAge(_key) {
return Date.now();
}
/**
* @param key Advance the age of the specified key
*/
updateAge(key) {
const oldAge = this.ageMap.get(key);
if (oldAge === undefined) {
return;
}
const newAge = Date.now();
this.ageMap.set(key, newAge);
this.deleteBucket(oldAge, key);
this.addBucket(newAge, key);
}
/**
* @return The number of keys in the queue
*/
size() {
return this.ageMap.size;
}
addBucket(age, key) {
const bucket = this.ageBuckets.get(age);
if (bucket === undefined) {
this.ageBuckets.set(age, new Set([key]));
}
else {
bucket.add(key);
}
const found = this.ageTree.find(age);
if (found === null) {
this.ageTree.insert(age);
}
}
deleteBucket(age, key) {
const bucket = this.ageBuckets.get(age);
if (bucket === undefined) {
return;
}
bucket.delete(key);
if (bucket.size > 0) {
return;
}
this.ageBuckets.delete(age);
const found = this.ageTree.find(age);
if (found !== null) {
this.ageTree.remove(age);
}
}
}
exports.FIFOAgedQueue = FIFOAgedQueue;