mongoose 4.12.6 → 4.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: node_js
2
2
  sudo: false
3
3
  node_js:
4
+ - "8"
4
5
  - "7"
5
6
  - "6"
6
7
  - "5"
package/History.md CHANGED
@@ -1,3 +1,36 @@
1
+ 4.13.3 / 2017-11-15
2
+ ===================
3
+ * chore: add node 8 to travis #5818 [superheri](https://github.com/superheri)
4
+ * fix(document): don't apply transforms to nested docs when updating already saved doc #5807
5
+
6
+ 4.13.2 / 2017-11-11
7
+ ===================
8
+ * feat(buffer): add support for subtype prop #5530
9
+
10
+ 4.13.1 / 2017-11-08
11
+ ===================
12
+ * fix: accept multiple paths or array of paths to depopulate #5798 #5797 [adamreisnz](https://github.com/adamreisnz)
13
+ * fix(document): pass default array as actual array rather than taking first element #5780
14
+ * fix(model): increment version when $set-ing it in a save() that requires a version bump #5779
15
+ * fix(query): don't explicitly project in discriminator key if user projected in parent path #5775 #5754
16
+ * fix(model): cast query option to geoNear() #5765
17
+ * fix(query): don't treat projection with just $slice as inclusive #5737
18
+ * fix(discriminator): defer applying embedded discriminator hooks until top-level model is compiled #5706
19
+ * docs(discriminator): add warning to always attach hooks before calling discriminator() #5706
20
+
21
+ 4.13.0 / 2017-11-02
22
+ ===================
23
+ * feat(aggregate): add $addFields helper #5740 [AyushG3112](https://github.com/AyushG3112)
24
+ * feat(connection): add connection-level bufferCommands #5720
25
+ * feat(connection): add createCollection() helper #5712
26
+ * feat(populate): support setting localField and foreignField to functions #5704 #5602
27
+ * feat(query): add multipleCastError option for aggregating cast errors when casting update #5609
28
+ * feat(populate): allow passing a function to virtual ref #5602
29
+ * feat(schema): add excludeIndexes option to optionally prevent collecting indexes from nested schemas #5575
30
+ * feat(model): report validation errors from `insertMany()` if using `ordered: false` and `rawResult: true` #5337
31
+ * feat(aggregate): add pre/post aggregate middleware #5251
32
+ * feat(schema): allow using `set` as a schema path #1939
33
+
1
34
  4.12.6 / 2017-11-01
2
35
  ===================
3
36
  * fix(schema): make clone() copy query helpers correctly #5752
package/lib/aggregate.js CHANGED
@@ -106,6 +106,41 @@ Aggregate.prototype.append = function() {
106
106
  return this;
107
107
  };
108
108
 
109
+ /**
110
+ * Appends a new $addFields operator to this aggregate pipeline.
111
+ * Requires MongoDB v3.4+ to work
112
+ *
113
+ * ####Examples:
114
+ *
115
+ * // adding new fields based on existing fields
116
+ * aggregate.addFields({
117
+ * newField: '$b.nested'
118
+ * , plusTen: { $add: ['$val', 10]}
119
+ * , sub: {
120
+ * name: '$a'
121
+ * }
122
+ * })
123
+ *
124
+ * // etc
125
+ * aggregate.addFields({ salary_k: { $divide: [ "$salary", 1000 ] } });
126
+ *
127
+ * @param {Object} arg field specification
128
+ * @see $addFields https://docs.mongodb.com/manual/reference/operator/aggregation/addFields/
129
+ * @return {Aggregate}
130
+ * @api public
131
+ */
132
+ Aggregate.prototype.addFields = function(arg) {
133
+ var fields = {};
134
+ if (typeof arg === 'object' && !util.isArray(arg)) {
135
+ Object.keys(arg).forEach(function(field) {
136
+ fields[field] = arg[field];
137
+ });
138
+ } else {
139
+ throw new Error('Invalid addFields() argument. Must be an object');
140
+ }
141
+ return this.append({$addFields: fields});
142
+ };
143
+
109
144
  /**
110
145
  * Appends a new $project operator to this aggregate pipeline.
111
146
  *
@@ -625,26 +660,27 @@ Aggregate.prototype.exec = function(callback) {
625
660
  throw new Error('Aggregate not bound to any Model');
626
661
  }
627
662
  var _this = this;
663
+ var model = this._model;
628
664
  var Promise = PromiseProvider.get();
629
- var options = utils.clone(this.options);
665
+ var options = utils.clone(this.options || {});
666
+ var pipeline = this._pipeline;
667
+ var collection = this._model.collection;
630
668
 
631
669
  if (options && options.cursor) {
632
670
  if (options.cursor.async) {
633
671
  delete options.cursor.async;
634
672
  return new Promise.ES6(function(resolve) {
635
- if (!_this._model.collection.buffer) {
673
+ if (!collection.buffer) {
636
674
  process.nextTick(function() {
637
- var cursor = _this._model.collection.
638
- aggregate(_this._pipeline, options || {});
675
+ var cursor = collection.aggregate(pipeline, options);
639
676
  decorateCursor(cursor);
640
677
  resolve(cursor);
641
678
  callback && callback(null, cursor);
642
679
  });
643
680
  return;
644
681
  }
645
- _this._model.collection.emitter.once('queue', function() {
646
- var cursor = _this._model.collection.
647
- aggregate(_this._pipeline, options || {});
682
+ collection.emitter.once('queue', function() {
683
+ var cursor = collection.aggregate(pipeline, options);
648
684
  decorateCursor(cursor);
649
685
  resolve(cursor);
650
686
  callback && callback(null, cursor);
@@ -654,14 +690,13 @@ Aggregate.prototype.exec = function(callback) {
654
690
  delete options.cursor.useMongooseAggCursor;
655
691
  return new AggregationCursor(this);
656
692
  }
657
- var cursor = this._model.collection.
658
- aggregate(this._pipeline, this.options || {});
693
+ var cursor = collection.aggregate(pipeline, options);
659
694
  decorateCursor(cursor);
660
695
  return cursor;
661
696
  }
662
697
 
663
698
  return new Promise.ES6(function(resolve, reject) {
664
- if (!_this._pipeline.length) {
699
+ if (!pipeline.length) {
665
700
  var err = new Error('Aggregate has empty pipeline');
666
701
  if (callback) {
667
702
  callback(err);
@@ -672,9 +707,20 @@ Aggregate.prototype.exec = function(callback) {
672
707
 
673
708
  prepareDiscriminatorPipeline(_this);
674
709
 
675
- _this._model
676
- .collection
677
- .aggregate(_this._pipeline, _this.options || {}, function(error, result) {
710
+ model.hooks.execPre('aggregate', _this, function(error) {
711
+ if (error) {
712
+ var _opts = { error: error };
713
+ return model.hooks.execPost('aggregate', _this, [null], _opts, function(error) {
714
+ if (callback) {
715
+ callback(error);
716
+ }
717
+ reject(error);
718
+ });
719
+ }
720
+
721
+ collection.aggregate(pipeline, options, function(error, result) {
722
+ var _opts = { error: error };
723
+ model.hooks.execPost('aggregate', _this, [result], _opts, function(error, result) {
678
724
  if (error) {
679
725
  if (callback) {
680
726
  callback(error);
@@ -688,6 +734,8 @@ Aggregate.prototype.exec = function(callback) {
688
734
  }
689
735
  resolve(result);
690
736
  });
737
+ });
738
+ });
691
739
  });
692
740
  };
693
741
 
@@ -758,6 +806,8 @@ function isOperator(obj) {
758
806
  * @param {Aggregate} aggregate Aggregate to prepare
759
807
  */
