PageRenderTime 47ms CodeModel.GetById 16ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/node_modules/mongoose/lib/utils.js

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