1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | (function() {
|
11 | var EventEmitter, NodeCache, _assignIn, _isArray, _isFunction, _isNumber, _isObject, _isString, _size, _template, clone,
|
12 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
13 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
14 | hasProp = {}.hasOwnProperty,
|
15 | slice = [].slice,
|
16 | indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
17 |
|
18 | _assignIn = require("lodash/assignIn");
|
19 |
|
20 | _isArray = require("lodash/isArray");
|
21 |
|
22 | _isString = require("lodash/isString");
|
23 |
|
24 | _isFunction = require("lodash/isFunction");
|
25 |
|
26 | _isNumber = require("lodash/isNumber");
|
27 |
|
28 | _isObject = require("lodash/isObject");
|
29 |
|
30 | _size = require("lodash/size");
|
31 |
|
32 | _template = require("lodash/template");
|
33 |
|
34 | clone = require("clone");
|
35 |
|
36 | EventEmitter = require('events').EventEmitter;
|
37 |
|
38 | module.exports = NodeCache = (function(superClass) {
|
39 | extend(NodeCache, superClass);
|
40 |
|
41 | function NodeCache(options) {
|
42 | this.options = options != null ? options : {};
|
43 | this._initErrors = bind(this._initErrors, this);
|
44 | this._error = bind(this._error, this);
|
45 | this._getValLength = bind(this._getValLength, this);
|
46 | this._wrap = bind(this._wrap, this);
|
47 | this._isInvalidKey = bind(this._isInvalidKey, this);
|
48 | this._check = bind(this._check, this);
|
49 | this._checkData = bind(this._checkData, this);
|
50 | this.close = bind(this.close, this);
|
51 | this.flushAll = bind(this.flushAll, this);
|
52 | this.getStats = bind(this.getStats, this);
|
53 | this.keys = bind(this.keys, this);
|
54 | this.getTtl = bind(this.getTtl, this);
|
55 | this.ttl = bind(this.ttl, this);
|
56 | this.del = bind(this.del, this);
|
57 | this.set = bind(this.set, this);
|
58 | this.mget = bind(this.mget, this);
|
59 | this.get = bind(this.get, this);
|
60 | this._initErrors();
|
61 | this.data = {};
|
62 | this.options = _assignIn({
|
63 | forceString: false,
|
64 | objectValueSize: 80,
|
65 | arrayValueSize: 40,
|
66 | stdTTL: 0,
|
67 | checkperiod: 600,
|
68 | useClones: true,
|
69 | errorOnMissing: false,
|
70 | deleteOnExpire: true
|
71 | }, this.options);
|
72 | this.stats = {
|
73 | hits: 0,
|
74 | misses: 0,
|
75 | keys: 0,
|
76 | ksize: 0,
|
77 | vsize: 0
|
78 | };
|
79 | this.validKeyTypes = ["string", "number"];
|
80 | this._checkData();
|
81 | return;
|
82 | }
|
83 |
|
84 | NodeCache.prototype.get = function(key, cb, errorOnMissing) {
|
85 | var _err, _ret, err;
|
86 | if (typeof cb === "boolean" && arguments.length === 2) {
|
87 | errorOnMissing = cb;
|
88 | cb = void 0;
|
89 | }
|
90 | if ((err = this._isInvalidKey(key)) != null) {
|
91 | if (cb != null) {
|
92 | cb(err);
|
93 | return;
|
94 | } else {
|
95 | throw err;
|
96 | }
|
97 | }
|
98 | if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
99 | this.stats.hits++;
|
100 | _ret = this._unwrap(this.data[key]);
|
101 | if (cb != null) {
|
102 | cb(null, _ret);
|
103 | }
|
104 | return _ret;
|
105 | } else {
|
106 | this.stats.misses++;
|
107 | if (this.options.errorOnMissing || errorOnMissing) {
|
108 | _err = this._error("ENOTFOUND", {
|
109 | key: key
|
110 | }, cb);
|
111 | if (_err != null) {
|
112 | throw _err;
|
113 | }
|
114 | return;
|
115 | } else {
|
116 | if (cb != null) {
|
117 | cb(null, void 0);
|
118 | }
|
119 | }
|
120 | return void 0;
|
121 | }
|
122 | };
|
123 |
|
124 | NodeCache.prototype.mget = function(keys, cb) {
|
125 | var _err, err, i, key, len, oRet;
|
126 | if (!_isArray(keys)) {
|
127 | _err = this._error("EKEYSTYPE");
|
128 | if (cb != null) {
|
129 | cb(_err);
|
130 | }
|
131 | return _err;
|
132 | }
|
133 | oRet = {};
|
134 | for (i = 0, len = keys.length; i < len; i++) {
|
135 | key = keys[i];
|
136 | if ((err = this._isInvalidKey(key)) != null) {
|
137 | if (cb != null) {
|
138 | cb(err);
|
139 | return;
|
140 | } else {
|
141 | throw err;
|
142 | }
|
143 | }
|
144 | if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
145 | this.stats.hits++;
|
146 | oRet[key] = this._unwrap(this.data[key]);
|
147 | } else {
|
148 | this.stats.misses++;
|
149 | }
|
150 | }
|
151 | if (cb != null) {
|
152 | cb(null, oRet);
|
153 | }
|
154 | return oRet;
|
155 | };
|
156 |
|
157 | NodeCache.prototype.set = function(key, value, ttl, cb) {
|
158 | var err, existent;
|
159 | if (this.options.forceString && !_isString(value)) {
|
160 | value = JSON.stringify(value);
|
161 | }
|
162 | if (arguments.length === 3 && _isFunction(ttl)) {
|
163 | cb = ttl;
|
164 | ttl = this.options.stdTTL;
|
165 | }
|
166 | if ((err = this._isInvalidKey(key)) != null) {
|
167 | if (cb != null) {
|
168 | cb(err);
|
169 | return;
|
170 | } else {
|
171 | throw err;
|
172 | }
|
173 | }
|
174 | existent = false;
|
175 | if (this.data[key]) {
|
176 | existent = true;
|
177 | this.stats.vsize -= this._getValLength(this._unwrap(this.data[key], false));
|
178 | }
|
179 | this.data[key] = this._wrap(value, ttl);
|
180 | this.stats.vsize += this._getValLength(value);
|
181 | if (!existent) {
|
182 | this.stats.ksize += this._getKeyLength(key);
|
183 | this.stats.keys++;
|
184 | }
|
185 | this.emit("set", key, value);
|
186 | if (cb != null) {
|
187 | cb(null, true);
|
188 | }
|
189 | return true;
|
190 | };
|
191 |
|
192 | NodeCache.prototype.del = function(keys, cb) {
|
193 | var delCount, err, i, key, len, oldVal;
|
194 | if (!_isArray(keys)) {
|
195 | keys = [keys];
|
196 | }
|
197 | delCount = 0;
|
198 | for (i = 0, len = keys.length; i < len; i++) {
|
199 | key = keys[i];
|
200 | if ((err = this._isInvalidKey(key)) != null) {
|
201 | if (cb != null) {
|
202 | cb(err);
|
203 | return;
|
204 | } else {
|
205 | throw err;
|
206 | }
|
207 | }
|
208 | if (this.data[key] != null) {
|
209 | this.stats.vsize -= this._getValLength(this._unwrap(this.data[key], false));
|
210 | this.stats.ksize -= this._getKeyLength(key);
|
211 | this.stats.keys--;
|
212 | delCount++;
|
213 | oldVal = this.data[key];
|
214 | delete this.data[key];
|
215 | this.emit("del", key, oldVal.v);
|
216 | } else {
|
217 | this.stats.misses++;
|
218 | }
|
219 | }
|
220 | if (cb != null) {
|
221 | cb(null, delCount);
|
222 | }
|
223 | return delCount;
|
224 | };
|
225 |
|
226 | NodeCache.prototype.ttl = function() {
|
227 | var arg, args, cb, err, i, key, len, ttl;
|
228 | key = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
229 | for (i = 0, len = args.length; i < len; i++) {
|
230 | arg = args[i];
|
231 | switch (typeof arg) {
|
232 | case "number":
|
233 | ttl = arg;
|
234 | break;
|
235 | case "function":
|
236 | cb = arg;
|
237 | }
|
238 | }
|
239 | ttl || (ttl = this.options.stdTTL);
|
240 | if (!key) {
|
241 | if (cb != null) {
|
242 | cb(null, false);
|
243 | }
|
244 | return false;
|
245 | }
|
246 | if ((err = this._isInvalidKey(key)) != null) {
|
247 | if (cb != null) {
|
248 | cb(err);
|
249 | return;
|
250 | } else {
|
251 | throw err;
|
252 | }
|
253 | }
|
254 | if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
255 | if (ttl >= 0) {
|
256 | this.data[key] = this._wrap(this.data[key].v, ttl, false);
|
257 | } else {
|
258 | this.del(key);
|
259 | }
|
260 | if (cb != null) {
|
261 | cb(null, true);
|
262 | }
|
263 | return true;
|
264 | } else {
|
265 | if (cb != null) {
|
266 | cb(null, false);
|
267 | }
|
268 | return false;
|
269 | }
|
270 | };
|
271 |
|
272 | NodeCache.prototype.getTtl = function(key, cb) {
|
273 | var _ttl, err;
|
274 | if (!key) {
|
275 | if (cb != null) {
|
276 | cb(null, void 0);
|
277 | }
|
278 | return void 0;
|
279 | }
|
280 | if ((err = this._isInvalidKey(key)) != null) {
|
281 | if (cb != null) {
|
282 | cb(err);
|
283 | return;
|
284 | } else {
|
285 | throw err;
|
286 | }
|
287 | }
|
288 | if ((this.data[key] != null) && this._check(key, this.data[key])) {
|
289 | _ttl = this.data[key].t;
|
290 | if (cb != null) {
|
291 | cb(null, _ttl);
|
292 | }
|
293 | return _ttl;
|
294 | } else {
|
295 | if (cb != null) {
|
296 | cb(null, void 0);
|
297 | }
|
298 | return void 0;
|
299 | }
|
300 | };
|
301 |
|
302 | NodeCache.prototype.keys = function(cb) {
|
303 | var _keys;
|
304 | _keys = Object.keys(this.data);
|
305 | if (cb != null) {
|
306 | cb(null, _keys);
|
307 | }
|
308 | return _keys;
|
309 | };
|
310 |
|
311 | NodeCache.prototype.getStats = function() {
|
312 | return this.stats;
|
313 | };
|
314 |
|
315 | NodeCache.prototype.flushAll = function(_startPeriod) {
|
316 | if (_startPeriod == null) {
|
317 | _startPeriod = true;
|
318 | }
|
319 | this.data = {};
|
320 | this.stats = {
|
321 | hits: 0,
|
322 | misses: 0,
|
323 | keys: 0,
|
324 | ksize: 0,
|
325 | vsize: 0
|
326 | };
|
327 | this._killCheckPeriod();
|
328 | this._checkData(_startPeriod);
|
329 | this.emit("flush");
|
330 | };
|
331 |
|
332 | NodeCache.prototype.close = function() {
|
333 | this._killCheckPeriod();
|
334 | };
|
335 |
|
336 | NodeCache.prototype._checkData = function(startPeriod) {
|
337 | var key, ref, value;
|
338 | if (startPeriod == null) {
|
339 | startPeriod = true;
|
340 | }
|
341 | ref = this.data;
|
342 | for (key in ref) {
|
343 | value = ref[key];
|
344 | this._check(key, value);
|
345 | }
|
346 | if (startPeriod && this.options.checkperiod > 0) {
|
347 | this.checkTimeout = setTimeout(this._checkData, this.options.checkperiod * 1000, startPeriod);
|
348 | if (this.checkTimeout.unref != null) {
|
349 | this.checkTimeout.unref();
|
350 | }
|
351 | }
|
352 | };
|
353 |
|
354 | NodeCache.prototype._killCheckPeriod = function() {
|
355 | if (this.checkTimeout != null) {
|
356 | return clearTimeout(this.checkTimeout);
|
357 | }
|
358 | };
|
359 |
|
360 | NodeCache.prototype._check = function(key, data) {
|
361 | var _retval;
|
362 | _retval = true;
|
363 | if (data.t !== 0 && data.t < Date.now()) {
|
364 | if (this.options.deleteOnExpire) {
|
365 | _retval = false;
|
366 | this.del(key);
|
367 | }
|
368 | this.emit("expired", key, this._unwrap(data));
|
369 | }
|
370 | return _retval;
|
371 | };
|
372 |
|
373 | NodeCache.prototype._isInvalidKey = function(key) {
|
374 | var ref;
|
375 | if (ref = typeof key, indexOf.call(this.validKeyTypes, ref) < 0) {
|
376 | return this._error("EKEYTYPE", {
|
377 | type: typeof key
|
378 | });
|
379 | }
|
380 | };
|
381 |
|
382 | NodeCache.prototype._wrap = function(value, ttl, asClone) {
|
383 | var livetime, now, oReturn, ttlMultiplicator;
|
384 | if (asClone == null) {
|
385 | asClone = true;
|
386 | }
|
387 | if (!this.options.useClones) {
|
388 | asClone = false;
|
389 | }
|
390 | now = Date.now();
|
391 | livetime = 0;
|
392 | ttlMultiplicator = 1000;
|
393 | if (ttl === 0) {
|
394 | livetime = 0;
|
395 | } else if (ttl) {
|
396 | livetime = now + (ttl * ttlMultiplicator);
|
397 | } else {
|
398 | if (this.options.stdTTL === 0) {
|
399 | livetime = this.options.stdTTL;
|
400 | } else {
|
401 | livetime = now + (this.options.stdTTL * ttlMultiplicator);
|
402 | }
|
403 | }
|
404 | return oReturn = {
|
405 | t: livetime,
|
406 | v: asClone ? clone(value) : value
|
407 | };
|
408 | };
|
409 |
|
410 | NodeCache.prototype._unwrap = function(value, asClone) {
|
411 | if (asClone == null) {
|
412 | asClone = true;
|
413 | }
|
414 | if (!this.options.useClones) {
|
415 | asClone = false;
|
416 | }
|
417 | if (value.v != null) {
|
418 | if (asClone) {
|
419 | return clone(value.v);
|
420 | } else {
|
421 | return value.v;
|
422 | }
|
423 | }
|
424 | return null;
|
425 | };
|
426 |
|
427 | NodeCache.prototype._getKeyLength = function(key) {
|
428 | return key.length;
|
429 | };
|
430 |
|
431 | NodeCache.prototype._getValLength = function(value) {
|
432 | if (_isString(value)) {
|
433 | return value.length;
|
434 | } else if (this.options.forceString) {
|
435 | return JSON.stringify(value).length;
|
436 | } else if (_isArray(value)) {
|
437 | return this.options.arrayValueSize * value.length;
|
438 | } else if (_isNumber(value)) {
|
439 | return 8;
|
440 | } else if (_isObject(value)) {
|
441 | return this.options.objectValueSize * _size(value);
|
442 | } else {
|
443 | return 0;
|
444 | }
|
445 | };
|
446 |
|
447 | NodeCache.prototype._error = function(type, data, cb) {
|
448 | var error;
|
449 | if (data == null) {
|
450 | data = {};
|
451 | }
|
452 | error = new Error();
|
453 | error.name = type;
|
454 | error.errorcode = type;
|
455 | error.message = this.ERRORS[type] != null ? this.ERRORS[type](data) : "-";
|
456 | error.data = data;
|
457 | if (cb && _isFunction(cb)) {
|
458 | cb(error, null);
|
459 | } else {
|
460 | return error;
|
461 | }
|
462 | };
|
463 |
|
464 | NodeCache.prototype._initErrors = function() {
|
465 | var _errMsg, _errT, ref;
|
466 | this.ERRORS = {};
|
467 | ref = this._ERRORS;
|
468 | for (_errT in ref) {
|
469 | _errMsg = ref[_errT];
|
470 | this.ERRORS[_errT] = _template(_errMsg);
|
471 | }
|
472 | };
|
473 |
|
474 | NodeCache.prototype._ERRORS = {
|
475 | "ENOTFOUND": "Key `<%= key %>` not found",
|
476 | "EKEYTYPE": "The key argument has to be of type `string` or `number`. Found: `<%= type %>`",
|
477 | "EKEYSTYPE": "The keys argument has to be an array."
|
478 | };
|
479 |
|
480 | return NodeCache;
|
481 |
|
482 | })(EventEmitter);
|
483 |
|
484 | }).call(this);
|