760
808
 
809
+ Aggregate._prepareDiscriminatorPipeline = prepareDiscriminatorPipeline;
810
+
761
811
  function prepareDiscriminatorPipeline(aggregate) {
762
812
  var schema = aggregate._model.schema,
763
813
  discriminatorMapping = schema && schema.discriminatorMapping;
@@ -781,12 +831,11 @@ function prepareDiscriminatorPipeline(aggregate) {
781
831
  } else {
782
832
  var match = {};
783
833
  match[discriminatorKey] = discriminatorValue;
784
- aggregate._pipeline = [{$match: match}].concat(originalPipeline);
834
+ aggregate._pipeline.unshift({ $match: match });
785
835
  }
786
836
  }
787
837
  }
788
838
 
789
-
790
839
  /*!
791
840
  * Exports
792
841
  */
package/lib/cast.js CHANGED
@@ -10,7 +10,7 @@ var utils = require('./utils');
10
10
  var ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
11
11
 
12
12
  /**
13
- * Handles internal casting for queries
13
+ * Handles internal casting for query filters.
14
14
  *
15
15
  * @param {Schema} schema
16
16
  * @param {Object} obj Object to cast
package/lib/connection.js CHANGED
@@ -368,6 +368,28 @@ Connection.prototype._openWithoutPromise = function() {
368
368
  });
369
369
  };
370
370
 
371
+ /**
372
+ * Helper for `createCollection()`. Will explicitly create the given collection
373
+ * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/)
374
+ * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose.
375
+ *
376
+ * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
377
+ *
378
+ * @param {string} collection The collection to delete
379
+ * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
380
+ * @param {Function} [callback]
381
+ * @return {Promise}
382
+ * @api public
383
+ */
384
+
385
+ Connection.prototype.createCollection = _wrapConnHelper(function createCollection(collection, options, cb) {
386
+ if (typeof options === 'function') {
387
+ cb = options;
388
+ options = {};
389
+ }
390
+ this.db.createCollection(collection, options, cb);
391
+ });
392
+
371
393
  /**
372
394
  * Helper for `dropCollection()`. Will delete the given collection, including
373
395
  * all documents and indexes.
@@ -403,8 +425,10 @@ function _wrapConnHelper(fn) {
403
425
  return function() {
404
426
  var _this = this;
405
427
  var Promise = PromiseProvider.get();
406
- var argsWithoutCb = Array.prototype.slice.call(arguments, 0, fn.length - 1);
407
- var cb = arguments[arguments.length - 1];
428
+ var cb = arguments.length > 0 ? arguments[arguments.length - 1] : null;
429
+ var argsWithoutCb = typeof cb === 'function' ?
430
+ Array.prototype.slice.call(arguments, 0, arguments.length - 1) :
431
+ Array.prototype.slice.call(arguments);
408
432
  var promise = new Promise.ES6(function(resolve, reject) {
409
433
  if (_this.readyState !== STATES.connected) {
410
434
  _this.on('open', function() {
@@ -782,6 +806,12 @@ Connection.prototype.openUri = function(uri, options, callback) {
782
806
  this.user = options.auth.user;
783
807
  this.pass = options.auth.password;
784
808
  }
809
+
810
+ if (options.bufferCommands != null) {
811
+ options.bufferMaxEntries = 0;
812
+ this.config.bufferCommands = options.bufferCommands;
813
+ delete options.bufferCommands;
814
+ }
785
815
  }
786
816
 
787
817
  this._connectionOptions = options;
@@ -14,6 +14,9 @@ var util = require('util');
14
14
  * in addition to several other mechanisms for loading documents from MongoDB
15
15
  * one at a time.
16
16
  *
17
+ * Creating an AggregationCursor executes the model's pre aggregate hooks,
18
+ * but **not** the model's post aggregate hooks.
19
+ *
17
20
  * Unless you're an advanced user, do **not** instantiate this class directly.
18
21
  * Use [`Aggregate#cursor()`](/docs/api.html#aggregate_Aggregate-cursor) instead.
19
22
  *
@@ -47,13 +50,17 @@ util.inherits(AggregationCursor, Readable);
47
50
 
48
51
  function _init(model, c, agg) {
49
52
  if (!model.collection.buffer) {
50
- c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
51
- c.emit('cursor', c.cursor);
52
- } else {
53
- model.collection.emitter.once('queue', function() {
53
+ model.hooks.execPre('aggregate', agg, function() {
54
54
  c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
55
55
  c.emit('cursor', c.cursor);
56
56
  });
57
+ } else {
58
+ model.collection.emitter.once('queue', function() {
59
+ model.hooks.execPre('aggregate', agg, function() {
60
+ c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
61
+ c.emit('cursor', c.cursor);
62
+ });
63
+ });
57
64
  }
58
65
  }
59
66
 
package/lib/document.js CHANGED
@@ -74,7 +74,7 @@ function Document(obj, fields, skipId, options) {
74
74
  if (this.$__original_set) {
75
75
  this.$__original_set(obj, undefined, true);
76
76
  } else {
77
- this.set(obj, undefined, true);
77
+ this.$set(obj, undefined, true);
78
78
  }
79
79
  }
80
80
 
@@ -435,8 +435,18 @@ function init(self, obj, doc, prefix) {
435
435
  */
436
436
 
