803 lines
23 KiB
JavaScript
Raw Normal View History

2025-06-03 11:09:52 +08:00
/*
* node-cache 5.1.2 ( 2020-07-01 )
* https://github.com/node-cache/node-cache
*
* Released under the MIT license
* https://github.com/node-cache/node-cache/blob/master/LICENSE
*
* Maintained by ( )
*/
(function() {
var EventEmitter, NodeCache, clone,
splice = [].splice,
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } },
indexOf = [].indexOf;
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
// **Example:**
// myCache.get "myKey", ( err, val )
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
// **Example:**
// myCache.mget [ "foo", "bar" ]
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.
// **Example:**
// myCache.set "myKey", "my_String Value"
// myCache.set "myKey", "my_String Value", 10
this.set = this.set.bind(this);
// ## mset
// set multiple keys at once
// **Parameters:**
// * `keyValueSet` ( Object[] ): an array of object which includes key,value and ttl
// **Example:**
// myCache.mset(
// [
// {
// key: "myKey",
// val: "myValue",
// ttl: [ttl in seconds]
// }
// ])
this.mset = this.mset.bind(this);
// ## del
// remove keys
// **Parameters:**
// * `keys` ( String | Number | String|Number[] ): cache key to delete or a array of cache keys
// **Return**
// ( Number ): Number of deleted keys
// **Example:**
// myCache.del( "myKey" )
this.del = this.del.bind(this);
// ## take
// get the cached value and remove the key from the cache.
// Equivalent to calling `get(key)` + `del(key)`.
// Useful for implementing `single use` mechanism such as OTP, where once a value is read it will become obsolete.
// **Parameters:**
// * `key` ( String | Number ): cache key
// **Example:**
// myCache.take "myKey", ( err, val )
this.take = this.take.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
// **Return**
// ( Boolen ): key found and ttl set
// **Example:**
// myCache.ttl( "myKey" ) // will set ttl to default ttl
// myCache.ttl( "myKey", 1000 )
this.ttl = this.ttl.bind(this);
// ## getTtl
// receive the ttl of a key.
// **Parameters:**
// * `key` ( String | Number ): cache key to check the ttl value
// **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:**
// myCache.getTtl( "myKey" )
this.getTtl = this.getTtl.bind(this);
// ## keys
// list all keys within this cache
// **Return**
// ( Array ): An array of all keys
// **Example:**
// _keys = myCache.keys()
// # [ "foo", "bar", "fizz", "buzz", "anotherKeys" ]
this.keys = this.keys.bind(this);
// ## has
// Check if a key is cached
// **Parameters:**
// * `key` ( String | Number ): cache key to check the ttl value
// **Return**
// ( Boolean ): A boolean that indicates if the key is cached
// **Example:**
// _exists = myCache.has('myKey')
// # true
this.has = this.has.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);
// ## flushStats
// flush the stats and reset all counters to 0
// **Example:**
// myCache.flushStats()
// myCache.getStats()
// # {
// # hits: 0,
// # misses: 0,
// # keys: 0,
// # ksize: 0,
// # vsize: 0
// # }
this.flushStats = this.flushStats.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 = Object.assign({
// 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,
// whether values should be deleted automatically at expiration
deleteOnExpire: true,
// enable legacy callbacks
enableLegacyCallbacks: false,
// max amount of keys that are being stored
maxKeys: -1
}, this.options);
// generate functions with callbacks (legacy)
if (this.options.enableLegacyCallbacks) {
console.warn("WARNING! node-cache legacy callback support will drop in v6.x");
["get", "mget", "set", "del", "ttl", "getTtl", "keys", "has"].forEach((methodKey) => {
var oldMethod;
// reference real function
oldMethod = this[methodKey];
this[methodKey] = function(...args) {
var cb, err, ref, res;
ref = args, [...args] = ref, [cb] = splice.call(args, -1);
// return a callback if cb is defined and a function
if (typeof cb === "function") {
try {
res = oldMethod(...args);
cb(null, res);
} catch (error1) {
err = error1;
cb(err);
}
} else {
return oldMethod(...args, cb);
}
};
});
}
// 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) {
var _ret, err;
boundMethodCheck(this, NodeCache);
// handle invalid key types
if ((err = this._isInvalidKey(key)) != null) {
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]);
// return data
return _ret;
} else {
// if not found return undefined
this.stats.misses++;
return void 0;
}
}
mget(keys) {
var _err, err, i, key, len, oRet;
boundMethodCheck(this, NodeCache);
// convert a string to an array of one key
if (!Array.isArray(keys)) {
_err = this._error("EKEYSTYPE");
throw _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) {
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++;
}
}
// return all found keys
return oRet;
}
set(key, value, ttl) {
var _err, err, existent;
boundMethodCheck(this, NodeCache);
// check if cache is overflowing
if (this.options.maxKeys > -1 && this.stats.keys >= this.options.maxKeys) {
_err = this._error("ECACHEFULL");
throw _err;
}
// force the data to string
if (this.options.forceString && !typeof value === "string") {
value = JSON.stringify(value);
}
// set default ttl if not passed
if (ttl == null) {
ttl = this.options.stdTTL;
}
// handle invalid key types
if ((err = this._isInvalidKey(key)) != null) {
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);
// return true
return true;
}
mset(keyValueSet) {
var _err, err, i, j, key, keyValuePair, len, len1, ttl, val;
boundMethodCheck(this, NodeCache);
// check if cache is overflowing
if (this.options.maxKeys > -1 && this.stats.keys + keyValueSet.length >= this.options.maxKeys) {
_err = this._error("ECACHEFULL");
throw _err;
}
// loop over keyValueSet to validate key and ttl
for (i = 0, len = keyValueSet.length; i < len; i++) {
keyValuePair = keyValueSet[i];
({key, val, ttl} = keyValuePair);
// check if there is ttl and it's a number
if (ttl && typeof ttl !== "number") {
_err = this._error("ETTLTYPE");
throw _err;
}
// handle invalid key types
if ((err = this._isInvalidKey(key)) != null) {
throw err;
}
}
for (j = 0, len1 = keyValueSet.length; j < len1; j++) {
keyValuePair = keyValueSet[j];
({key, val, ttl} = keyValuePair);
this.set(key, val, ttl);
}
return true;
}
del(keys) {
var delCount, err, i, key, len, oldVal;
boundMethodCheck(this, NodeCache);
// convert keys to an array of itself
if (!Array.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) {
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);
}
}
return delCount;
}
take(key) {
var _ret;
boundMethodCheck(this, NodeCache);
_ret = this.get(key);
if ((_ret != null)) {
this.del(key);
}
return _ret;
}
ttl(key, ttl) {
var err;
boundMethodCheck(this, NodeCache);
ttl || (ttl = this.options.stdTTL);
if (!key) {
return false;
}
// handle invalid key types
if ((err = this._isInvalidKey(key)) != null) {
throw err;
}
// check for existent 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);
}
return true;
} else {
// return false if key has not been found
return false;
}
}
getTtl(key) {
var _ttl, err;
boundMethodCheck(this, NodeCache);
if (!key) {
return void 0;
}
// handle invalid key types
if ((err = this._isInvalidKey(key)) != null) {
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;
return _ttl;
} else {
// return undefined if key has not been found
return void 0;
}
}
keys() {
var _keys;
boundMethodCheck(this, NodeCache);
_keys = Object.keys(this.data);
return _keys;
}
has(key) {
var _exists;
boundMethodCheck(this, NodeCache);
_exists = (this.data[key] != null) && this._check(key, this.data[key]);
return _exists;
}
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");
}
flushStats() {
boundMethodCheck(this, NodeCache);
// reset stats
this.stats = {
hits: 0,
misses: 0,
keys: 0,
ksize: 0,
vsize: 0
};
this.emit("flush_stats");
}
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 != null) && (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.toString().length;
}
_getValLength(value) {
boundMethodCheck(this, NodeCache);
if (typeof value === "string") {
// 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 (Array.isArray(value)) {
// if the data is an Array multiply each element with a defined default length
return this.options.arrayValueSize * value.length;
} else if (typeof value === "number") {
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 (typeof Buffer !== "undefined" && Buffer !== null ? Buffer.isBuffer(value) : void 0) {
return value.length;
} else if ((value != null) && typeof value === "object") {
// if the data is an Object multiply each element with a defined default length
return this.options.objectValueSize * Object.keys(value).length;
} else if (typeof value === "boolean") {
return 8;
} else {
// default fallback
return 0;
}
}
_error(type, data = {}) {
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;
// 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] = this.createErrorMessage(_errMsg);
}
}
createErrorMessage(errMsg) {
return function(args) {
return errMsg.replace("__key", args.type);
};
}
};
NodeCache.prototype._ERRORS = {
"ENOTFOUND": "Key `__key` not found",
"ECACHEFULL": "Cache max keys amount exceeded",
"EKEYTYPE": "The key argument has to be of type `string` or `number`. Found: `__key`",
"EKEYSTYPE": "The keys argument has to be an array.",
"ETTLTYPE": "The ttl argument has to be a number."
};
return NodeCache;
}).call(this);
}).call(this);