Coverage

73%
305
223
82

/Users/xiaoyi/Projects/apollojs/server.js

73%
305
223
82
LineHitsSource
1
2
3
4
5
6
7
8
9
10
111if (!global.$apollo) {
12
13
14
15/**
16 * Extend an object with another object
17 * @param {Object} obj object to be extended
18 * @param {Object} ext extension object
19 * @param {bool} override Overwrite existing properties in obj
20 * @param {bool} deep Doing an deep extend (perform extend on every object property)
21 * @return {Object} reference to obj
22 */
231function $extend(obj, ext, override, deep) {
240 var key;
250 if (override) {
260 if (deep)
270 _overrideDeepExtend(obj, ext);
28 else
290 for (key in ext)
300 obj[key] = ext[key];
31 } else {
320 if (deep)
330 _deepExtend(obj, ext);
34 else
350 for (key in ext)
360 if (!(key in obj))
370 obj[key] = ext[key];
38 }
390 return obj;
40}
41
421function _overrideDeepExtend(obj, ext) {
430 for (var key in ext)
440 if (Object.isObjectStrict(obj[key]) && Object.isObjectStrict(ext[key]))
450 _overrideDeepExtend(obj[key], ext[key]);
46 else
470 obj[key] = ext[key];
48}
49
501function _deepExtend(obj, ext) {
510 for (var key in ext)
520 if (Object.isObjectStrict(obj[key]) && Object.isObjectStrict(ext[key]))
530 _deepExtend(obj[key], ext[key]);
540 else if (!(key in obj))
550 obj[key] = ext[key];
56}
57
58/**
59 * Define properties of an Object, Which usually used to extend prototype
60 * of an object, as it will set properties as non-enumerable, and will
61 * turn setValue(value) and getValue() functions to setter and getters.
62 * Note: You should only use $define or Object.defineProperty on prototype,
63 * or on a class' itself (to define static methods), instead of on instances
64 * which could lead to severe performance issue.
65 * @param {Object} object target object
66 * @param {Object} prototype extension object
67 * @param {bool} preserve preserve existing property
68 * @return {Object} reference to object
69 */
701function $define(object, prototype, preserve) {
7111 Object.getOwnPropertyNames(prototype).forEach(function(key) {
7251 if (preserve && (key in object))
732 return;
7449 var desc = Object.getOwnPropertyDescriptor(prototype, key);
7549 if ('value' in desc)
7646 desc.writable = true;
7749 delete desc.enumerable;
7849 delete desc.configurable;
7949 Object.defineProperty(object, key, desc);
80 });
8111 return object;
82}
83
84/**
85 * Declare a Class.
86 * @param {Function} fn constructor of the Class
87 * @param {Object} prototype prototype of Class
88 * @return {Function} reference to constructor
89 */
901function $declare(fn, prototype) {
910 fn.prototype.constructor = fn;
920 $define(fn.prototype, prototype);
930 return fn;
94}
95
96/**
97 * Inherit another Class to current Class
98 * @param {Function} fn constructor of the Class
99 * @param {Function} parent parent Class
100 * @param {Object} prototype prototype of Class
101 * @return {Function} reference to constructor
102 */
103
104
1051function $inherit(fn, parent, prototype) {
1060 fn.prototype = {
107 constructor: fn,
108 __proto__: parent.prototype
109 };
1100 if (prototype)
1110 $define(fn.prototype, prototype);
1120 return fn;
113}
114
115
116
117/**
118 * Adding enumerations to a Class (both static and prototype).
119 * @param {Function} fn constructor of the Class
120 * @param {Object} values object holding all enumerates want to define
121 * @return {Function} reference to constructor
122 */
1231function $defenum(fn, values) {
1240 $define(fn, values);
1250 $define(fn.prototype, values);
1260 return fn;
127}
128
129/**
130 * Format a string with given pattern.
131 * @param {string} str pattern
132 * @return {string} formatted string
133 */
134
135
1361var $format = require('util').format;
137
138
139
140/**
141 * Making an Error instance with given format and parameters.
142 * Note: this is a helper function works like util.format(),
143 * apart from it returns an Error object instead of string.
144 * @return {Error} generated Error instance
145 */
1461function $error() {
1471 return new Error($format.apply(null, arguments));
148}
149
150
151/**
152 * Generate a deep copy of an Object with its primitive typed
153 * fields (exclude functions).
154 * @param {mixed} obj source object
155 * @return {mixed} cloned object
156 */
1571function $valueCopy(obj) {
1580 var res;
1590 if (Array.isArray(obj)) {
1600 res = obj.slice(0);
1610 for (var i = 0; i < res.length; i++)
1620 if (Object.isObject(res[i]))
1630 res[i] = $valueCopy(res[i]);
1640 } else if (Object.isObjectStrict(obj)) {
1650 res = {};
1660 for (var key in obj)
1670 res[key] = $valueCopy(obj[key]);
1680 } else if (Function.isFunction(obj)) {
1690 return undefined;
170 } else {
1710 return obj;
172 }
1730 return res;
174}
175
176/**
177 * Generates a copy of an Object.
178 * @param {Mixed} org source object
179 * @param {bool} deep perform a deep clone
180 * @return {Mixed} cloned object
181 */
1821function $clone(obj, deep) {
1830 var res;
1840 var _deep = deep === true || deep - 1;
1850 if (Array.isArray(obj)) {
1860 res = obj.slice(0);
1870 if (deep)
1880 for (var i = 0; i < res.length; i++)
1890 if (Object.isObject(res[i]))
1900 res[i] = $clone(res[i], _deep);
1910 } else if (Object.isObjectStrict(obj)) {
1920 res = {};
1930 for (var key in obj)
1940 res[key] = obj[key];
1950 if (deep)
1960 for (var key in obj)
1970 if (Object.isObject(res[key]))
1980 res[key] = $clone(res[key], _deep);
199 } else {
2000 return obj;
201 }
2020 return res;
203}
204
205/**
206 * Return default value of an undefined variable.
207 * @param {Mixed} val value
208 * @param {Mixed} def default value
209 * @return {Mixed}
210 */
2111function $default(val, def) {
2125 return val === undefined ? def : val;
213}
214
215/**
216 * Wrap an object with given Class.
217 * Note: it will call Class.__wrap method to do custom wrapping.
218 * @param {Object} obj object to be wrapped
219 * @param {Function} Type wrapping Class
220 * @return {Object} wrapped object
221 */
222
223
2241function $wrap(obj, Type) {
2252 obj.__proto__ = Type.prototype;
2262 if (Type.__wrap)
2272 Type.__wrap(obj);
2282 return obj;
229}
230
231/**
232 * Removing prototype chain from a given object.
233 * @param {Object} object object to be stripped
234 * @return {Object} object stripped
235 */
2361function $strip(object) {
2371 object.__proto__ = Object.prototype;
2381 return object;
239}
240
241/**
242 * Use Object.prototype.toString to determine an element's type
243 * This method provide more stricter strategy on type detection,
244 * can be worked with typeof.
245 * @param {Mixed} obj Variable
246 * @return {String} type of the variable, like typeof,
247 * but with better precision.
248 */
2491function $typeof(obj) {
25016 var type = Object.prototype.toString.call(obj);
25116 return type.substring(8, type.length - 1).toLowerCase();
252}
253
254
255
2561$define(global, {
257 $extend: $extend,
258 $define: $define,
259 $declare: $declare,
260 $inherit: $inherit,
261 $defenum: $defenum,
262 $format: $format,
263 $error: $error,
264 $valueCopy: $valueCopy,
265 $clone: $clone,
266 $default: $default,
267 $wrap: $wrap
268
269 ,
270 $apollo: require('./package').version,
271 $strip: $strip,
272 $typeof: $typeof
273
274});
275
2761$define(String.prototype, {
277 /**
278 * Repeat current string for given times.
279 * @param {int} times Times to repeat
280 * @return {string} result
281 */
282 repeat: function(times) {
2838 var res = '';
2848 for (var i = 0; i < times; i++)
28527 res += this;
2868 return res;
287 },
288 /**
289 * Padding this to given length with specified char from left.
290 * @param {char} ch padding char
291 * @param {int} length desired length
292 * @return {string} result
293 */
294 paddingLeft: function(ch, length) {
2953 if (this.length < length)
2962 return ch.repeat(length - this.length) + this;
2971 return this;
298 },
299 /**
300 * Padding this to given length with specified char from right.
301 * @param {char} ch padding char
302 * @param {int} length desired length
303 * @return {string} result
304 */
305 paddingRight: function(ch, length) {
3063 if (this.length < length)
3072 return this + ch.repeat(length - this.length);
3081 return this;
309 },
310 /**
311 * Tests if this string starts with the given one.
312 * @param {string} str string to test with
313 * @param {number} pos optional, position to start compare, defaults
314 * to 0
315 * @return {bool} result
316 */
317 startsWith: function(str, pos) {
31810 if (str === null || str === undefined || str.length === 0)
3196 return true;
3204 return this.substr(pos || 0, str.length) === str;
321 },
322 /**
323 * Tests if this string ends with the given one.
324 * @param {string} str string to test with
325 * @param {number} len optional, pretend this string is of given length,
326 * defaults to actual length
327 * @return {bool} result
328 */
329 endsWith: function(str, len) {
33010 if (str === null || str === undefined || str.length === 0)
3316 return true;
3324 return this.substr((len || this.length) - str.length, str.length) === str;
333 },
334 /**
335 * Return a string in it's title form.
336 * @return {string} string in title case
337 * Note: if a word containing upper case, nothing
338 * will be done.
339 */
340 toTitleCase: function() {
34111 return this.replace(/\b([a-z])(['a-z]*)\b/g, function(all, letter, rest) {
34216 return letter.toUpperCase() + rest;
343 });
344 },
345 /**
346 * Trim whitespaces at the begining of the string
347 * @return {string} trimmed string
348 */
349 trimLeft: function() {
3500 return this.replace(/^\s+/, '');
351 },
352 /**
353 * Trim whitespaces at the ending of the string
354 * @return {string} trimmed string
355 */
356 trimRight: function() {
3570 return this.replace(/\s+$/, '');
358 }
359}, true);
360
3611$define(Number.prototype, {
362 /**
363 * Clamp current value to the given range [lb, ub]
364 * @param {number} lb lower bound
365 * @param {number} ub upper bound
366 * @return {number} result
367 */
368 clamp: function(lb, ub) {
3694 var rtn = Number(this);
3704 if (lb !== undefined && rtn < lb)
3711 rtn = lb;
3724 if (ub !== undefined && rtn > ub)
3731 rtn = ub;
3744 return rtn;
375 },
376 /**
377 * Shortcut to Math.floor(this)
378 * @return {number} Math.floor(this)
379 */
380 floor: function() {
3812 return Math.floor(this);
382 },
383 /**
384 * Shortcut to Math.ceil(this)
385 * @return {number} Math.ceil(this)
386 */
387 ceil: function() {
3882 return Math.ceil(this);
389 },
390 /**
391 * Shortcut to Math.round(this) with additional parameters
392 * @param {number} decimals number of decimal digits to round up to
393 * @return {number} rounded number
394 */
395 round: function(decimals) {
3964 if (decimals) {
3972 var unit = Math.pow(10, decimals);
3982 return Math.round(this * unit) / unit;
399 }
4002 return Math.round(this);
401 },
402 /**
403 * Get the thousands separated number
404 * @param {number} decimals number of decimal digits to remain
405 * @param {string} separator separator
406 * @return {string} separated number
407 */
408 toGroup: function(decimals, separator) {
409
41012 decimals = decimals || 0;
411
41212 if (this > -1000 && this < 1000)
4133 return this.toFixed(decimals);
414
4159 separator = separator || ',';
416
4179 var sign = this < 0 ? '-' : '';
4189 var tmp = Math.abs(this).toFixed(decimals);
419
4209 var intPart, decimalPart;
4219 if (decimals > 0) {
4222 intPart = tmp.substr(0, tmp.length - decimals - 1);
4232 decimalPart = tmp.substr(tmp.length - decimals - 1);
424 } else {
4257 intPart = tmp;
4267 decimalPart = '';
427 }
428
4299 var res = '';
4309 for (var pos = 0, len = intPart.length % 3 || 3;
431 pos < intPart.length; pos += len, len = 3) {
43227 if (res !== '')
43318 res += separator;
43427 res += intPart.substr(pos, len);
435 }
4369 return sign + res + decimalPart;
437
438 }
439});
440
4411$define(Array.prototype, {
442 /**
443 * get minimum value in this array
444 * @return {Mixed} minimal value
445 */
446 min: function() {
4473 var res = this[0];
4483 for (var i = 1; i < this.length; i++)
4495 if (this[i] < res)
4501 res = this[i];
4513 return res;
452 },
453 /**
454 * get maximum value in this array
455 * @return {Mixed} maximum value
456 */
457 max: function() {
4583 var res = this[0];
4593 for (var i = 1; i < this.length; i++)
4605 if (this[i] > res)
4614 res = this[i];
4623 return res;
463 },
464 /**
465 * Push a value iif it's not in this array, and return value's index.
466 * @param {Mixed} val new value
467 * @return {int} index of the value
468 * Note: This only works with primitive typed elements, which can be found
469 * with Array#indexOf().
470 */
471 add: function(val) {
4723 var index = this.indexOf(val);
4733 if (index === -1)
4742 return this.push(val) - 1;
4751 return index;
476 },
477 /**
478 * Find a value in the array and remove it.
479 * @param {Mixed} val value to remove
480 * @return {Array} this
481 * Note: This only works with primitive typed elements, which can be found
482 * with Array#indexOf().
483 */
484 remove: function(val) {
4854 var index = this.indexOf(val);
4864 if (index > -1) {
487 // Shift copy elements instead of Array#splice() for better performance.
488 // http://jsperf.com/fast-array-splice/18
4893 while (++index < this.length)
4904 this[index - 1] = this[index];
4913 this.pop();
492 }
4934 return this;
494 },
495 /**
496 * Rotate this array (n->0, n+1->1, ...)
497 * @param {int} n the offset
498 * @return {Array} this
499 */
500 rotate: function(n) {
50110 if (n < 0)
5022 n = n % this.length + this.length;
50310 n %= this.length;
50410 var middle = n;
50510 var next = n;
50610 var first = 0;
50710 while (first < this.length) {
50844 var t = this[first];
50944 this[first] = this[next];
51044 this[next] = t;
51144 first++;
51244 next++;
51373 if (next == this.length) next = middle;
51422 else if (first == middle) middle = next;
515 }
51610 return this;
517 },
518 /**
519 * get last element in this array
520 * Note: It's not a reference when returning a non-object!
521 * @return {Mixed} last element
522 */
523 get back() {
5242 return this[this.length - 1];
525 },
526 /**
527 * get first element in this array
528 * Note: It's not a reference when returning a non-object!
529 * @return {Mixed} first element
530 */
531 get front() {
5323 return this[0];
533 },
534 /**
535 * Flattern a array with sub arrays.
536 * @param {bool} deep if continue to flatten sub arrays
537 * @return {Array} flattened array.
538 */
539 flatten: function(deep) {
5406 var res = [];
5416 if (!deep)
5422 return res.concat.apply(res, this);
5434 for (var i = 0; i < this.length; i++)
5447 if (Array.isArray(this[i]))
5453 res.push.apply(res, this[i].flatten(true));
546 else
5474 res.push(this[i]);
5484 return res;
549 },
550 /**
551 * Return unique elements in the array
552 * @return {Array}
553 */
554 unique: function() {
5553 var res = [];
5563 var dict = {};
5573 for (var i = 0; i < this.length; ++i) {
5588 var key = this[i].toString();
5598 if (dict.hasOwnProperty(key))
5604 continue;
5614 dict[key] = true;
5624 res.push(this[i]);
563 }
5643 return res;
565 },
566 /**
567 * shuffle elements in the array in-place
568 * @return {Array}
569 */
570 shuffle: function() {
57110000 for (var n = this.length; n > 0; n--) {
57230000 var idx = Math.floor(n * Math.random());
57330000 if (idx != n - 1) {
57411598 var tmp = this[idx];
57511598 this[idx] = this[n - 1];
57611598 this[n - 1] = tmp;
577 }
578 }
57910000 return this;
580 }
581});
582
583/**
584 * Forward declaring prototype functions to Array's static
585 * methods.
586 */
5871if (Array.map === undefined)
5881 ['forEach', 'every', 'some', 'filter', 'map', 'reduce', 'reduceRight', 'slice']
589 .forEach(function(method) {
5908 var fn = Array.prototype[method];
5918 Object.defineProperty(Array, method, {
592 value: function(a, b, c) {
59325 return fn.call(a, b, c);
594 }
595 });
596 });
597
5981if (String.trim === undefined)
5991 ['trim', 'trimLeft', 'trimRight']
600 .forEach(function(method) {
6013 var fn = String.prototype[method];
6023 Object.defineProperty(String, method, {
603 value: function(a) {
6043 return fn.call(a);
605 }
606 });
607 });
608
6091$define(Object, {
610 /**
611 * Determine if an object is empty
612 * @param {Object} obj object to test
613 * @return {bool} object is empty
614 */
615 isEmpty: function(obj) {
6167 if (!obj)
6174 return true;
6183 for (var key in obj)
6191 return false;
6202 return true;
621 },
622 /**
623 * Get values of an object, like Object.keys().
624 * @param {Object} obj object to extract
625 * @return {Array} values in the object
626 */
627 values: function(obj) {
6285 return Object.keys(obj).map(function(k) {
6297 return obj[k];
630 });
631 },
632 /**
633 * Vague but fast isObject test
634 * Note: new String(), function, array, etc will return true
635 * @param {Mixed} obj object to test
636 * @return {bool} true if obj is an object and not null
637 */
638 isObject: function(obj) {
639 /**
640 * Known fastest way to test, the order of the test
641 * following: http://jsperf.com/typeof-vs-bool.
642 */
64314 return obj && typeof obj === 'object';
644 },
645 /**
646 * Strict isObject test, only pure Object will return true
647 * Note: only {} will return true
648 * @param {Mixed} obj object to test
649 * @return {bool} true if obj is strictly an object
650 */
651 isObjectStrict: function(obj) {
65220 return Object.prototype.toString.call(obj) === '[object Object]';
653 }
654
655 ,
656 /**
657 * project $object with projectiong, same behaviour with mongodb projection
658 * @param {Object} object target object
659 * @param {Object} projection An object mapping fields to values
660 * @param {Boolean} deep if true, go deep for sub objects
661 * @param {Boolean} keep if true, keep undefined field of this
662 * @return {Object} projected object
663 */
664 project: function(object, projection, deep, keep) {
66514 if (!Object.isObject(projection))
6660 return object;
66714 var res = {};
66814 Object.keys(projection).forEach(function(key) {
66915 var proj = projection[key];
67015 if (proj) {
67114 var obj = object[key];
67214 if (deep && Object.isObjectStrict(obj) && Object.isObjectStrict(proj)) {
6737 res[key] = Object.project(obj, projection[key], deep, keep);
674 } else {
6757 if (keep)
6761 res[key] = obj;
6776 else if (obj !== undefined)
6784 res[key] = obj;
679 }
680 }
681 });
68214 return res;
683 },
684 Transformer: function(mapping) {
6851 var expr = [];
6861 expr.push('exec=function (object) {');
6871 expr.push('var res = {};');
6881 (function loop(lhv, mapping) {
6892 Object.keys(mapping).forEach(function(key) {
6905 var source = mapping[key];
6916 if (/\W/.test(key)) key = '["' + key + '"]';
6924 else key = '.' + key;
693
694
6955 var target = lhv + key;
6965 if ($typeof(source) == 'object') {
6971 expr.push(target + ' = {};');
6981 return loop(target, source);
699 }
700
7014 if (true === source)
7021 source = 'object' + key;
7033 else if ($typeof(source) == 'string')
7042 source = 'object' + source;
7051 else if ($typeof(source) == 'function')
7061 source = '('+source.toString()+')(object)';
7074 expr.push(target + ' = ' + source + ';');
708 });
709 })('res', mapping);
7101 expr.push('return res;');
7111 expr.push('}');
7121 this.exec = eval(expr.join(''));
713 }
714
715});
716
7171$define(Function, {
718 /**
719 * Test if an object is a function
720 * @param {Mixed} obj object to test
721 * @return {bool} true if so
722 */
723 isFunction: function(obj) {
72411 return typeof obj === 'function';
725 }
726});
727
7281$define(Date, {
729 /**
730 * Cast a value to Date
731 * @param {Mixed} obj object to cast
732 * @return {Date} casted value
733 */
734 cast: function(obj) {
7350 if (obj instanceof Date)
7360 return obj;
7370 if (typeof obj === 'string')
7380 obj = Date.parse(obj);
7390 if (typeof obj === 'number') {
7400 if (isNaN(obj))
7410 return null;
7420 obj = new Date(obj);
7430 if (isNaN(obj.valueOf()))
7440 return null;
7450 return obj;
746 }
7470 return null;
748 },
749 /**
750 * Determine if an object is a Date
751 * @param {Object} object to test
752 * @return {bool} true iif it's a date.
753 */
754 isDate: function(obj) {
7550 obj = Date.cast(obj);
7560 if (obj)
7570 return obj >= 0 && obj < 2147483647000;
7580 return false;
759 }
760});
761
7621$define(Boolean, {
763 /**
764 * Cast a value to bool
765 * @param {Object} obj object to cast
766 * @return {bool} casted value
767 */
768 cast: function(obj) {
76918 if (obj === true || obj === false)
7702 return obj;
77116 if (typeof obj === 'string')
77210 return (/^(true|yes|ok|y|on)$/i).test(obj);
7736 return Boolean(obj);
774 }
775});
776
7771$define(RegExp, {
778 /**
779 * Escape a string to work within a regular expression
780 * @param {string} str string to escape
781 * @return {strign} escaped string
782 */
783 escape: function(str) {
78416 return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
785 }
786});
787
788
789
790
7911$define(global, {
792 $utils: {
793 encodeRLE: function(str) {
79413 return str.replace(/([^0-9a-z])\1+/g, function(all, cap) {
79510 var replacement = all.length.toString(36) + cap;
79610 if (replacement.length < all.length)
7973 return replacement;
7987 return all;
799 });
800 },
801 decodeRLE: function(str) {
80213 return str.replace(/([0-9a-z]+)([^0-9a-z])/g, function(all, count, cap) {
8034 return cap.repeat(parseInt(count, 36));
804 });
805 }
806 }
807});
808
809
810
811
812
813}
814
815
816
817