UNPKG

@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
"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;