UNPKG

23.1 kBJavaScriptView Raw
1/*
2 * node-cache 5.1.0 ( 2019-12-08 )
3 * https://github.com/mpneuried/nodecache
4 *
5 * Released under the MIT license
6 * https://github.com/mpneuried/nodecache/blob/master/LICENSE
7 *
8 * Maintained by ( )
9*/
10(function() {
11 var EventEmitter, NodeCache, clone,
12 splice = [].splice,
13 boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } },
14 indexOf = [].indexOf;
15
16 clone = require("clone");
17
18 EventEmitter = require('events').EventEmitter;
19
20 // generate superclass
21 module.exports = NodeCache = (function() {
22 class NodeCache extends EventEmitter {
23 constructor(options = {}) {
24 super();
25 // ## get
26
27 // get a cached key and change the stats
28
29 // **Parameters:**
30
31 // * `key` ( String | Number ): cache key
32
33 // **Example:**
34
35 // myCache.get "myKey", ( err, val )
36
37 this.get = this.get.bind(this);
38 // ## mget
39
40 // get multiple cached keys at once and change the stats
41
42 // **Parameters:**
43
44 // * `keys` ( String|Number[] ): an array of keys
45
46 // **Example:**
47
48 // myCache.mget [ "foo", "bar" ]
49
50 this.mget = this.mget.bind(this);
51 // ## set
52
53 // set a cached key and change the stats
54
55 // **Parameters:**
56
57 // * `key` ( String | Number ): cache key
58 // * `value` ( Any ): A element to cache. If the option `option.forceString` is `true` the module trys to translate it to a serialized JSON
59 // * `[ ttl ]` ( Number | String ): ( optional ) The time to live in seconds.
60
61 // **Example:**
62
63 // myCache.set "myKey", "my_String Value"
64
65 // myCache.set "myKey", "my_String Value", 10
66
67 this.set = this.set.bind(this);
68
69 // ## mset
70
71 // set multiple keys at once
72
73 // **Parameters:**
74
75 // * `keyValueSet` ( Object[] ): an array of object which includes key,value and ttl
76
77 // **Example:**
78
79 // myCache.mset(
80 // [
81 // {
82 // key: "myKey",
83 // val: "myValue",
84 // ttl: [ttl in seconds]
85 // }
86 // ])
87
88 this.mset = this.mset.bind(this);
89 // ## del
90
91 // remove keys
92
93 // **Parameters:**
94
95 // * `keys` ( String | Number | String|Number[] ): cache key to delete or a array of cache keys
96
97 // **Return**
98
99 // ( Number ): Number of deleted keys
100
101 // **Example:**
102
103 // myCache.del( "myKey" )
104
105 this.del = this.del.bind(this);
106 // ## take
107
108 // get the cached value and remove the key from the cache.
109 // Equivalent to calling `get(key)` + `del(key)`.
110 // Useful for implementing `single use` mechanism such as OTP, where once a value is read it will become obsolete.
111
112 // **Parameters:**
113
114 // * `key` ( String | Number ): cache key
115
116 // **Example:**
117
118 // myCache.take "myKey", ( err, val )
119
120 this.take = this.take.bind(this);
121 // ## ttl
122
123 // reset or redefine the ttl of a key. `ttl` = 0 means infinite lifetime.
124 // If `ttl` is not passed the default ttl is used.
125 // If `ttl` < 0 the key will be deleted.
126
127 // **Parameters:**
128
129 // * `key` ( String | Number ): cache key to reset the ttl value
130 // * `ttl` ( Number ): ( optional -> options.stdTTL || 0 ) The time to live in seconds
131
132 // **Return**
133
134 // ( Boolen ): key found and ttl set
135
136 // **Example:**
137
138 // myCache.ttl( "myKey" ) // will set ttl to default ttl
139
140 // myCache.ttl( "myKey", 1000 )
141
142 this.ttl = this.ttl.bind(this);
143 // ## getTtl
144
145 // receive the ttl of a key.
146
147 // **Parameters:**
148
149 // * `key` ( String | Number ): cache key to check the ttl value
150
151 // **Return**
152
153 // ( Number|undefined ): The timestamp in ms when the key will expire, 0 if it will never expire or undefined if it not exists
154
155 // **Example:**
156
157 // myCache.getTtl( "myKey" )
158
159 this.getTtl = this.getTtl.bind(this);
160 // ## keys
161
162 // list all keys within this cache
163
164 // **Return**
165
166 // ( Array ): An array of all keys
167
168 // **Example:**
169
170 // _keys = myCache.keys()
171
172 // # [ "foo", "bar", "fizz", "buzz", "anotherKeys" ]
173
174 this.keys = this.keys.bind(this);
175 // ## has
176
177 // Check if a key is cached
178
179 // **Parameters:**
180
181 // * `key` ( String | Number ): cache key to check the ttl value
182
183 // **Return**
184
185 // ( Boolean ): A boolean that indicates if the key is cached
186
187 // **Example:**
188
189 // _exists = myCache.has('myKey')
190
191 // # true
192
193 this.has = this.has.bind(this);
194 // ## getStats
195
196 // get the stats
197
198 // **Parameters:**
199
200 // -
201
202 // **Return**
203
204 // ( Object ): Stats data
205
206 // **Example:**
207
208 // myCache.getStats()
209 // # {
210 // # hits: 0,
211 // # misses: 0,
212 // # keys: 0,
213 // # ksize: 0,
214 // # vsize: 0
215 // # }
216
217 this.getStats = this.getStats.bind(this);
218 // ## flushAll
219
220 // flush the whole data and reset the stats
221
222 // **Example:**
223
224 // myCache.flushAll()
225
226 // myCache.getStats()
227 // # {
228 // # hits: 0,
229 // # misses: 0,
230 // # keys: 0,
231 // # ksize: 0,
232 // # vsize: 0
233 // # }
234
235 this.flushAll = this.flushAll.bind(this);
236
237 // ## flushStats
238
239 // flush the stats and reset all counters to 0
240
241 // **Example:**
242
243 // myCache.flushStats()
244
245 // myCache.getStats()
246 // # {
247 // # hits: 0,
248 // # misses: 0,
249 // # keys: 0,
250 // # ksize: 0,
251 // # vsize: 0
252 // # }
253
254 this.flushStats = this.flushStats.bind(this);
255 // ## close
256
257 // This will clear the interval timeout which is set on checkperiod option.
258
259 // **Example:**
260
261 // myCache.close()
262
263 this.close = this.close.bind(this);
264 // ## _checkData
265
266 // internal housekeeping method.
267 // Check all the cached data and delete the invalid values
268 this._checkData = this._checkData.bind(this);
269 // ## _check
270
271 // internal method the check the value. If it's not valid any more delete it
272 this._check = this._check.bind(this);
273 // ## _isInvalidKey
274
275 // internal method to check if the type of a key is either `number` or `string`
276 this._isInvalidKey = this._isInvalidKey.bind(this);
277 // ## _wrap
278
279 // internal method to wrap a value in an object with some metadata
280 this._wrap = this._wrap.bind(this);
281 // ## _getValLength
282
283 // internal method to calculate the value length
284 this._getValLength = this._getValLength.bind(this);
285 // ## _error
286
287 // internal method to handle an error message
288 this._error = this._error.bind(this);
289 // ## _initErrors
290
291 // internal method to generate error message templates
292 this._initErrors = this._initErrors.bind(this);
293 this.options = options;
294 this._initErrors();
295 // container for cached data
296 this.data = {};
297 // module options
298 this.options = Object.assign({
299 // convert all elements to string
300 forceString: false,
301 // used standard size for calculating value size
302 objectValueSize: 80,
303 promiseValueSize: 80,
304 arrayValueSize: 40,
305 // standard time to live in seconds. 0 = infinity;
306 stdTTL: 0,
307 // time in seconds to check all data and delete expired keys
308 checkperiod: 600,
309 // 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
310 useClones: true,
311 // whether values should be deleted automatically at expiration
312 deleteOnExpire: true,
313 // enable legacy callbacks
314 enableLegacyCallbacks: false,
315 // max amount of keys that are being stored
316 maxKeys: -1
317 }, this.options);
318 // generate functions with callbacks (legacy)
319 if (this.options.enableLegacyCallbacks) {
320 console.warn("WARNING! node-cache legacy callback support will drop in v6.x");
321 ["get", "mget", "set", "del", "ttl", "getTtl", "keys", "has"].forEach((methodKey) => {
322 var oldMethod;
323 // reference real function
324 oldMethod = this[methodKey];
325 this[methodKey] = function(...args) {
326 var cb, err, ref, res;
327 ref = args, [...args] = ref, [cb] = splice.call(args, -1);
328 // return a callback if cb is defined and a function
329 if (typeof cb === "function") {
330 try {
331 res = oldMethod(...args);
332 cb(null, res);
333 } catch (error1) {
334 err = error1;
335 cb(err);
336 }
337 } else {
338 return oldMethod(...args, cb);
339 }
340 };
341 });
342 }
343 // statistics container
344 this.stats = {
345 hits: 0,
346 misses: 0,
347 keys: 0,
348 ksize: 0,
349 vsize: 0
350 };
351 // pre allocate valid keytypes array
352 this.validKeyTypes = ["string", "number"];
353 // initalize checking period
354 this._checkData();
355 return;
356 }
357
358 get(key) {
359 var _ret, err;
360 boundMethodCheck(this, NodeCache);
361 // handle invalid key types
362 if ((err = this._isInvalidKey(key)) != null) {
363 throw err;
364 }
365 // get data and incremet stats
366 if ((this.data[key] != null) && this._check(key, this.data[key])) {
367 this.stats.hits++;
368 _ret = this._unwrap(this.data[key]);
369 // return data
370 return _ret;
371 } else {
372 // if not found return undefined
373 this.stats.misses++;
374 return void 0;
375 }
376 }
377
378 mget(keys) {
379 var _err, err, i, key, len, oRet;
380 boundMethodCheck(this, NodeCache);
381 // convert a string to an array of one key
382 if (!Array.isArray(keys)) {
383 _err = this._error("EKEYSTYPE");
384 throw _err;
385 }
386 // define return
387 oRet = {};
388 for (i = 0, len = keys.length; i < len; i++) {
389 key = keys[i];
390 // handle invalid key types
391 if ((err = this._isInvalidKey(key)) != null) {
392 throw err;
393 }
394 // get data and increment stats
395 if ((this.data[key] != null) && this._check(key, this.data[key])) {
396 this.stats.hits++;
397 oRet[key] = this._unwrap(this.data[key]);
398 } else {
399 // if not found return a error
400 this.stats.misses++;
401 }
402 }
403 // return all found keys
404 return oRet;
405 }
406
407 set(key, value, ttl) {
408 var _err, err, existent;
409 boundMethodCheck(this, NodeCache);
410 // check if cache is overflowing
411 if (this.options.maxKeys > -1 && this.stats.keys >= this.options.maxKeys) {
412 _err = this._error("ECACHEFULL");
413 throw _err;
414 }
415 // force the data to string
416 if (this.options.forceString && !typeof value === "string") {
417 value = JSON.stringify(value);
418 }
419 // set default ttl if not passed
420 if (ttl == null) {
421 ttl = this.options.stdTTL;
422 }
423 // handle invalid key types
424 if ((err = this._isInvalidKey(key)) != null) {
425 throw err;
426 }
427 // internal helper variables
428 existent = false;
429 // remove existing data from stats
430 if (this.data[key]) {
431 existent = true;
432 this.stats.vsize -= this._getValLength(this._unwrap(this.data[key], false));
433 }
434 // set the value
435 this.data[key] = this._wrap(value, ttl);
436 this.stats.vsize += this._getValLength(value);
437 // only add the keys and key-size if the key is new
438 if (!existent) {
439 this.stats.ksize += this._getKeyLength(key);
440 this.stats.keys++;
441 }
442 this.emit("set", key, value);
443 // return true
444 return true;
445 }
446
447 mset(keyValueSet) {
448 var _err, err, i, j, key, keyValuePair, len, len1, ttl, val;
449 boundMethodCheck(this, NodeCache);
450 // check if cache is overflowing
451 if (this.options.maxKeys > -1 && this.stats.keys + keyValueSet.length >= this.options.maxKeys) {
452 _err = this._error("ECACHEFULL");
453 throw _err;
454 }
455
456// loop over keyValueSet to validate key and ttl
457 for (i = 0, len = keyValueSet.length; i < len; i++) {
458 keyValuePair = keyValueSet[i];
459 ({key, val, ttl} = keyValuePair);
460 // check if there is ttl and it's a number
461 if (ttl && typeof ttl !== "number") {
462 _err = this._error("ETTLTYPE");
463 throw _err;
464 }
465 // handle invalid key types
466 if ((err = this._isInvalidKey(key)) != null) {
467 throw err;
468 }
469 }
470 for (j = 0, len1 = keyValueSet.length; j < len1; j++) {
471 keyValuePair = keyValueSet[j];
472 ({key, val, ttl} = keyValuePair);
473 this.set(key, val, ttl);
474 }
475 return true;
476 }
477
478 del(keys) {
479 var delCount, err, i, key, len, oldVal;
480 boundMethodCheck(this, NodeCache);
481 // convert keys to an array of itself
482 if (!Array.isArray(keys)) {
483 keys = [keys];
484 }
485 delCount = 0;
486 for (i = 0, len = keys.length; i < len; i++) {
487 key = keys[i];
488 // handle invalid key types
489 if ((err = this._isInvalidKey(key)) != null) {
490 throw err;
491 }
492 // only delete if existent
493 if (this.data[key] != null) {
494 // calc the stats
495 this.stats.vsize -= this._getValLength(this._unwrap(this.data[key], false));
496 this.stats.ksize -= this._getKeyLength(key);
497 this.stats.keys--;
498 delCount++;
499 // delete the value
500 oldVal = this.data[key];
501 delete this.data[key];
502 // return true
503 this.emit("del", key, oldVal.v);
504 }
505 }
506 return delCount;
507 }
508
509 take(key) {
510 var _ret;
511 boundMethodCheck(this, NodeCache);
512 _ret = this.get(key);
513 if ((_ret != null)) {
514 this.del(key);
515 }
516 return _ret;
517 }
518
519 ttl(key, ttl) {
520 var err;
521 boundMethodCheck(this, NodeCache);
522 ttl || (ttl = this.options.stdTTL);
523 if (!key) {
524 return false;
525 }
526 // handle invalid key types
527 if ((err = this._isInvalidKey(key)) != null) {
528 throw err;
529 }
530 // check for existent data and update the ttl value
531 if ((this.data[key] != null) && this._check(key, this.data[key])) {
532 // if ttl < 0 delete the key. otherwise reset the value
533 if (ttl >= 0) {
534 this.data[key] = this._wrap(this.data[key].v, ttl, false);
535 } else {
536 this.del(key);
537 }
538 return true;
539 } else {
540 // return false if key has not been found
541 return false;
542 }
543 }
544
545 getTtl(key) {
546 var _ttl, err;
547 boundMethodCheck(this, NodeCache);
548 if (!key) {
549 return void 0;
550 }
551 // handle invalid key types
552 if ((err = this._isInvalidKey(key)) != null) {
553 throw err;
554 }
555 // check for existant data and update the ttl value
556 if ((this.data[key] != null) && this._check(key, this.data[key])) {
557 _ttl = this.data[key].t;
558 return _ttl;
559 } else {
560 // return undefined if key has not been found
561 return void 0;
562 }
563 }
564
565 keys() {
566 var _keys;
567 boundMethodCheck(this, NodeCache);
568 _keys = Object.keys(this.data);
569 return _keys;
570 }
571
572 has(key) {
573 var _exists;
574 boundMethodCheck(this, NodeCache);
575 _exists = (this.data[key] != null) && this._check(key, this.data[key]);
576 return _exists;
577 }
578
579 getStats() {
580 boundMethodCheck(this, NodeCache);
581 return this.stats;
582 }
583
584 flushAll(_startPeriod = true) {
585 boundMethodCheck(this, NodeCache);
586 // parameter just for testing
587
588 // set data empty
589 this.data = {};
590 // reset stats
591 this.stats = {
592 hits: 0,
593 misses: 0,
594 keys: 0,
595 ksize: 0,
596 vsize: 0
597 };
598 // reset check period
599 this._killCheckPeriod();
600 this._checkData(_startPeriod);
601 this.emit("flush");
602 }
603
604 flushStats() {
605 boundMethodCheck(this, NodeCache);
606 // reset stats
607 this.stats = {
608 hits: 0,
609 misses: 0,
610 keys: 0,
611 ksize: 0,
612 vsize: 0
613 };
614 this.emit("flush_stats");
615 }
616
617 close() {
618 boundMethodCheck(this, NodeCache);
619 this._killCheckPeriod();
620 }
621
622 _checkData(startPeriod = true) {
623 var key, ref, value;
624 boundMethodCheck(this, NodeCache);
625 ref = this.data;
626 // run the housekeeping method
627 for (key in ref) {
628 value = ref[key];
629 this._check(key, value);
630 }
631 if (startPeriod && this.options.checkperiod > 0) {
632 this.checkTimeout = setTimeout(this._checkData, this.options.checkperiod * 1000, startPeriod);
633 if (this.checkTimeout.unref != null) {
634 this.checkTimeout.unref();
635 }
636 }
637 }
638
639 // ## _killCheckPeriod
640
641 // stop the checkdata period. Only needed to abort the script in testing mode.
642 _killCheckPeriod() {
643 if (this.checkTimeout != null) {
644 return clearTimeout(this.checkTimeout);
645 }
646 }
647
648 _check(key, data) {
649 var _retval;
650 boundMethodCheck(this, NodeCache);
651 _retval = true;
652 // data is invalid if the ttl is too old and is not 0
653 // console.log data.t < Date.now(), data.t, Date.now()
654 if (data.t !== 0 && data.t < Date.now()) {
655 if (this.options.deleteOnExpire) {
656 _retval = false;
657 this.del(key);
658 }
659 this.emit("expired", key, this._unwrap(data));
660 }
661 return _retval;
662 }
663
664 _isInvalidKey(key) {
665 var ref;
666 boundMethodCheck(this, NodeCache);
667 if (ref = typeof key, indexOf.call(this.validKeyTypes, ref) < 0) {
668 return this._error("EKEYTYPE", {
669 type: typeof key
670 });
671 }
672 }
673
674 _wrap(value, ttl, asClone = true) {
675 var livetime, now, oReturn, ttlMultiplicator;
676 boundMethodCheck(this, NodeCache);
677 if (!this.options.useClones) {
678 asClone = false;
679 }
680 // define the time to live
681 now = Date.now();
682 livetime = 0;
683 ttlMultiplicator = 1000;
684 // use given ttl
685 if (ttl === 0) {
686 livetime = 0;
687 } else if (ttl) {
688 livetime = now + (ttl * ttlMultiplicator);
689 } else {
690 // use standard ttl
691 if (this.options.stdTTL === 0) {
692 livetime = this.options.stdTTL;
693 } else {
694 livetime = now + (this.options.stdTTL * ttlMultiplicator);
695 }
696 }
697 // return the wrapped value
698 return oReturn = {
699 t: livetime,
700 v: asClone ? clone(value) : value
701 };
702 }
703
704 // ## _unwrap
705
706 // internal method to extract get the value out of the wrapped value
707 _unwrap(value, asClone = true) {
708 if (!this.options.useClones) {
709 asClone = false;
710 }
711 if (value.v != null) {
712 if (asClone) {
713 return clone(value.v);
714 } else {
715 return value.v;
716 }
717 }
718 return null;
719 }
720
721 // ## _getKeyLength
722
723 // internal method the calculate the key length
724 _getKeyLength(key) {
725 return key.toString().length;
726 }
727
728 _getValLength(value) {
729 boundMethodCheck(this, NodeCache);
730 if (typeof value === "string") {
731 // if the value is a String get the real length
732 return value.length;
733 } else if (this.options.forceString) {
734 // force string if it's defined and not passed
735 return JSON.stringify(value).length;
736 } else if (Array.isArray(value)) {
737 // if the data is an Array multiply each element with a defined default length
738 return this.options.arrayValueSize * value.length;
739 } else if (typeof value === "number") {
740 return 8;
741 } else if (typeof (value != null ? value.then : void 0) === "function") {
742 // if the data is a Promise, use defined default
743 // (can't calculate actual/resolved value size synchronously)
744 return this.options.promiseValueSize;
745 } else if ((value != null) && typeof value === "object") {
746 // if the data is an Object multiply each element with a defined default length
747 return this.options.objectValueSize * Object.keys(value).length;
748 } else if (typeof value === "boolean") {
749 return 8;
750 } else {
751 // default fallback
752 return 0;
753 }
754 }
755
756 _error(type, data = {}) {
757 var error;
758 boundMethodCheck(this, NodeCache);
759 // generate the error object
760 error = new Error();
761 error.name = type;
762 error.errorcode = type;
763 error.message = this.ERRORS[type] != null ? this.ERRORS[type](data) : "-";
764 error.data = data;
765 // return the error object
766 return error;
767 }
768
769 _initErrors() {
770 var _errMsg, _errT, ref;
771 boundMethodCheck(this, NodeCache);
772 this.ERRORS = {};
773 ref = this._ERRORS;
774 for (_errT in ref) {
775 _errMsg = ref[_errT];
776 this.ERRORS[_errT] = this.createErrorMessage(_errMsg);
777 }
778 }
779
780 createErrorMessage(errMsg) {
781 return function(args) {
782 return errMsg.replace("__key", args.type);
783 };
784 }
785
786 };
787
788 NodeCache.prototype._ERRORS = {
789 "ENOTFOUND": "Key `__key` not found",
790 "ECACHEFULL": "Cache max key size exceeded",
791 "EKEYTYPE": "The key argument has to be of type `string` or `number`. Found: `__key`",
792 "EKEYSTYPE": "The keys argument has to be an array.",
793 "ETTLTYPE": "The ttl argument has to be a number."
794 };
795
796 return NodeCache;
797
798 }).call(this);
799
800}).call(this);