UNPKG

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