@phamleduy04/cheshire
Version:
TLRU / LRU cache based on @discordjs/collections
138 lines (130 loc) • 4.64 kB
JavaScript
const { Collection } = require('@discordjs/collection');
const { Scheduler } = require('fun-dispatcher');
/**
* Extended map with array methods
* @external Collection
* @see {@link https://discord.js.org/#/docs/collection/main/class/Collection}
*/
/**
* Scheduler that runs functions after x amount of time
* @external Scheduler
* @see {@link https://github.com/tinovyatkin/fun-dispatcher}
*/
/**
* Cheshire's options
* @class Options
*/
class Options {
/**
* @param {Object} [options={}] Options for Cheshire to initialize with
*/
constructor(options = {}) {
/**
* The size limit for this cache. Defaults to "Infinity"
* @type {number}
*/
this.limit = options.limit || Infinity;
/**
* The lifetime for an entry in this cache in ms. Defaults to "12 * 60 * 60 * 1000"
* @type {number}
*/
this.lifetime = options.lifetime || 12 * 60 * 60 * 1000;
/**
* What Cheshire will execute on timeout, you can use this to customize your cache deletion behavior. First parameter is the key, second parameter is the value
* @type {Function|null}
*/
this.disposer = options.disposer || (() => true);
/**
* If true, the cache will operate in LRU mode, if false, the cache will operate in TLRU mode. Defaults to "false"
* @type {boolean}
*/
this.lru = options.lru || false;
if (isNaN(this.limit)) throw new Error('Option limit must be a number');
if (isNaN(this.lifetime)) throw new Error('Option lifetime must be a number');
if (this.disposer && typeof this.disposer !== 'function') throw new Error('Option disposer must be a function');
}
}
/**
* Cheshire, a TLRU / LRU cache extended from @discordjs/collection
* @class Cheshire
* @extends {Collection}
*/
class Cheshire extends Collection {
/**
* @param {Object} [options={}] Options for Cheshire to initialize with
*/
constructor(options = {}) {
super();
/**
* Options for Cheshire
* @type {Options}
* @private
*/
Object.defineProperty(this, 'options', { value: new Options(options) });
/**
* Scheduler for cache evictions
* @type {Scheduler}
* @private
*/
Object.defineProperty(this, 'scheduler', { value: new Scheduler() });
/**
* Cache runnable, with this as the class
* @type {Function}
* @private
*/
Object.defineProperty(this, 'runnable', {
value: (key, value, ttu) => {
const deletable = this.options.disposer(key, value);
if (deletable) return super.delete(key);
this.scheduler.schedule(key, () => this.runnable(key, value, ttu), ttu);
}
});
}
/**
* Sets a data in cache
* @param {*} key The key for this entry
* @param {*} value The data for this entry
* @param {number} [ttu] The time to use (TTU) for this entry in ms. Defaults to Options.lifetime
* @memberof Cheshire
* @returns {Cheshire}
*/
set(key, value, ttu = this.options.lifetime + this.size) {
if (this.size > this.options.limit) this.scheduler.runNext();
this.scheduler.schedule(key, () => this.runnable(key, value, ttu), ttu);
return super.set(key, value);
}
/**
* Sets a data in cache
* @param {*} key The key of the entry in cache
* @param {boolean} [revive] If you want to override this entry to use TLRU or LRU cache. Defaults to Options.lru
* @memberof Cheshire
* @returns {*|undefined}
*/
get(key, revive = this.options.lru) {
const data = super.get(key);
if (!data) return undefined;
if (!revive) return data;
const original = this.scheduler.get(key);
if (original) {
const ttu = original.delay + super.size;
this.scheduler.schedule(key, () => this.runnable(key, data, ttu), ttu);
}
return data;
}
delete(key) {
this.scheduler.delete(key);
return super.delete(key);
}
clear() {
this.scheduler.flush();
super.clear();
}
[Symbol('djsCleanup')]() {
return () => {
this.scheduler.timeouts.length = 0;
this.scheduler.clear();
this.scheduler.flush();
};
}
}
module.exports = { Cheshire, Options };