@linkedmink/multilevel-aging-cache
Version:
Package provides an interface to cache and persist data to Redis, MongoDB, memory
142 lines (141 loc) • 5.24 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgingCache = void 0;
const IAgingCache_1 = require("./IAgingCache");
const Logger_1 = require("../shared/Logger");
/**
* A cache that will replace entries in the order specified by the input IAgedQueue
*/
class AgingCache {
/**
* @param hierarchy The storage hierarchy to operate on
* @param evictQueue The keys in the order to evict
* @param setStrategy The implementation for setting keys
* @param deleteStrategy The implementation for deleting keys
* @param purgeInterval The interval to check for old entries in seconds
*/
constructor(hierarchy, evictQueue, setStrategy, deleteStrategy, evictAtLevel, purgeInterval = 30) {
this.hierarchy = hierarchy;
this.evictQueue = evictQueue;
this.setStrategy = setStrategy;
this.deleteStrategy = deleteStrategy;
this.evictAtLevel = evictAtLevel;
this.logger = Logger_1.Logger.get(AgingCache.name);
/**
* Purge the cache of stale entries instead of waiting for a periodic check
* @return A promise to track when the purge finishes
*/
this.purge = () => {
if (!this.purgePromise) {
this.logger.debug(`Starting Purge: ${Date.now()}`);
this.purgePromise = this.purgeNext().then(() => (this.purgePromise = undefined));
}
return this.purgePromise;
};
this.purgeInterval = purgeInterval * 1000;
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.purgeTimer = setInterval(this.purge, this.purgeInterval);
}
/**
* Clean up the object when it's no longer used. After a dispose(), an object
* is no longer guaranteed to be usable.
*/
dispose() {
this.logger.info(`Cleaning up cache`);
if (this.purgeTimer) {
clearInterval(this.purgeTimer);
this.purgeTimer = undefined;
}
return this.purgePromise;
}
/**
* @param key The key to retrieve
* @returns The value if it's in the cache or undefined
*/
get(key, force = false) {
this.logger.debug(`Getting Key: ${key}`);
return this.hierarchy.getAtLevel(key, undefined, !force).then(agedValue => {
if (agedValue) {
return agedValue.value;
}
return null;
});
}
/**
* @param key The key to set
* @param value The value to set
* @returns If setting the value was successful
*/
set(key, value, force = false) {
this.logger.debug(`Setting Key: ${key}`);
if (this.evictQueue.isNextExpired()) {
void this.evict();
}
return this.setStrategy.set(key, value, force);
}
/**
* @param key The key to the value to delete
* @returns If deleting the value was successful
*/
delete(key, force = false) {
this.logger.debug(`Deleting Key: ${key}`);
return this.deleteStrategy.delete(key, force);
}
/**
* @returns The keys that are currently in the cache
*/
keys() {
this.logger.debug('Getting Key List');
return this.hierarchy.getKeysAtTopLevel();
}
/**
* @param key The key to the value to clear from cache layers
* @param force If true write to levels below the persistence layer
* @returns If the write succeeded or the error condition
*/
clear(key, force) {
return this.deleteStrategy.evict(key, this.evictAtLevel, force);
}
/**
* @returns The next value that's set to expire or null when nothing will expire
*/
peek() {
const nextKey = this.evictQueue.next();
if (nextKey) {
return this.hierarchy.getValueAtBottomLevel(nextKey).then(v => (v ? v.value : null));
}
return Promise.resolve(null);
}
load(keyValues) {
const promises = keyValues.map(kv => {
return this.setStrategy.load(kv.key, kv.val, this.evictAtLevel);
});
return Promise.all(promises).then(all => {
return all.reduce((acc, next) => {
return next.status === IAgingCache_1.AgingCacheWriteStatus.Success ||
next.status === IAgingCache_1.AgingCacheWriteStatus.PartialWrite
? acc++
: acc;
}, 0);
});
}
purgeNext() {
if (this.evictQueue.isNextExpired()) {
return this.evict().then(write => {
if (write?.status === IAgingCache_1.AgingCacheWriteStatus.Success) {
return this.purgeNext();
}
});
}
return Promise.resolve();
}
evict() {
const nextKey = this.evictQueue.next();
if (nextKey) {
this.logger.debug(`Evicting Key: ${nextKey}`);
return this.deleteStrategy.evict(nextKey, this.evictAtLevel);
}
return Promise.resolve(null);
}
}
exports.AgingCache = AgingCache;