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