437
437
  for (var k in hooks) {
438
- if (k === 'pre' || k === 'post') {
438
+ if (k === 'post') {
439
439
  Document.prototype['$' + k] = Document['$' + k] = hooks[k];
440
+ } else if (k === 'pre') {
441
+ Document.prototype.$pre = Document.$pre = function mongoosePreWrapper() {
442
+ if (arguments[0] === 'set') {
443
+ // Make set hooks also work for `$set`
444
+ var $setArgs = Array.prototype.slice.call(arguments);
445
+ $setArgs[0] = '$set';
446
+ hooks.pre.apply(this, $setArgs);
447
+ }
448
+ return hooks.pre.apply(this, arguments);
449
+ };
440
450
  } else {
441
451
  Document.prototype[k] = Document[k] = hooks[k];
442
452
  }
@@ -468,29 +478,7 @@ Document.prototype.update = function update() {
468
478
  };
469
479
 
470
480
  /**
471
- * Sets the value of a path, or many paths.
472
- *
473
- * ####Example:
474
- *
475
- * // path, value
476
- * doc.set(path, value)
477
- *
478
- * // object
479
- * doc.set({
480
- * path : value
481
- * , path2 : {
482
- * path : value
483
- * }
484
- * })
485
- *
486
- * // on-the-fly cast to number
487
- * doc.set(path, value, Number)
488
- *
489
- * // on-the-fly cast to string
490
- * doc.set(path, value, String)
491
- *
492
- * // changing strict mode behavior
493
- * doc.set(path, value, { strict: false });
481
+ * Alias for `set()`, used internally to avoid conflicts
494
482
  *
495
483
  * @param {String|Object} path path or object of key/vals to set
496
484
  * @param {Any} val the value to set
@@ -499,7 +487,7 @@ Document.prototype.update = function update() {
499
487
  * @api public
500
488
  */
501
489
 
502
- Document.prototype.set = function(path, val, type, options) {
490
+ Document.prototype.$set = function(path, val, type, options) {
503
491
  if (type && utils.getFunctionName(type.constructor) === 'Object') {
504
492
  options = type;
505
493
  type = undefined;
@@ -548,7 +536,7 @@ Document.prototype.set = function(path, val, type, options) {
548
536
 
549
537
  if (len === 0 && !this.schema.options.minimize) {
550
538
  if (val) {
551
- this.set(val, {});
539
+ this.$set(val, {});
552
540
  }
553
541
  return this;
554
542
  }
@@ -583,7 +571,7 @@ Document.prototype.set = function(path, val, type, options) {
583
571
  && !(this.schema.paths[pathName] &&
584
572
  this.schema.paths[pathName].options &&
585
573
  this.schema.paths[pathName].options.ref)) {
586
- this.set(path[key], prefix + key, constructing);
574
+ this.$set(path[key], prefix + key, constructing);
587
575
  } else if (strict) {
588
576
  // Don't overwrite defaults with undefined keys (gh-3981)
589
577
  if (constructing && path[key] === void 0 &&
@@ -599,9 +587,9 @@ Document.prototype.set = function(path, val, type, options) {
599
587
  path[key] instanceof Document) {
600
588
  p = p.toObject({ virtuals: false, transform: false });
601
589
  }
602
- this.set(prefix + key, p, constructing);
590
+ this.$set(prefix + key, p, constructing);
603
591
  } else if (pathtype === 'nested' && path[key] instanceof Document) {
604
- this.set(prefix + key,
592
+ this.$set(prefix + key,
605
593
  path[key].toObject({transform: false}), constructing);
606
594
  } else if (strict === 'throw') {
607
595
  if (pathtype === 'nested') {
@@ -611,7 +599,7 @@ Document.prototype.set = function(path, val, type, options) {
611
599
  }
612
600
  }
613
601
  } else if (path[key] !== void 0) {
614
- this.set(prefix + key, path[key], constructing);
602
+ this.$set(prefix + key, path[key], constructing);
615
603
  }
616
604
  }
617
605
 
@@ -629,7 +617,7 @@ Document.prototype.set = function(path, val, type, options) {
629
617
  this.markModified(path);
630
618
  cleanModifiedSubpaths(this, path);
631
619
  } else {
632
- this.set(val, path, constructing);
620
+ this.$set(val, path, constructing);
633
621
  }
634
622
  return this;
635
623
  }
@@ -681,7 +669,7 @@ Document.prototype.set = function(path, val, type, options) {
681
669
  cur = cur[parts[i]];
682
670
  curPath += (curPath.length > 0 ? '.' : '') + parts[i];
683
671
  if (!cur) {
684
- this.set(curPath, {});
672
+ this.$set(curPath, {});
685
673
  cur = this.getValue(curPath);
686
674
  }
687
675
  }
@@ -785,6 +773,40 @@ Document.prototype.set = function(path, val, type, options) {
785
773
  return this;
786
774
  };
787
775
 
776
+ /**
777
+ * Sets the value of a path, or many paths.
778
+ *
779
+ * ####Example:
780
+ *
781
+ * // path, value
782
+ * doc.set(path, value)
783
+ *
784
+ * // object
785
+ * doc.set({
786
+ * path : value
787
+ * , path2 : {
788
+ * path : value
789
+ * }
790
+ * })
791
+ *
792
+ * // on-the-fly cast to number
793
+ * doc.set(path, value, Number)
794
+ *
795
+ * // on-the-fly cast to string
796
+ * doc.set(path, value, String)
797
+ *
798
+ * // changing strict mode behavior
799
+ * doc.set(path, value, { strict: false });
800
+ *
801
+ * @param {String|Object} path path or object of key/vals to set
802
+ * @param {Any} val the value to set
803
+ * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
804
+ * @param {Object} [options] optionally specify options that modify the behavior of the set
805
+ * @api public
806
+ */
807
+
808
+ Document.prototype.set = Document.prototype.$set;
809
+
788
810
  /**
789
811
  * Determine if we should mark this change as modified.
790
812
  *
@@ -2570,12 +2592,17 @@ Document.prototype.populated = function(path, val, options) {
2570
2592
  */
2571
2593
 
2572
2594
  Document.prototype.depopulate = function(path) {
2573
- var populatedIds = this.populated(path);
2574
- if (!populatedIds) {
2575
- return;
2595
+ if (typeof path === 'string') {
2596
+ path = path.split(' ');
2597
+ }
2598
+ for (var i = 0; i < path.length; i++) {
2599
+ var populatedIds = this.populated(path[i]);
2600
+ if (!populatedIds) {
2601
+ continue;
2602
+ }
2603
+ delete this.$__.populated[path[i]];
2604
+ this.$set(path[i], populatedIds);
2576
2605
  }
2577
- delete this.$__.populated[path];
2578
- this.set(path, populatedIds);
2579
2606
  return this;
2580
2607
  };
2581
2608
 
package/lib/model.js CHANGED
@@ -20,6 +20,7 @@ var cast = require('./cast');
20
20
  var castUpdate = require('./services/query/castUpdate');
21
21
  var discriminator = require('./services/model/discriminator');
22
22
  var isPathSelectedInclusive = require('./services/projection/isPathSelectedInclusive');
23
+ var get = require('lodash.get');
23
24
  var mpath = require('mpath');
24
25
  var parallel = require('async/parallel');
25
26
  var parallelLimit = require('async/parallelLimit');
@@ -552,7 +553,13 @@ Model.prototype.$__delta = function() {
552
553
  value = value.toObject();
553
554
  operand(this, where, delta, data, value);
554
555
  } else {
555
- value = utils.clone(value, {depopulate: 1, _isNested: true});
556
+ value = utils.clone(value, {
557
+ depopulate: true,
558
+ transform: false,
559
+ virtuals: false,
560
+ retainKeyOrder: true,
561
+ _isNested: true
562
+ });
556
563
  operand(this, where, delta, data, value);
557
564
  }
558
565
  }
@@ -651,8 +658,13 @@ Model.prototype.$__version = function(where, delta) {
651
658
  }
652
659
 
653
660
  if (VERSION_INC === (VERSION_INC & this.$__.version)) {
654
- if (!delta.$set || typeof delta.$set[key] === 'undefined') {
655
- delta.$inc || (delta.$inc = {});
661
+ if (get(delta.$set, key, null) != null) {
662
+ // Version key is getting set, means we'll increment the doc's version
663
+ // after a successful save, so we should set the incremented version so
664
+ // future saves don't fail (gh-5779)
665
+ ++delta.$set[key];
666
+ } else {
667
+ delta.$inc = delta.$inc || {};
656
668
  delta.$inc[key] = 1;
657
669
  }
658
670
  }
@@ -921,9 +933,9 @@ Model.init = function init(callback) {
921
933
  callback && callback(null, _this);
922
934
  resolve(_this);
923
935
  });
936
+ } else {
937
+ resolve(_this);
924
938
  }
925
-
926
- return _this;
927
939
  });
928
940
 
929
941
  return this.$init;
@@ -2102,6 +2114,11 @@ var INSERT_MANY_CONVERT_OPTIONS = {
2102
2114
  * because it only sends one operation to the server, rather than one for each
2103
2115
  * document.
2104
2116
  *
2117
+ * Mongoose always validates each document **before** sending `insertMany`
2118
+ * to MongoDB. So if one document has a validation error, no documents will
2119
+ * be saved, unless you set
2120
+ * [the `ordered` option to false](https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#error-handling).
2121
+ *
2105
2122
  * This function does **not** trigger save middleware.
2106
2123
  *
2107
2124
  * This function triggers the following middleware:
@@ -2114,6 +2131,8 @@ var INSERT_MANY_CONVERT_OPTIONS = {
2114
2131
  *
2115
2132
  * @param {Array|Object|*} doc(s)
2116
2133
  * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#insertMany)
2134
+ * @param {Boolean} [options.ordered = true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
2135
+ * @param {Boolean} [options.rawResult = false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `false`, will return the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~insertWriteOpCallback) with a `mongoose` property that contains `validationErrors` if this is an unordered `insertMany`.
2117
2136
  * @param {Function} [callback] callback
2118
2137
  * @return {Promise}
2119
2138
  * @api public
@@ -2128,16 +2147,18 @@ Model.insertMany = function(arr, options, callback) {
2128
2147
  if (callback) {
2129
2148
  callback = this.$wrapCallback(callback);
2130
2149
  }
2131
- var limit = options && options.limit;
2132
- if (typeof limit !== 'number') {
2133
- limit = 1000;
2134
- }
2150
+ callback = callback || utils.noop;
2151
+ options = options || {};
2152
+ var limit = get(options, 'limit', 1000);
2153
+ var rawResult = get(options, 'rawResult', false);
2154
+ var ordered = get(options, 'ordered', true);
2135
2155
 
2136
2156
  if (!Array.isArray(arr)) {
2137
2157
  arr = [arr];
2138
2158
  }
2139
2159
 
2140
2160
  var toExecute = [];
2161
+ var validationErrors = [];
2141
2162
  arr.forEach(function(doc) {
2142
2163
  toExecute.push(function(callback) {
2143
2164
  doc = new _this(doc);
@@ -2146,7 +2167,8 @@ Model.insertMany = function(arr, options, callback) {
2146
2167
  // Option `ordered` signals that insert should be continued after reaching
2147
2168
  // a failing insert. Therefore we delegate "null", meaning the validation
2148
2169
  // failed. It's up to the next function to filter out all failed models
2149
- if (options != null && typeof options === 'object' && options['ordered'] === false) {
2170
+ if (ordered === false) {
2171
+ validationErrors.push(error);
2150
2172
  return callback(null, null);
2151
2173
  }
2152
2174
  return callback(error);
@@ -2167,7 +2189,7 @@ Model.insertMany = function(arr, options, callback) {
2167
2189
  });
2168
2190
  // Quickly escape while there aren't any valid docAttributes
2169
2191
  if (docAttributes.length < 1) {
2170
- callback && callback(null, []);
2192
+ callback(null, []);
2171
2193
  return;
2172
2194
  }
2173
2195
  var docObjects = docAttributes.map(function(doc) {
@@ -2180,7 +2202,7 @@ Model.insertMany = function(arr, options, callback) {
2180
2202
  return doc.toObject(INSERT_MANY_CONVERT_OPTIONS);
2181
2203
  });
2182
2204
 
2183
- _this.collection.insertMany(docObjects, options, function(error) {
2205
+ _this.collection.insertMany(docObjects, options, function(error, res) {
2184
2206
  if (error) {
2185
2207
  callback && callback(error);
2186
2208
  return;
@@ -2190,7 +2212,17 @@ Model.insertMany = function(arr, options, callback) {
2190
2212
  docAttributes[i].emit('isNew', false);
2191
2213
  docAttributes[i].constructor.emit('isNew', false);
2192
2214
  }
2193
- callback && callback(null, docAttributes);
2215
+ if (rawResult) {
2216
+ if (ordered === false) {
2217
+ // Decorate with mongoose validation errors in case of unordered,
2218
+ // because then still do `insertMany()`
2219
+ res.mongoose = {
2220
+ validationErrors: validationErrors
2221
+ };
2222
+ }
2223
+ return callback(null, res);
2224
+ }
2225
+ callback(null, docAttributes);
2194
2226
  });
2195
2227
  });
2196
2228
  };
@@ -2763,7 +2795,9 @@ Model.geoNear = function(near, options, callback) {
2763
2795
  });
2764
2796
  }
2765
2797
 
2766
- var x, y;
2798
+ var x;
2799
+ var y;
2800
+ var schema = this.schema;
2767
2801
 
2768
2802
  return new Promise.ES6(function(resolve, reject) {
2769
2803
  var handler = function(err, res) {
@@ -2808,6 +2842,11 @@ Model.geoNear = function(near, options, callback) {
2808
2842
  }
2809
2843
  };
2810
2844
 
2845
+ if (options.query != null) {
2846
+ options.query = utils.clone(options.query, { retainKeyOrder: 1 });
2847
+ cast(schema, options.query);
2848
+ }
2849
+
2811
2850
  if (Array.isArray(near)) {
2812
2851
  if (near.length !== 2) {
2813
2852
  var error = new Error('If using legacy coordinates, must be an array ' +
@@ -3552,10 +3591,14 @@ function getModelsMapForPopulate(model, docs, options) {
3552
3591
  }
3553
3592
  var virtual = modelForCurrentDoc.schema._getVirtual(options.path);
3554
3593
 
3555
- if (schemaForCurrentDoc && schemaForCurrentDoc.options && schemaForCurrentDoc.options.ref) {
3556
- modelNames = [schemaForCurrentDoc.options.ref];
3557
- } else if (virtual && virtual.options && virtual.options.ref) {
3558
- modelNames = [virtual && virtual.options && virtual.options.ref];
3594
+ var ref;
3595
+ if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
3596
+ modelNames = [ref];
3597
+ } else if ((ref = get(virtual, 'options.ref')) != null) {
3598
+ if (typeof ref === 'function') {
3599
+ ref = ref.call(doc, doc);
3600
+ }
3601
+ modelNames = [ref];
3559
3602
  isVirtual = true;
3560
3603
  } else {
3561
3604
  // We may have a discriminator, in which case we don't want to
@@ -3576,9 +3619,18 @@ function getModelsMapForPopulate(model, docs, options) {
3576
3619
  }
3577
3620
 
3578
3621
  virtual = model.schema._getVirtual(options.path);
3579
- var localField = virtual && virtual.options ?
3580
- (virtual.$nestedSchemaPath ? virtual.$nestedSchemaPath + '.' : '') + virtual.options.localField :
3581
- options.path;
3622
+ var localField;
3623
+ if (virtual && virtual.options) {
3624
+ var virtualPrefix = virtual.$nestedSchemaPath ?
3625
+ virtual.$nestedSchemaPath + '.' : '';
3626
+ if (typeof virtual.options.localField === 'function') {
3627
+ localField = virtualPrefix + virtual.options.localField.call(doc, doc);
3628
+ } else {
3629
+ localField = virtualPrefix + virtual.options.localField;
3630
+ }
3631
+ } else {
3632
+ localField = options.path;
3633
+ }
3582
3634
  var foreignField = virtual && virtual.options ?
3583
3635
  virtual.options.foreignField :
3584
3636
  '_id';
@@ -3593,6 +3645,12 @@ function getModelsMapForPopulate(model, docs, options) {
3593
3645
  }
3594
3646
 
3595
3647
  options.isVirtual = isVirtual;
3648
+ if (typeof localField === 'function') {
3649
+ localField = localField.call(doc, doc);
3650
+ }
3651
+ if (typeof foreignField === 'function') {
3652
+ foreignField = foreignField.call(doc);
3653
+ }
3596
3654
  var ret = convertTo_id(utils.getValue(localField, doc));
3597
3655
  var id = String(utils.getValue(foreignField, doc));
3598
3656
  options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
@@ -3841,8 +3899,17 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
3841
3899
 
3842
3900
  model.prototype.$__setSchema(schema);
3843
3901
 
3902
+ var _userProvidedOptions = schema._userProvidedOptions || {};
3903
+ var bufferCommands = true;
3904
+ if (connection.config.bufferCommands != null) {
3905
+ bufferCommands = connection.config.bufferCommands;
3906
+ }
3907
+ if (_userProvidedOptions.bufferCommands != null) {
3908
+ bufferCommands = _userProvidedOptions.bufferCommands;
3909
+ }
3910
+
3844
3911
  var collectionOptions = {
3845
- bufferCommands: schema.options.bufferCommands,
3912
+ bufferCommands: bufferCommands,
3846
3913
  capped: schema.options.capped
3847
3914
  };
3848
3915
 
@@ -3937,14 +4004,24 @@ Model.__subclass = function subclass(conn, schema, collection) {
3937
4004
  : _this.prototype.schema;
3938
4005
 
3939
4006
  var options = s.options || {};
4007
+ var _userProvidedOptions = s._userProvidedOptions || {};
3940
4008
 
3941
4009
  if (!collection) {
3942
4010
  collection = _this.prototype.schema.get('collection')
3943
4011
  || utils.toCollectionName(_this.modelName, options);
3944
4012
  }
3945
4013
 
4014
+ var bufferCommands = true;
4015
+ if (s) {
4016
+ if (conn.config.bufferCommands != null) {
4017
+ bufferCommands = conn.config.bufferCommands;
4018
+ }
4019
+ if (_userProvidedOptions.bufferCommands != null) {
4020
+ bufferCommands = _userProvidedOptions.bufferCommands;
4021
+ }
4022
+ }
3946
4023
  var collectionOptions = {
3947
- bufferCommands: s ? options.bufferCommands : true,
4024
+ bufferCommands: bufferCommands,
3948
4025
  capped: s && options.capped
3949
4026
  };
3950
4027
 
package/lib/query.js CHANGED
@@ -11,6 +11,7 @@ var cast = require('./cast');
11
11
  var castUpdate = require('./services/query/castUpdate');
12
12
  var hasDollarKeys = require('./services/query/hasDollarKeys');
13
13
  var helpers = require('./queryhelpers');
14
+ var isInclusive = require('./services/projection/isInclusive');
14
15
  var mquery = require('mquery');
15
16
  var readPref = require('./drivers').ReadPreference;
16
17
  var selectPopulatedFields = require('./services/query/selectPopulatedFields');
@@ -2025,6 +2026,7 @@ function prepareDiscriminatorCriteria(query) {
2025
2026
  * @param {Object} [options]
2026
2027
  * @param {Boolean} [options.passRawResult] if true, passes the [raw result from the MongoDB driver as the third callback parameter](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
2027
2028
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
2029
+ * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
2028
2030
  * @param {Function} [callback] optional params are (error, doc), _unless_ `passRawResult` is used, in which case params are (error, doc, writeOpResult)
2029
2031
  * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
2030
2032
  * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
@@ -2720,6 +2722,7 @@ Query.prototype._replaceOne = function(callback) {
2720
2722
  * @param {Object} [criteria]
2721
2723
  * @param {Object} [doc] the update command
2722
2724
  * @param {Object} [options]
2725
+ * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
2723
2726
  * @param {Function} [callback] optional, params are (error, writeOpResult)
2724
2727
  * @return {Query} this
2725
2728
  * @see Model.update #model_Model.update
@@ -2771,6 +2774,7 @@ Query.prototype.update = function(conditions, doc, options, callback) {
2771
2774
  * @param {Object} [criteria]
2772
2775
  * @param {Object} [doc] the update command
2773
2776
  * @param {Object} [options]
2777
+ @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
2774
2778
  * @param {Function} [callback] optional params are (error, writeOpResult)
2775
2779
  * @return {Query} this
2776
2780
  * @see Model.update #model_Model.update
@@ -2821,6 +2825,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
2821
2825
  * @param {Object} [criteria]
2822
2826
  * @param {Object} [doc] the update command
2823
2827
  * @param {Object} [options]
2828
+ @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
2824
2829
  * @param {Function} [callback] params are (error, writeOpResult)
2825
2830
  * @return {Query} this
2826
2831
  * @see Model.update #model_Model.update
@@ -3824,28 +3829,7 @@ Query.prototype.centerSphere = function() {
3824
3829
  */
3825
3830
 
3826
3831
  Query.prototype.selectedInclusively = function selectedInclusively() {
3827
- if (!this._fields) {
3828
- return false;
3829
- }
3830
-
3831
- var keys = Object.keys(this._fields);
3832
- if (keys.length === 0) {
3833
- return false;
3834
- }
3835
-
3836
- for (var i = 0; i < keys.length; ++i) {
3837
- var key = keys[i];
3838
- if (this._fields[key] === 0 || this._fields[key] === false) {
3839
- return false;
3840
- }
3841
- if (this._fields[key] &&
3842
- typeof this._fields[key] === 'object' &&
3843
- this._fields[key].$meta) {
3844
- return false;
3845
- }
3846
- }
3847
-
3848
- return true;
3832
+ return isInclusive(this._fields);
3849
3833
  };
3850
3834
 
3851
3835
  /**
@@ -3,6 +3,7 @@
3
3
  * Module dependencies
4
4
  */
5
5
 
6
+ var get = require('lodash.get');
6
7
  var utils = require('./utils');
7
8
 
8
9
  /*!
@@ -75,7 +76,6 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField
75
76
 
76
77
  exports.applyPaths = function applyPaths(fields, schema) {
77
78
  // determine if query is selecting or excluding fields
78
-
79
79
  var exclude;
80
80
  var keys;
81
81
  var ki;
@@ -123,8 +123,25 @@ exports.applyPaths = function applyPaths(fields, schema) {
123
123
  }
124
124
 
125
125
  // check for parent exclusions
126
- var root = path.split('.')[0];
127
- if (~excluded.indexOf(root)) return;
126
+ var pieces = path.split('.');
127
+ var root = pieces[0];
128
+ if (~excluded.indexOf(root)) {
129
+ return;
130
+ }
131
+
132
+ // Special case: if user has included a parent path of a discriminator key,
133
+ // don't explicitly project in the discriminator key because that will
134
+ // project out everything else under the parent path
135
+ if (!exclude && get(type, 'options.$skipDiscriminatorCheck', false)) {
136
+ var cur = '';
137
+ for (var i = 0; i < pieces.length; ++i) {
138
+ cur += (cur.length === 0 ? '' : '.') + pieces[i];
139
+ var projection = get(fields, cur, false);
140
+ if (projection && typeof projection !== 'object') {
141
+ return;
142
+ }
143
+ }
144
+ }
128
145
 
129
146
  (type.selected ? selected : excluded).push(path);
130
147
  };
@@ -93,7 +93,7 @@ function SchemaArray(key, cast, options, schemaOptions) {
93
93
  if (fn) {
94
94
  arr = defaultArr();
95
95
  } else if (defaultArr != null) {
96
- arr = defaultArr;
96
+ arr = arr.concat(defaultArr);
97
97
  }
98
98
  // Leave it up to `cast()` to convert the array
99
99
  return arr;
@@ -113,6 +113,9 @@ SchemaBuffer.prototype.cast = function(value, doc, init) {
113
113
  if (Buffer.isBuffer(value)) {
114
114
  if (!value || !value.isMongooseBuffer) {
115
115
  value = new MongooseBuffer(value, [this.path, doc]);
116
+ if (this.options.subtype != null) {
117
+ value._subtype = this.options.subtype;
118
+ }
116
119
  }
117
120
 
118
121
  return value;
@@ -135,12 +138,36 @@ SchemaBuffer.prototype.cast = function(value, doc, init) {
135
138
  value = [value];
136
139
  }
137
140
  ret = new MongooseBuffer(value, [this.path, doc]);
141
+ if (this.options.subtype != null) {
142
+ ret._subtype = this.options.subtype;
143
+ }
138
144
  return ret;
139
145
  }
140
146
 
141
147
  throw new CastError('buffer', value, this.path);
142
148
  };
143
149
 
150
+ /**
151
+ * Sets the default [subtype](https://studio3t.com/whats-new/best-practices-uuid-mongodb/)
152
+ * for this buffer. You can find a [list of allowed subtypes here](http://api.mongodb.com/python/current/api/bson/binary.html).
153
+ *
154
+ * ####Example:
155
+ *
156
+ * var s = new Schema({ uuid: { type: Buffer, subtype: 4 });
157
+ * var M = db.model('M', s);
158
+ * var m = new M({ uuid: 'test string' });
159
+ * m.uuid._subtype; // 4
160
+ *
161
+ * @param {Number} subtype the default subtype
162
+ * @return {SchemaType} this
163
+ * @api public
164
+ */
165
+
166
+ SchemaBuffer.prototype.subtype = function(subtype) {
167
+ this.options.subtype = subtype;
168
+ return this;
169
+ };
170
+
144
171
  /*!
145
172
  * ignore
146
173
  */
@@ -11,7 +11,6 @@ var EventEmitter = require('events').EventEmitter;
11
11
  var MongooseDocumentArray = require('../types/documentarray');
12
12
  var SchemaType = require('../schematype');
13
13
  var Subdocument = require('../types/embedded');
14
- var applyHooks = require('../services/model/applyHooks');
15
14
  var discriminator = require('../services/model/discriminator');
16
15
  var util = require('util');
17
16
  var utils = require('../utils');
@@ -120,8 +119,6 @@ DocumentArray.prototype.discriminator = function(name, schema) {
120
119
 
121
120
  this.casterConstructor.discriminators[name] = EmbeddedDocument;
122
121
 
123
- applyHooks(EmbeddedDocument, schema);
124
-
125
122
  return this.casterConstructor.discriminators[name];
126
123
  };
127
124
 
@@ -8,7 +8,6 @@ var $exists = require('./operators/exists');
8
8
  var EventEmitter = require('events').EventEmitter;
9
9
  var SchemaType = require('../schematype');
10
10
  var Subdocument = require('../types/subdocument');
11
- var applyHooks = require('../services/model/applyHooks');
12
11
  var castToNumber = require('./operators/helpers').castToNumber;
13
12
  var discriminator = require('../services/model/discriminator');
14
13
  var geospatial = require('./operators/geospatial');
@@ -255,7 +254,5 @@ Embedded.prototype.discriminator = function(name, schema) {
255
254
 
256
255
  this.caster.discriminators[name] = _createConstructor(schema);
257
256
 
258
- applyHooks(this.caster.discriminators[name], schema);
259
-
260
257
  return this.caster.discriminators[name];
261
258
  };
package/lib/schema.js CHANGED
@@ -13,6 +13,7 @@ var SchemaType = require('./schematype');
13
13
  var mpath = require('mpath');
14
14
 
15
15
  var IS_KAREEM_HOOK = {
16
+ aggregate: true,
16
17
  count: true,
17
18
  find: true,
18
19
  findOne: true,
@@ -327,6 +328,10 @@ Schema.prototype.defaultOptions = function(options) {
327
328
  options.versionKey = false;
328
329
  }
329
330
 
331
+ this._userProvidedOptions = utils.clone(options, {
332
+ retainKeyOrder: true
333
+ });
334
+
330
335
  options = utils.options({
331
336
  strict: true,
332
337
  bufferCommands: true,
@@ -438,7 +443,6 @@ reserved.get =
438
443
  reserved.modelName =
439
444
  reserved.save =
440
445
  reserved.schema =
441
- reserved.set =
442
446
  reserved.toObject =
443
447
  reserved.validate =
444
448
  reserved.remove =
@@ -588,7 +592,7 @@ Schema.interpretAsType = function(path, obj, options) {
588
592
  if (cast &&
589
593
  cast[options.typeKey] &&
590
594
  cast[options.typeKey].instanceOfSchema) {
591
- return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj);
595
+ return new MongooseTypes.DocumentArray(path, cast[options.typeKey], cast);
592
596
  }
593
597
 
594
598
  if (Array.isArray(cast)) {
@@ -1427,7 +1431,9 @@ Schema.prototype.indexes = function() {
1427
1431
  path = schema.paths[key];
1428
1432
 
1429
1433
  if ((path instanceof MongooseTypes.DocumentArray) || path.$isSingleNested) {
1430
- collectIndexes(path.schema, prefix + key + '.');
1434
+ if (path.options.excludeIndexes !== true) {
1435
+ collectIndexes(path.schema, prefix + key + '.');
1436
+ }
1431
1437
  } else {
1432
1438
  index = path._index || (path.caster && path.caster._index);
1433
1439
 
package/lib/schematype.js CHANGED
@@ -28,18 +28,23 @@ function SchemaType(path, options, instance) {
28
28
  this._index = null;
29
29
  this.selected;
30
30
 
31
- for (var i in options) {
32
- if (this[i] && typeof this[i] === 'function') {
31
+ for (var prop in options) {
32
+ if (this[prop] && typeof this[prop] === 'function') {
33
33
  // { unique: true, index: true }
34
- if (i === 'index' && this._index) {
34
+ if (prop === 'index' && this._index) {
35
35
  continue;
36
36
  }
37
37
 
38
- var opts = Array.isArray(options[i])
39
- ? options[i]
40
- : [options[i]];
38
+ var val = options[prop];
39
+ // Special case so we don't screw up array defaults, see gh-5780
40
+ if (prop === 'default') {
41
+ this.default(val);
42
+ continue;
43
+ }
44
+
45
+ var opts = Array.isArray(val) ? val : [val];
41
46
 
42
- this[i].apply(this, opts);
47
+ this[prop].apply(this, opts);
43
48
  }
44
49
  }
45
50
 
@@ -126,7 +126,8 @@ function defineKey(prop, subprops, prototype, prefix, keys, options) {
126
126
  if (v instanceof Document) {
127
127
  v = v.toObject({ transform: false });
128
128
  }
129
- return (this.$__.scope || this).set(path, v);
129
+ var doc = this.$__.scope || this;
130
+ return doc.$set(path, v);
130
131
  }
131
132
  });
132
133
  } else {
@@ -137,7 +138,7 @@ function defineKey(prop, subprops, prototype, prefix, keys, options) {
137
138
  return this.get.call(this.$__.scope || this, path);
138
139
  },
139
140
  set: function(v) {
140
- return this.set.call(this.$__.scope || this, path, v);
141
+ return this.$set.call(this.$__.scope || this, path, v);
141
142
  }
142
143
  });
143
144
  }
@@ -24,10 +24,18 @@ function applyHooks(model, schema) {
24
24
 
25
25
  model.$appliedHooks = true;
26
26
  for (i = 0; i < schema.childSchemas.length; ++i) {
27
- if (schema.childSchemas[i].model.$appliedHooks) {
27
+ var childModel = schema.childSchemas[i].model;
28
+ if (childModel.$appliedHooks) {
28
29
  continue;
29
30
  }
30
- applyHooks(schema.childSchemas[i].model, schema.childSchemas[i].schema);
31
+ if (childModel.discriminators != null) {
32
+ keys = Object.keys(childModel.discriminators);
33
+ for (j = 0; j < keys.length; ++j) {
34
+ applyHooks(childModel.discriminators[keys[j]],
35
+ childModel.discriminators[keys[j]].schema);
36
+ }
37
+ }
38
+ applyHooks(childModel, schema.childSchemas[i].schema);
31
39
  }
32
40
 
33
41
  if (!q.length) {
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ /*!
4
+ * ignore
5
+ */
6
+
7
+ module.exports = function isInclusive(projection) {
8
+ if (projection == null) {
9
+ return false;
10
+ }
11
+
12
+ var props = Object.keys(projection);
13
+ var numProps = props.length;
14
+ if (numProps === 0) {
15
+ return false;
16
+ }
17
+
18
+ for (var i = 0; i < numProps; ++i) {
19
+ var prop = props[i];
20
+ // If field is truthy (1, true, etc.) and not an object, then this
21
+ // projection must be inclusive. If object, assume its $meta, $slice, etc.
22
+ if (typeof projection[prop] !== 'object' && !!projection[prop]) {
23
+ return true;
24
+ }
25
+ }
26
+
27
+ return false;
28
+ };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var StrictModeError = require('../../error/strict');
4
+ var ValidationError = require('../../error/validation');
4
5
  var utils = require('../../utils');
5
6
 
6
7
  /*!
@@ -102,13 +103,16 @@ module.exports = function castUpdate(schema, obj, options, context) {
102
103
  */
103
104
 
104
105
  function walkUpdatePath(schema, obj, op, strict, context, pref) {
105
- var prefix = pref ? pref + '.' : '',
106
- keys = Object.keys(obj),
107
- i = keys.length,
108
- hasKeys = false,
109
- schematype,
110
- key,
111
- val;
106
+ var prefix = pref ? pref + '.' : '';
107
+ var keys = Object.keys(obj);
108
+ var i = keys.length;
109
+ var hasKeys = false;
110
+ var schematype;
111
+ var key;
112
+ var val;
113
+
114
+ var hasError = false;
115
+ var aggregatedError = new ValidationError();
112
116
 
113
117
  var useNestedStrict = schema.options.useNestedStrict;
114
118
 
@@ -124,9 +128,14 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
124
128
  hasKeys = true;
125
129
 
126
130
  if ('$each' in val) {
127
- obj[key] = {
128
- $each: castUpdateVal(schematype, val.$each, op, context)
129
- };
131
+ try {
132
+ obj[key] = {
133
+ $each: castUpdateVal(schematype, val.$each, op, context)
134
+ };
135
+ } catch (error) {
136
+ hasError = true;
137
+ _handleCastError(error, context, key, aggregatedError);
138
+ }
130
139
 
131
140
  if (val.$slice != null) {
132
141
  obj[key].$slice = val.$slice | 0;
@@ -140,14 +149,22 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
140
149
  obj[key].$position = val.$position;
141
150
  }
142
151
  } else {
143
- obj[key] = castUpdateVal(schematype, val, op, context);
152
+ try {
153
+ obj[key] = castUpdateVal(schematype, val, op, context);
154
+ } catch (error) {
155
+ hasError = true;
156
+ _handleCastError(error, context, key, aggregatedError);
157
+ }
144
158
  }
145
- } else if (op === '$currentDate') {
159
+ } else if ((op === '$currentDate') || (op in castOps && schematype)) {
146
160
  // $currentDate can take an object
147
- obj[key] = castUpdateVal(schematype, val, op, context);
148
- hasKeys = true;
149
- } else if (op in castOps && schematype) {
150
- obj[key] = castUpdateVal(schematype, val, op, context);
161
+ try {
162
+ obj[key] = castUpdateVal(schematype, val, op, context);
163
+ } catch (error) {
164
+ hasError = true;
165
+ _handleCastError(error, context, key, aggregatedError);
166
+ }
167
+
151
168
  hasKeys = true;
152
169
  } else {
153
170
  var pathToCheck = (prefix + key);
@@ -208,13 +225,34 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
208
225
  }
209
226
 
210
227
  hasKeys = true;
211
- obj[key] = castUpdateVal(schematype, val, op, key, context);
228
+ try {
229
+ obj[key] = castUpdateVal(schematype, val, op, key, context);
230
+ } catch (error) {
231
+ hasError = true;
232
+ _handleCastError(error, context, key, aggregatedError);
233
+ }
212
234
  }
213
235
  }
214
236
  }
237
+
238
+ if (hasError) {
239
+ throw aggregatedError;
240
+ }
241
+
215
242
  return hasKeys;
216
243
  }
217
244
 
245
+ /*!
246
+ * ignore
247
+ */
248
+
249
+ function _handleCastError(error, query, key, aggregatedError) {
250
+ if (!query.options.multipleCastError) {
251
+ throw error;
252
+ }
253
+ aggregatedError.addError(key, error);
254
+ }
255
+
218
256
  /*!
219
257
  * These operators should be cast to numbers instead
220
258
  * of their path schema type.
package/lib/utils.js CHANGED
@@ -894,3 +894,9 @@ exports.each = function(arr, fn) {
894
894
  fn(arr[i]);
895
895
  }
896
896
  };
897
+
898
+ /*!
899
+ * ignore
900
+ */
901
+
902
+ exports.noop = function() {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "4.12.6",
4
+ "version": "4.13.3",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -23,6 +23,7 @@
23
23
  "bson": "~1.0.4",
24
24
  "hooks-fixed": "2.0.2",
25
25
  "kareem": "1.5.0",
26
+ "lodash.get": "4.4.2",
26
27
  "mongodb": "2.2.33",
27
28
  "mpath": "0.3.0",
28
29
  "mpromise": "0.5.5",
@@ -44,7 +45,7 @@
44
45
  "istanbul": "0.4.4",
45
46
  "jade": "0.26.3",
46
47
  "lodash": "4.16.6",
47
- "markdown": "0.3.1",
48
+ "markdown": "0.5.0",
48
49
  "marked": "0.3.6",
49
50
  "mocha": "3.2.0",
50
51
  "mongoose-long": "0.1.1",
package/release-items.md CHANGED
@@ -18,7 +18,7 @@ For 4.x
18
18
 
19
19
  0. Change to the master branch
20
20
  1. execute `make docs` (when this process completes you'll be on the gh-pages branch)
21
- 2. `git commit -a -m 'docs: release 4.x.x'`
21
+ 2. `git commit -a -m 'chore: website 4.x.x'`
22
22
  3. `git push origin gh-pages`
23
23
 
24
24
  For 3.8.x: