arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/utils.js
node_modules/mongoose/lib/utils.jsRaw
  1/*!
  2 * Module dependencies.
  3 */
  4
  5var ObjectId = require('./types/objectid');
  6var cloneRegExp = require('regexp-clone');
  7var sliced = require('sliced');
  8var mpath = require('mpath');
  9var ms = require('ms');
 10var MongooseBuffer;
 11var MongooseArray;
 12var Document;
 13
 14/*!
 15 * Produces a collection name from model `name`.
 16 *
 17 * @param {String} name a model name
 18 * @return {String} a collection name
 19 * @api private
 20 */
 21
 22exports.toCollectionName = function(name, options) {
 23  options = options || {};
 24  if (name === 'system.profile') {
 25    return name;
 26  }
 27  if (name === 'system.indexes') {
 28    return name;
 29  }
 30  if (options.pluralization === false) {
 31    return name;
 32  }
 33  return pluralize(name.toLowerCase());
 34};
 35
 36/**
 37 * Pluralization rules.
 38 *
 39 * These rules are applied while processing the argument to `toCollectionName`.
 40 *
 41 * @deprecated remove in 4.x gh-1350
 42 */
 43
 44exports.pluralization = [
 45  [/(m)an$/gi, '$1en'],
 46  [/(pe)rson$/gi, '$1ople'],
 47  [/(child)$/gi, '$1ren'],
 48  [/^(ox)$/gi, '$1en'],
 49  [/(ax|test)is$/gi, '$1es'],
 50  [/(octop|vir)us$/gi, '$1i'],
 51  [/(alias|status)$/gi, '$1es'],
 52  [/(bu)s$/gi, '$1ses'],
 53  [/(buffal|tomat|potat)o$/gi, '$1oes'],
 54  [/([ti])um$/gi, '$1a'],
 55  [/sis$/gi, 'ses'],
 56  [/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'],
 57  [/(hive)$/gi, '$1s'],
 58  [/([^aeiouy]|qu)y$/gi, '$1ies'],
 59  [/(x|ch|ss|sh)$/gi, '$1es'],
 60  [/(matr|vert|ind)ix|ex$/gi, '$1ices'],
 61  [/([m|l])ouse$/gi, '$1ice'],
 62  [/(kn|w|l)ife$/gi, '$1ives'],
 63  [/(quiz)$/gi, '$1zes'],
 64  [/s$/gi, 's'],
 65  [/([^a-z])$/, '$1'],
 66  [/$/gi, 's']
 67];
 68var rules = exports.pluralization;
 69
 70/**
 71 * Uncountable words.
 72 *
 73 * These words are applied while processing the argument to `toCollectionName`.
 74 * @api public
 75 */
 76
 77exports.uncountables = [
 78  'advice',
 79  'energy',
 80  'excretion',
 81  'digestion',
 82  'cooperation',
 83  'health',
 84  'justice',
 85  'labour',
 86  'machinery',
 87  'equipment',
 88  'information',
 89  'pollution',
 90  'sewage',
 91  'paper',
 92  'money',
 93  'species',
 94  'series',
 95  'rain',
 96  'rice',
 97  'fish',
 98  'sheep',
 99  'moose',
100  'deer',
101  'news',
102  'expertise',
103  'status',
104  'media'
105];
106var uncountables = exports.uncountables;
107
108/*!
109 * Pluralize function.
110 *
111 * @author TJ Holowaychuk (extracted from _ext.js_)
112 * @param {String} string to pluralize
113 * @api private
114 */
115
116function pluralize(str) {
117  var found;
118  if (!~uncountables.indexOf(str.toLowerCase())) {
119    found = rules.filter(function(rule) {
120      return str.match(rule[0]);
121    });
122    if (found[0]) {
123      return str.replace(found[0][0], found[0][1]);
124    }
125  }
126  return str;
127}
128
129/*!
130 * Determines if `a` and `b` are deep equal.
131 *
132 * Modified from node/lib/assert.js
133 *
134 * @param {any} a a value to compare to `b`
135 * @param {any} b a value to compare to `a`
136 * @return {Boolean}
137 * @api private
138 */
139
140exports.deepEqual = function deepEqual(a, b) {
141  if (a === b) {
142    return true;
143  }
144
145  if (a instanceof Date && b instanceof Date) {
146    return a.getTime() === b.getTime();
147  }
148
149  if (a instanceof ObjectId && b instanceof ObjectId) {
150    return a.toString() === b.toString();
151  }
152
153  if (a instanceof RegExp && b instanceof RegExp) {
154    return a.source === b.source &&
155        a.ignoreCase === b.ignoreCase &&
156        a.multiline === b.multiline &&
157        a.global === b.global;
158  }
159
160  if (typeof a !== 'object' && typeof b !== 'object') {
161    return a == b;
162  }
163
164  if (a === null || b === null || a === undefined || b === undefined) {
165    return false;
166  }
167
168  if (a.prototype !== b.prototype) {
169    return false;
170  }
171
172  // Handle MongooseNumbers
173  if (a instanceof Number && b instanceof Number) {
174    return a.valueOf() === b.valueOf();
175  }
176
177  if (Buffer.isBuffer(a)) {
178    return exports.buffer.areEqual(a, b);
179  }
180
181  if (isMongooseObject(a)) {
182    a = a.toObject();
183  }
184  if (isMongooseObject(b)) {
185    b = b.toObject();
186  }
187
188  try {
189    var ka = Object.keys(a),
190        kb = Object.keys(b),
191        key, i;
192  } catch (e) {
193    // happens when one is a string literal and the other isn't
194    return false;
195  }
196
197  // having the same number of owned properties (keys incorporates
198  // hasOwnProperty)
199  if (ka.length !== kb.length) {
200    return false;
201  }
202
203  // the same set of keys (although not necessarily the same order),
204  ka.sort();
205  kb.sort();
206
207  // ~~~cheap key test
208  for (i = ka.length - 1; i >= 0; i--) {
209    if (ka[i] !== kb[i]) {
210      return false;
211    }
212  }
213
214  // equivalent values for every corresponding key, and
215  // ~~~possibly expensive deep test
216  for (i = ka.length - 1; i >= 0; i--) {
217    key = ka[i];
218    if (!deepEqual(a[key], b[key])) {
219      return false;
220    }
221  }
222
223  return true;
224};
225
226/*!
227 * Object clone with Mongoose natives support.
228 *
229 * If options.minimize is true, creates a minimal data object. Empty objects and undefined values will not be cloned. This makes the data payload sent to MongoDB as small as possible.
230 *
231 * Functions are never cloned.
232 *
233 * @param {Object} obj the object to clone
234 * @param {Object} options
235 * @return {Object} the cloned object
236 * @api private
237 */
238
239exports.clone = function clone(obj, options) {
240  if (obj === undefined || obj === null) {
241    return obj;
242  }
243
244  if (Array.isArray(obj)) {
245    return cloneArray(obj, options);
246  }
247
248  if (isMongooseObject(obj)) {
249    if (options && options.json && typeof obj.toJSON === 'function') {
250      return obj.toJSON(options);
251    }
252    return obj.toObject(options);
253  }
254
255  if (obj.constructor) {
256    switch (exports.getFunctionName(obj.constructor)) {
257      case 'Object':
258        return cloneObject(obj, options);
259      case 'Date':
260        return new obj.constructor(+obj);
261      case 'RegExp':
262        return cloneRegExp(obj);
263      default:
264        // ignore
265        break;
266    }
267  }
268
269  if (obj instanceof ObjectId) {
270    return new ObjectId(obj.id);
271  }
272
273  if (!obj.constructor && exports.isObject(obj)) {
274    // object created with Object.create(null)
275    return cloneObject(obj, options);
276  }
277
278  if (obj.valueOf) {
279    return obj.valueOf();
280  }
281};
282var clone = exports.clone;
283
284/*!
285 * ignore
286 */
287
288function cloneObject(obj, options) {
289  var retainKeyOrder = options && options.retainKeyOrder,
290      minimize = options && options.minimize,
291      ret = {},
292      hasKeys,
293      keys,
294      val,
295      k,
296      i;
297
298  if (retainKeyOrder) {
299    for (k in obj) {
300      val = clone(obj[k], options);
301
302      if (!minimize || (typeof val !== 'undefined')) {
303        hasKeys || (hasKeys = true);
304        ret[k] = val;
305      }
306    }
307  } else {
308    // faster
309
310    keys = Object.keys(obj);
311    i = keys.length;
312
313    while (i--) {
314      k = keys[i];
315      val = clone(obj[k], options);
316
317      if (!minimize || (typeof val !== 'undefined')) {
318        if (!hasKeys) {
319          hasKeys = true;
320        }
321        ret[k] = val;
322      }
323    }
324  }
325
326  return minimize
327      ? hasKeys && ret
328      : ret;
329}
330
331function cloneArray(arr, options) {
332  var ret = [];
333  for (var i = 0, l = arr.length; i < l; i++) {
334    ret.push(clone(arr[i], options));
335  }
336  return ret;
337}
338
339/*!
340 * Shallow copies defaults into options.
341 *
342 * @param {Object} defaults
343 * @param {Object} options
344 * @return {Object} the merged object
345 * @api private
346 */
347
348exports.options = function(defaults, options) {
349  var keys = Object.keys(defaults),
350      i = keys.length,
351      k;
352
353  options = options || {};
354
355  while (i--) {
356    k = keys[i];
357    if (!(k in options)) {
358      options[k] = defaults[k];
359    }
360  }
361
362  return options;
363};
364
365/*!
366 * Generates a random string
367 *
368 * @api private
369 */
370
371exports.random = function() {
372  return Math.random().toString().substr(3);
373};
374
375/*!
376 * Merges `from` into `to` without overwriting existing properties.
377 *
378 * @param {Object} to
379 * @param {Object} from
380 * @api private
381 */
382
383exports.merge = function merge(to, from) {
384  var keys = Object.keys(from),
385      i = keys.length,
386      key;
387
388  while (i--) {
389    key = keys[i];
390    if (typeof to[key] === 'undefined') {
391      to[key] = from[key];
392    } else if (exports.isObject(from[key])) {
393      merge(to[key], from[key]);
394    }
395  }
396};
397
398/*!
399 * toString helper
400 */
401
402var toString = Object.prototype.toString;
403
404/*!
405 * Applies toObject recursively.
406 *
407 * @param {Document|Array|Object} obj
408 * @return {Object}
409 * @api private
410 */
411
412exports.toObject = function toObject(obj) {
413  Document || (Document = require('./document'));
414  var ret;
415
416  if (exports.isNullOrUndefined(obj)) {
417    return obj;
418  }
419
420  if (obj instanceof Document) {
421    return obj.toObject();
422  }
423
424  if (Array.isArray(obj)) {
425    ret = [];
426
427    for (var i = 0, len = obj.length; i < len; ++i) {
428      ret.push(toObject(obj[i]));
429    }
430
431    return ret;
432  }
433
434  if ((obj.constructor && exports.getFunctionName(obj.constructor) === 'Object') ||
435      (!obj.constructor && exports.isObject(obj))) {
436    ret = {};
437
438    for (var k in obj) {
439      ret[k] = toObject(obj[k]);
440    }
441
442    return ret;
443  }
444
445  return obj;
446};
447
448/*!
449 * Determines if `arg` is an object.
450 *
451 * @param {Object|Array|String|Function|RegExp|any} arg
452 * @api private
453 * @return {Boolean}
454 */
455
456exports.isObject = function(arg) {
457  if (Buffer.isBuffer(arg)) {
458    return true;
459  }
460  return toString.call(arg) === '[object Object]';
461};
462
463/*!
464 * A faster Array.prototype.slice.call(arguments) alternative
465 * @api private
466 */
467
468exports.args = sliced;
469
470/*!
471 * process.nextTick helper.
472 *
473 * Wraps `callback` in a try/catch + nextTick.
474 *
475 * node-mongodb-native has a habit of state corruption when an error is immediately thrown from within a collection callback.
476 *
477 * @param {Function} callback
478 * @api private
479 */
480
481exports.tick = function tick(callback) {
482  if (typeof callback !== 'function') {
483    return;
484  }
485  return function() {
486    try {
487      callback.apply(this, arguments);
488    } catch (err) {
489      // only nextTick on err to get out of
490      // the event loop and avoid state corruption.
491      process.nextTick(function() {
492        throw err;
493      });
494    }
495  };
496};
497
498/*!
499 * Returns if `v` is a mongoose object that has a `toObject()` method we can use.
500 *
501 * This is for compatibility with libs like Date.js which do foolish things to Natives.
502 *
503 * @param {any} v
504 * @api private
505 */
506
507exports.isMongooseObject = function(v) {
508  Document || (Document = require('./document'));
509  MongooseArray || (MongooseArray = require('./types').Array);
510  MongooseBuffer || (MongooseBuffer = require('./types').Buffer);
511
512  return v instanceof Document ||
513      (v && v.isMongooseArray) ||
514      (v && v.isMongooseBuffer);
515};
516var isMongooseObject = exports.isMongooseObject;
517
518/*!
519 * Converts `expires` options of index objects to `expiresAfterSeconds` options for MongoDB.
520 *
521 * @param {Object} object
522 * @api private
523 */
524
525exports.expires = function expires(object) {
526  if (!(object && object.constructor.name === 'Object')) {
527    return;
528  }
529  if (!('expires' in object)) {
530    return;
531  }
532
533  var when;
534  if (typeof object.expires !== 'string') {
535    when = object.expires;
536  } else {
537    when = Math.round(ms(object.expires) / 1000);
538  }
539  object.expireAfterSeconds = when;
540  delete object.expires;
541};
542
543/*!
544 * Populate options constructor
545 */
546
547function PopulateOptions(path, select, match, options, model, subPopulate) {
548  this.path = path;
549  this.match = match;
550  this.select = select;
551  this.options = options;
552  this.model = model;
553  if (typeof subPopulate === 'object') {
554    this.populate = subPopulate;
555  }
556  this._docs = {};
557}
558
559// make it compatible with utils.clone
560PopulateOptions.prototype.constructor = Object;
561
562// expose
563exports.PopulateOptions = PopulateOptions;
564
565/*!
566 * populate helper
567 */
568
569exports.populate = function populate(path, select, model, match, options, subPopulate) {
570  // The order of select/conditions args is opposite Model.find but
571  // necessary to keep backward compatibility (select could be
572  // an array, string, or object literal).
573
574  // might have passed an object specifying all arguments
575  if (arguments.length === 1) {
576    if (path instanceof PopulateOptions) {
577      return [path];
578    }
579
580    if (Array.isArray(path)) {
581      return path.map(function(o) {
582        return exports.populate(o)[0];
583      });
584    }
585
586    if (exports.isObject(path)) {
587      match = path.match;
588      options = path.options;
589      select = path.select;
590      model = path.model;
591      subPopulate = path.populate;
592      path = path.path;
593    }
594  } else if (typeof model !== 'string' && typeof model !== 'function') {
595    options = match;
596    match = model;
597    model = undefined;
598  }
599
600  if (typeof path !== 'string') {
601    throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`');
602  }
603
604  if (typeof subPopulate === 'object') {
605    subPopulate = exports.populate(subPopulate);
606  }
607
608  var ret = [];
609  var paths = path.split(' ');
610  options = exports.clone(options, { retainKeyOrder: true });
611  for (var i = 0; i < paths.length; ++i) {
612    ret.push(new PopulateOptions(paths[i], select, match, options, model, subPopulate));
613  }
614
615  return ret;
616};
617
618/*!
619 * Return the value of `obj` at the given `path`.
620 *
621 * @param {String} path
622 * @param {Object} obj
623 */
624
625exports.getValue = function(path, obj, map) {
626  return mpath.get(path, obj, '_doc', map);
627};
628
629/*!
630 * Sets the value of `obj` at the given `path`.
631 *
632 * @param {String} path
633 * @param {Anything} val
634 * @param {Object} obj
635 */
636
637exports.setValue = function(path, val, obj, map) {
638  mpath.set(path, val, obj, '_doc', map);
639};
640
641/*!
642 * Returns an array of values from object `o`.
643 *
644 * @param {Object} o
645 * @return {Array}
646 * @private
647 */
648
649exports.object = {};
650exports.object.vals = function vals(o) {
651  var keys = Object.keys(o),
652      i = keys.length,
653      ret = [];
654
655  while (i--) {
656    ret.push(o[keys[i]]);
657  }
658
659  return ret;
660};
661
662/*!
663 * @see exports.options
664 */
665
666exports.object.shallowCopy = exports.options;
667
668/*!
669 * Safer helper for hasOwnProperty checks
670 *
671 * @param {Object} obj
672 * @param {String} prop
673 */
674
675var hop = Object.prototype.hasOwnProperty;
676exports.object.hasOwnProperty = function(obj, prop) {
677  return hop.call(obj, prop);
678};
679
680/*!
681 * Determine if `val` is null or undefined
682 *
683 * @return {Boolean}
684 */
685
686exports.isNullOrUndefined = function(val) {
687  return val === null || val === undefined;
688};
689
690/*!
691 * ignore
692 */
693
694exports.array = {};
695
696/*!
697 * Flattens an array.
698 *
699 * [ 1, [ 2, 3, [4] ]] -> [1,2,3,4]
700 *
701 * @param {Array} arr
702 * @param {Function} [filter] If passed, will be invoked with each item in the array. If `filter` returns a falsey value, the item will not be included in the results.
703 * @return {Array}
704 * @private
705 */
706
707exports.array.flatten = function flatten(arr, filter, ret) {
708  ret || (ret = []);
709
710  arr.forEach(function(item) {
711    if (Array.isArray(item)) {
712      flatten(item, filter, ret);
713    } else {
714      if (!filter || filter(item)) {
715        ret.push(item);
716      }
717    }
718  });
719
720  return ret;
721};
722
723/*!
724 * Removes duplicate values from an array
725 *
726 * [1, 2, 3, 3, 5] => [1, 2, 3, 5]
727 * [ ObjectId("550988ba0c19d57f697dc45e"), ObjectId("550988ba0c19d57f697dc45e") ]
728 *    => [ObjectId("550988ba0c19d57f697dc45e")]
729 *
730 * @param {Array} arr
731 * @return {Array}
732 * @private
733 */
734
735exports.array.unique = function(arr) {
736  var primitives = {};
737  var ids = {};
738  var ret = [];
739  var length = arr.length;
740  for (var i = 0; i < length; ++i) {
741    if (typeof arr[i] === 'number' || typeof arr[i] === 'string') {
742      if (primitives[arr[i]]) {
743        continue;
744      }
745      ret.push(arr[i]);
746      primitives[arr[i]] = true;
747    } else if (arr[i] instanceof ObjectId) {
748      if (ids[arr[i].toString()]) {
749        continue;
750      }
751      ret.push(arr[i]);
752      ids[arr[i].toString()] = true;
753    } else {
754      ret.push(arr[i]);
755    }
756  }
757
758  return ret;
759};
760
761/*!
762 * Determines if two buffers are equal.
763 *
764 * @param {Buffer} a
765 * @param {Object} b
766 */
767
768exports.buffer = {};
769exports.buffer.areEqual = function(a, b) {
770  if (!Buffer.isBuffer(a)) {
771    return false;
772  }
773  if (!Buffer.isBuffer(b)) {
774    return false;
775  }
776  if (a.length !== b.length) {
777    return false;
778  }
779  for (var i = 0, len = a.length; i < len; ++i) {
780    if (a[i] !== b[i]) {
781      return false;
782    }
783  }
784  return true;
785};
786
787exports.getFunctionName = function(fn) {
788  if (fn.name) {
789    return fn.name;
790  }
791  return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
792};
793
794exports.decorate = function(destination, source) {
795  for (var key in source) {
796    destination[key] = source[key];
797  }
798};
799
800/**
801 * merges to with a copy of from
802 *
803 * @param {Object} to
804 * @param {Object} fromObj
805 * @api private
806 */
807
808exports.mergeClone = function(to, fromObj) {
809  var keys = Object.keys(fromObj),
810      i = keys.length,
811      key;
812
813  while (i--) {
814    key = keys[i];
815    if (typeof to[key] === 'undefined') {
816      // make sure to retain key order here because of a bug handling the $each
817      // operator in mongodb 2.4.4
818      to[key] = exports.clone(fromObj[key], {retainKeyOrder: 1});
819    } else {
820      if (exports.isObject(fromObj[key])) {
821        var obj = fromObj[key];
822        if (isMongooseObject(fromObj[key]) && !fromObj[key].isMongooseBuffer) {
823          obj = obj.toObject({ virtuals: false });
824        }
825        exports.mergeClone(to[key], obj);
826      } else {
827        // make sure to retain key order here because of a bug handling the
828        // $each operator in mongodb 2.4.4
829        to[key] = exports.clone(fromObj[key], {retainKeyOrder: 1});
830      }
831    }
832  }
833};
834
835/**
836 * Executes a function on each element of an array (like _.each)
837 *
838 * @param {Array} arr
839 * @param {Function} fn
840 * @api private
841 */
842
843exports.each = function(arr, fn) {
844  for (var i = 0; i < arr.length; ++i) {
845    fn(arr[i]);
846  }
847};