/*
|
* node-cache 4.2.1 ( 2019-07-24 )
|
* https://github.com/mpneuried/nodecache
|
*
|
* Released under the MIT license
|
* https://github.com/mpneuried/nodecache/blob/master/LICENSE
|
*
|
* Maintained by ( )
|
*/
|
(function() {
|
// lodash requires
|
var EventEmitter, NodeCache, _assignIn, _isArray, _isFunction, _isNumber, _isObject, _isString, _size, _template, clone,
|
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } },
|
indexOf = [].indexOf;
|
|
_assignIn = require("lodash/assignIn");
|
|
_isArray = require("lodash/isArray");
|
|
_isString = require("lodash/isString");
|
|
_isFunction = require("lodash/isFunction");
|
|
_isNumber = require("lodash/isNumber");
|
|
_isObject = require("lodash/isObject");
|
|
_size = require("lodash/size");
|
|
_template = require("lodash/template");
|
|
clone = require("clone");
|
|
EventEmitter = require('events').EventEmitter;
|
|
// generate superclass
|
module.exports = NodeCache = (function() {
|
class NodeCache extends EventEmitter {
|
constructor(options = {}) {
|
super();
|
// ## get
|
|
// get a cached key and change the stats
|
|
// **Parameters:**
|
|
// * `key` ( String | Number ): cache key
|
// * `[cb]` ( Function ): Callback function
|
// * `[errorOnMissing=false]` ( Boolean ) return a error to the `cb` or throw it if no `cb` is used. Otherwise the get will return `undefined` on a miss.
|
|
// **Example:**
|
|
// myCache.get "myKey", ( err, val )->
|
// console.log( err, val )
|
// return
|
|
this.get = this.get.bind(this);
|
// ## mget
|
|
// get multiple cached keys at once and change the stats
|
|
// **Parameters:**
|
|
// * `keys` ( String|Number[] ): an array of keys
|
// * `[cb]` ( Function ): Callback function
|
|
// **Example:**
|
|
// myCache.mget [ "foo", "bar" ], ( err, val )->
|
// console.log( err, val )
|
// return
|
|
this.mget = this.mget.bind(this);
|
// ## set
|
|
// set a cached key and change the stats
|
|
// **Parameters:**
|
|
// * `key` ( String | Number ): cache key
|
// * `value` ( Any ): A element to cache. If the option `option.forceString` is `true` the module trys to translate it to a serialized JSON
|
// * `[ ttl ]` ( Number | String ): ( optional ) The time to live in seconds.
|
// * `[cb]` ( Function ): Callback function
|
|
// **Example:**
|
|
// myCache.set "myKey", "my_String Value", ( err, success )->
|
// console.log( err, success )
|
|
// myCache.set "myKey", "my_String Value", "10", ( err, success )->
|
// console.log( err, success )
|
|
this.set = this.set.bind(this);
|
// ## del
|
|
// remove keys
|
|
// **Parameters:**
|
|
// * `keys` ( String | Number | String|Number[] ): cache key to delete or a array of cache keys
|
// * `[cb]` ( Function ): Callback function
|
|
// **Return**
|
|
// ( Number ): Number of deleted keys
|
|
// **Example:**
|
|
// myCache.del( "myKey" )
|
|
// myCache.del( "myKey", ( err, delCount )->
|
// console.log( err, delCount )
|
// return
|
this.del = this.del.bind(this);
|
// ## ttl
|
|
// reset or redefine the ttl of a key. `ttl` = 0 means infinite lifetime.
|
// If `ttl` is not passed the default ttl is used.
|
// If `ttl` < 0 the key will be deleted.
|
|
// **Parameters:**
|
|
// * `key` ( String | Number ): cache key to reset the ttl value
|
// * `ttl` ( Number ): ( optional -> options.stdTTL || 0 ) The time to live in seconds
|
// * `[cb]` ( Function ): Callback function
|
|
// **Return**
|
|
// ( Boolen ): key found and ttl set
|
|
// **Example:**
|
|
// myCache.ttl( "myKey" ) // will set ttl to default ttl
|
|
// myCache.ttl( "myKey", 1000, ( err, keyFound )->
|
// console.log( err, success )
|
|
this.ttl = this.ttl.bind(this);
|
// ## getTtl
|
|
// receive the ttl of a key.
|
|
// **Parameters:**
|
|
// * `key` ( String | Number ): cache key to check the ttl value
|
// * `[cb]` ( Function ): Callback function
|
|
// **Return**
|
|
// ( Number|undefined ): The timestamp in ms when the key will expire, 0 if it will never expire or undefined if it not exists
|
|
// **Example:**
|
|
// ts = myCache.getTtl( "myKey" )
|
|
// myCache.getTtl( "myKey",( err, ttl )->
|
// console.log( err, ttl )
|
// return
|
|
this.getTtl = this.getTtl.bind(this);
|
// ## keys
|
|
// list all keys within this cache
|
|
// **Parameters:**
|
|
// * `[cb]` ( Function ): Callback function
|
|
// **Return**
|
|
// ( Array ): An array of all keys
|
|
// **Example:**
|
|
// _keys = myCache.keys()
|
|
// # [ "foo", "bar", "fizz", "buzz", "anotherKeys" ]
|
|
this.keys = this.keys.bind(this);
|
// ## getStats
|
|
// get the stats
|
|
// **Parameters:**
|
|
// -
|
|
// **Return**
|
|
// ( Object ): Stats data
|
|
// **Example:**
|
|
// myCache.getStats()
|
// # {
|
// # hits: 0,
|
// # misses: 0,
|
// # keys: 0,
|
// # ksize: 0,
|
// # vsize: 0
|
// # }
|
|
this.getStats = this.getStats.bind(this);
|
// ## flushAll
|
|
// flush the whole data and reset the stats
|
|
// **Example:**
|
|
// myCache.flushAll()
|
|
// myCache.getStats()
|
// # {
|
// # hits: 0,
|
// # misses: 0,
|
// # keys: 0,
|
// # ksize: 0,
|
// # vsize: 0
|
// # }
|
|
this.flushAll = this.flushAll.bind(this);
|
// ## close
|
|
// This will clear the interval timeout which is set on checkperiod option.
|
|
// **Example:**
|
|
// myCache.close()
|
|
this.close = this.close.bind(this);
|
// ## _checkData
|
|
// internal housekeeping method.
|
// Check all the cached data and delete the invalid values
|
this._checkData = this._checkData.bind(this);
|
// ## _check
|
|
// internal method the check the value. If it's not valid any more delete it
|
this._check = this._check.bind(this);
|
// ## _isInvalidKey
|
|
// internal method to check if the type of a key is either `number` or `string`
|
this._isInvalidKey = this._isInvalidKey.bind(this);
|
// ## _wrap
|
|
// internal method to wrap a value in an object with some metadata
|
this._wrap = this._wrap.bind(this);
|
// ## _getValLength
|
|
// internal method to calculate the value length
|
this._getValLength = this._getValLength.bind(this);
|
// ## _error
|
|
// internal method to handle an error message
|
this._error = this._error.bind(this);
|
// ## _initErrors
|
|
// internal method to generate error message templates
|
this._initErrors = this._initErrors.bind(this);
|
this.options = options;
|
this._initErrors();
|
// container for cached data
|
this.data = {};
|
// module options
|
this.options = _assignIn({
|
// convert all elements to string
|
forceString: false,
|
// used standard size for calculating value size
|
objectValueSize: 80,
|
promiseValueSize: 80,
|
arrayValueSize: 40,
|
// standard time to live in seconds. 0 = infinity;
|
stdTTL: 0,
|
// time in seconds to check all data and delete expired keys
|
checkperiod: 600,
|
// en/disable cloning of variables. If `true` you'll get a copy of the cached variable. If `false` you'll save and get just the reference
|
useClones: true,
|
// en/disable throwing errors when trying to `.get` missing or expired values.
|
errorOnMissing: false,
|
// whether values should be deleted automatically at expiration
|
deleteOnExpire: true
|
}, this.options);
|
// statistics container
|
this.stats = {
|
hits: 0,
|
misses: 0,
|
keys: 0,
|
ksize: 0,
|
vsize: 0
|
};
|
// pre allocate valid keytypes array
|
this.validKeyTypes = ["string", "number"];
|
// initalize checking period
|
this._checkData();
|
return;
|
}
|
|
get(key, cb, errorOnMissing) {
|
var _err, _ret, err;
|
boundMethodCheck(this, NodeCache);
|
// handle passing in errorOnMissing without cb
|
if (typeof cb === "boolean" && arguments.length === 2) {
|
errorOnMissing = cb;
|
cb = void 0;
|
}
|
// handle invalid key types
|
if ((err = this._isInvalidKey(key)) != null) {
|
if (cb != null) {
|
cb(err);
|
return;
|
} else {
|
throw err;
|
}
|
}
|
// get data and incremet stats
|
if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
this.stats.hits++;
|
_ret = this._unwrap(this.data[key]);
|
if (cb != null) {
|
// return data
|
cb(null, _ret);
|
}
|
return _ret;
|
} else {
|
// if not found return a error
|
this.stats.misses++;
|
if (this.options.errorOnMissing || errorOnMissing) {
|
_err = this._error("ENOTFOUND", {
|
key: key
|
}, cb);
|
if (_err != null) {
|
throw _err;
|
}
|
return;
|
} else {
|
if (cb != null) {
|
cb(null, void 0);
|
}
|
}
|
return void 0;
|
}
|
}
|
|
mget(keys, cb) {
|
var _err, err, i, key, len, oRet;
|
boundMethodCheck(this, NodeCache);
|
// convert a string to an array of one key
|
if (!_isArray(keys)) {
|
_err = this._error("EKEYSTYPE");
|
if (cb != null) {
|
cb(_err);
|
}
|
return _err;
|
}
|
// define return
|
oRet = {};
|
for (i = 0, len = keys.length; i < len; i++) {
|
key = keys[i];
|
// handle invalid key types
|
if ((err = this._isInvalidKey(key)) != null) {
|
if (cb != null) {
|
cb(err);
|
return;
|
} else {
|
throw err;
|
}
|
}
|
// get data and increment stats
|
if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
this.stats.hits++;
|
oRet[key] = this._unwrap(this.data[key]);
|
} else {
|
// if not found return a error
|
this.stats.misses++;
|
}
|
}
|
if (cb != null) {
|
// return all found keys
|
cb(null, oRet);
|
}
|
return oRet;
|
}
|
|
set(key, value, ttl, cb) {
|
var err, existent;
|
boundMethodCheck(this, NodeCache);
|
// force the data to string
|
if (this.options.forceString && !_isString(value)) {
|
value = JSON.stringify(value);
|
}
|
// remap the arguments if `ttl` is not passed
|
if (arguments.length === 3 && _isFunction(ttl)) {
|
cb = ttl;
|
ttl = this.options.stdTTL;
|
}
|
// handle invalid key types
|
if ((err = this._isInvalidKey(key)) != null) {
|
if (cb != null) {
|
cb(err);
|
return;
|
} else {
|
throw err;
|
}
|
}
|
// internal helper variables
|
existent = false;
|
// remove existing data from stats
|
if (this.data[key]) {
|
existent = true;
|
this.stats.vsize -= this._getValLength(this._unwrap(this.data[key], false));
|
}
|
// set the value
|
this.data[key] = this._wrap(value, ttl);
|
this.stats.vsize += this._getValLength(value);
|
// only add the keys and key-size if the key is new
|
if (!existent) {
|
this.stats.ksize += this._getKeyLength(key);
|
this.stats.keys++;
|
}
|
this.emit("set", key, value);
|
if (cb != null) {
|
// return true
|
cb(null, true);
|
}
|
return true;
|
}
|
|
del(keys, cb) {
|
var delCount, err, i, key, len, oldVal;
|
boundMethodCheck(this, NodeCache);
|
// convert keys to an array of itself
|
if (!_isArray(keys)) {
|
keys = [keys];
|
}
|
delCount = 0;
|
for (i = 0, len = keys.length; i < len; i++) {
|
key = keys[i];
|
// handle invalid key types
|
if ((err = this._isInvalidKey(key)) != null) {
|
if (cb != null) {
|
cb(err);
|
return;
|
} else {
|
throw err;
|
}
|
}
|
// only delete if existent
|
if (this.data[key] != null) {
|
// calc the stats
|
this.stats.vsize -= this._getValLength(this._unwrap(this.data[key], false));
|
this.stats.ksize -= this._getKeyLength(key);
|
this.stats.keys--;
|
delCount++;
|
// delete the value
|
oldVal = this.data[key];
|
delete this.data[key];
|
// return true
|
this.emit("del", key, oldVal.v);
|
} else {
|
// if the key has not been found return an error
|
this.stats.misses++;
|
}
|
}
|
if (cb != null) {
|
cb(null, delCount);
|
}
|
return delCount;
|
}
|
|
ttl() {
|
var arg, args, cb, err, i, key, len, ttl;
|
boundMethodCheck(this, NodeCache);
|
// change args if only key and callback are passed
|
[key, ...args] = arguments;
|
for (i = 0, len = args.length; i < len; i++) {
|
arg = args[i];
|
switch (typeof arg) {
|
case "number":
|
ttl = arg;
|
break;
|
case "function":
|
cb = arg;
|
}
|
}
|
ttl || (ttl = this.options.stdTTL);
|
if (!key) {
|
if (cb != null) {
|
cb(null, false);
|
}
|
return false;
|
}
|
// handle invalid key types
|
if ((err = this._isInvalidKey(key)) != null) {
|
if (cb != null) {
|
cb(err);
|
return;
|
} else {
|
throw err;
|
}
|
}
|
// check for existant data and update the ttl value
|
if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
// if ttl < 0 delete the key. otherwise reset the value
|
if (ttl >= 0) {
|
this.data[key] = this._wrap(this.data[key].v, ttl, false);
|
} else {
|
this.del(key);
|
}
|
if (cb != null) {
|
cb(null, true);
|
}
|
return true;
|
} else {
|
if (cb != null) {
|
// return false if key has not been found
|
cb(null, false);
|
}
|
return false;
|
}
|
}
|
|
getTtl(key, cb) {
|
var _ttl, err;
|
boundMethodCheck(this, NodeCache);
|
if (!key) {
|
if (cb != null) {
|
cb(null, void 0);
|
}
|
return void 0;
|
}
|
// handle invalid key types
|
if ((err = this._isInvalidKey(key)) != null) {
|
if (cb != null) {
|
cb(err);
|
return;
|
} else {
|
throw err;
|
}
|
}
|
// check for existant data and update the ttl value
|
if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
_ttl = this.data[key].t;
|
if (cb != null) {
|
cb(null, _ttl);
|
}
|
return _ttl;
|
} else {
|
if (cb != null) {
|
// return undefined if key has not been found
|
cb(null, void 0);
|
}
|
return void 0;
|
}
|
}
|
|
keys(cb) {
|
var _keys;
|
boundMethodCheck(this, NodeCache);
|
_keys = Object.keys(this.data);
|
if (cb != null) {
|
cb(null, _keys);
|
}
|
return _keys;
|
}
|
|
getStats() {
|
boundMethodCheck(this, NodeCache);
|
return this.stats;
|
}
|
|
flushAll(_startPeriod = true) {
|
boundMethodCheck(this, NodeCache);
|
// parameter just for testing
|
|
// set data empty
|
this.data = {};
|
// reset stats
|
this.stats = {
|
hits: 0,
|
misses: 0,
|
keys: 0,
|
ksize: 0,
|
vsize: 0
|
};
|
// reset check period
|
this._killCheckPeriod();
|
this._checkData(_startPeriod);
|
this.emit("flush");
|
}
|
|
close() {
|
boundMethodCheck(this, NodeCache);
|
this._killCheckPeriod();
|
}
|
|
_checkData(startPeriod = true) {
|
var key, ref, value;
|
boundMethodCheck(this, NodeCache);
|
ref = this.data;
|
// run the housekeeping method
|
for (key in ref) {
|
value = ref[key];
|
this._check(key, value);
|
}
|
if (startPeriod && this.options.checkperiod > 0) {
|
this.checkTimeout = setTimeout(this._checkData, this.options.checkperiod * 1000, startPeriod);
|
if (this.checkTimeout.unref != null) {
|
this.checkTimeout.unref();
|
}
|
}
|
}
|
|
// ## _killCheckPeriod
|
|
// stop the checkdata period. Only needed to abort the script in testing mode.
|
_killCheckPeriod() {
|
if (this.checkTimeout != null) {
|
return clearTimeout(this.checkTimeout);
|
}
|
}
|
|
_check(key, data) {
|
var _retval;
|
boundMethodCheck(this, NodeCache);
|
_retval = true;
|
// data is invalid if the ttl is too old and is not 0
|
// console.log data.t < Date.now(), data.t, Date.now()
|
if (data.t !== 0 && data.t < Date.now()) {
|
if (this.options.deleteOnExpire) {
|
_retval = false;
|
this.del(key);
|
}
|
this.emit("expired", key, this._unwrap(data));
|
}
|
return _retval;
|
}
|
|
_isInvalidKey(key) {
|
var ref;
|
boundMethodCheck(this, NodeCache);
|
if (ref = typeof key, indexOf.call(this.validKeyTypes, ref) < 0) {
|
return this._error("EKEYTYPE", {
|
type: typeof key
|
});
|
}
|
}
|
|
_wrap(value, ttl, asClone = true) {
|
var livetime, now, oReturn, ttlMultiplicator;
|
boundMethodCheck(this, NodeCache);
|
if (!this.options.useClones) {
|
asClone = false;
|
}
|
// define the time to live
|
now = Date.now();
|
livetime = 0;
|
ttlMultiplicator = 1000;
|
// use given ttl
|
if (ttl === 0) {
|
livetime = 0;
|
} else if (ttl) {
|
livetime = now + (ttl * ttlMultiplicator);
|
} else {
|
// use standard ttl
|
if (this.options.stdTTL === 0) {
|
livetime = this.options.stdTTL;
|
} else {
|
livetime = now + (this.options.stdTTL * ttlMultiplicator);
|
}
|
}
|
// return the wrapped value
|
return oReturn = {
|
t: livetime,
|
v: asClone ? clone(value) : value
|
};
|
}
|
|
// ## _unwrap
|
|
// internal method to extract get the value out of the wrapped value
|
_unwrap(value, asClone = true) {
|
if (!this.options.useClones) {
|
asClone = false;
|
}
|
if (value.v != null) {
|
if (asClone) {
|
return clone(value.v);
|
} else {
|
return value.v;
|
}
|
}
|
return null;
|
}
|
|
// ## _getKeyLength
|
|
// internal method the calculate the key length
|
_getKeyLength(key) {
|
return key.length;
|
}
|
|
_getValLength(value) {
|
boundMethodCheck(this, NodeCache);
|
if (_isString(value)) {
|
// if the value is a String get the real length
|
return value.length;
|
} else if (this.options.forceString) {
|
// force string if it's defined and not passed
|
return JSON.stringify(value).length;
|
} else if (_isArray(value)) {
|
// if the data is an Array multiply each element with a defined default length
|
return this.options.arrayValueSize * value.length;
|
} else if (_isNumber(value)) {
|
return 8;
|
} else if (typeof (value != null ? value.then : void 0) === "function") {
|
// if the data is a Promise, use defined default
|
// (can't calculate actual/resolved value size synchronously)
|
return this.options.promiseValueSize;
|
} else if (_isObject(value)) {
|
// if the data is an Object multiply each element with a defined default length
|
return this.options.objectValueSize * _size(value);
|
} else {
|
// default fallback
|
return 0;
|
}
|
}
|
|
_error(type, data = {}, cb) {
|
var error;
|
boundMethodCheck(this, NodeCache);
|
// generate the error object
|
error = new Error();
|
error.name = type;
|
error.errorcode = type;
|
error.message = this.ERRORS[type] != null ? this.ERRORS[type](data) : "-";
|
error.data = data;
|
if (cb && _isFunction(cb)) {
|
// return the error
|
cb(error, null);
|
} else {
|
// if no callback is defined return the error object
|
return error;
|
}
|
}
|
|
_initErrors() {
|
var _errMsg, _errT, ref;
|
boundMethodCheck(this, NodeCache);
|
this.ERRORS = {};
|
ref = this._ERRORS;
|
for (_errT in ref) {
|
_errMsg = ref[_errT];
|
this.ERRORS[_errT] = _template(_errMsg);
|
}
|
}
|
|
};
|
|
NodeCache.prototype._ERRORS = {
|
"ENOTFOUND": "Key `<%= key %>` not found",
|
"EKEYTYPE": "The key argument has to be of type `string` or `number`. Found: `<%= type %>`",
|
"EKEYSTYPE": "The keys argument has to be an array."
|
};
|
|
return NodeCache;
|
|
}).call(this);
|
|
}).call(this);
|