/**
* Dekache: Written by dekitarpg@gmail.com
*/
// @ignore
const Emitter = require('events').EventEmitter;
const DekacheItem = require('./dekache-item');
/**
* ```
* const mycache = new Dekache({name:'you got any cache?', mins: 2})
* await mycache.get('some-identifier', async() => { return 1 });
* ```
* `mycache.get('some-identifier', ()=>{})` calls will now return a
* promise that resolves to 1 until the number of mins (2) has been reached.
*
*
*
* @class
*/
class Dekache extends Emitter {
/**
* An object containing key value pairs where the key is a string identifier,
* and the value is an object with the properties detailed below:
* @typedef Dekache~options
* @property {string} [name=''] - An identifyer for this cache.
* @property {string} [type=force] - the cache type, either 'force' or 'renew'.
* @property {number} [mins=1] - Number of minutes to cache each item for
* @property {number} [freq=1000] - The frequency to check cache items for deletion (ms)
*/
static DEFAULT_OPTS = {
name: 'unnamed-cache', // the cache name for easy identifications
type: 'force', // should renew data, or 'force' refresh after mins?
mins: 1, // duration before data is removed from the cache
freq: 1000, // frequency at which the cache items are checked for removal.
}
/**
* See {@link Dekache#initialize}
*/
constructor() {
super(); // become event emitter
this.initialize(...arguments);
}
/**
* @property {object} data - stores key value pairs for cache items
*/
get data() {
return this._data;
}
/**
* Called automatically when created
* @param {Dekache~options} options - a cache options object.
*/
initialize(options = {}) {
// setup general config options
const cache_options = { ...Dekache.DEFAULT_OPTS, ...options };
this._name = cache_options.name;
this._type = cache_options.type;
this._mins = cache_options.mins;
this._freq = cache_options.freq;
this._hand = null;
this._data = {};
if (!cache_options.no_start) this.start();
}
/**
* Starts the cache loop. Can be later stopped called {@link Dekache#stop}
* @returns {boolean} Based on if started. False if already started.
* @async
*/
async start() {
if (!this._hand) {
this._hand = setInterval(()=>{
this.loop();
}, this._freq);
}
return !(!this._hand);
}
/**
* Stops the cache loop. Can later be restarted calling {@link Dekache#start}
* @returns {boolean} Based on if stopped. False if already stopped.
* @async
*/
async stop() {
if (this._hand) {
clearInterval(this._hand);
this._hand = null;
return true;
}
return false;
}
/**
* The main cache loop function. Calls {@link Dekache#clear} .
* @async
*/
async loop() {
await this.clear();
}
/**
* Delete the given key from the cache
* @param {object} data_key - the identifier for the cache item to delte.
* @returns {boolean} Based on if any item was deleted from cache. False if not.
*/
async delete(data_key) {
const cache_name = `${this._type}_${JSON.stringify(data_key)}`;
if (this._data[cache_name]) {
this._data[cache_name] = null;
delete this._data[cache_name];
return true;
}
return false;
}
/**
* Iterates over each cache item and clears any that have overstayed the duration.
* @param {boolean} [forced=false] - Should all cache items be forced out regardless of duration?
*/
async clear(forced = false) {
const cache2clear = [];
const cache_k = Object.keys(this._data);
const cache_v = Object.values(this._data);
for (let index = 0; index < cache_v.length; index++) {
const item = cache_v[index];
if (item.data) {
if (forced || item.checkTimeDiff(this._mins)) {
cache2clear.push(cache_k[index]);
}
} else {
cache2clear.push(cache_k[index]);
}
}
if (!cache2clear.length) return;
cache2clear.forEach((key) => {
this.emit('clear-item', this._data[key], key);
this._data[key] = null;
delete this._data[key];
});
this.emit('clear', this._data, {
delete_count: cache2clear.length,
cache_count: cache_v.length,
});
}
/**
* Get the data, or uses callback function to populate cache and then returns data.
* @param {object} data_key - the identifier for the cache item to get.
* @param {function} callback - an asyncronous callback that should be called if no data is currently in the cache.
* @returns {promise} Based on the data returned from the first time that
* this function was called and the callbacks return data.
* @promise
*/
get(data_id, new_data_callback) {
const cache_name = this.key(data_id);
return new Promise(async (resolve, reject) => {
if (this._data[cache_name]) {
if (this._type === 'renew') {
this._data[cache_name].renew();
}
} else if (new_data_callback) {
const new_data = await new_data_callback();
this._data[cache_name] = new DekacheItem(new_data);
}
resolve(this._data[cache_name].data);
});
}
/**
* Set the cache data to new data directly and then returns promise.
* @param {object} data_key - the identifier for the cache item to get.
* @param {object} new_data - some object or primative.
* @returns {promise} that resolves with new_data
* @promise
*/
set(data_id, new_data) {
const cache_name = this.key(data_id);
return new Promise(async (resolve, reject) => {
this._data[cache_name] = new DekacheItem(new_data);
resolve(this._data[cache_name].data);
});
}
/**
* Gets the internal key from data_id. Can be used for comparing an id when a cache item is cleared.
* @param {object} data_key - the identifier for the cache item to get.
* @returns {string} the internal cache key used
* @promise
*/
key(data_id) {
return `${this._type}_${JSON.stringify(data_id)}`;
}
}
module.exports = Dekache;