mongoose 6.4.7 → 6.5.2

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.
Files changed (47) hide show
  1. package/lib/connection.js +23 -3
  2. package/lib/cursor/ChangeStream.js +39 -1
  3. package/lib/document.js +129 -154
  4. package/lib/error/index.js +1 -0
  5. package/lib/error/validation.js +9 -0
  6. package/lib/helpers/document/applyDefaults.js +115 -0
  7. package/lib/helpers/document/cleanModifiedSubpaths.js +3 -3
  8. package/lib/helpers/document/compile.js +7 -6
  9. package/lib/helpers/firstKey.js +8 -0
  10. package/lib/helpers/model/applyDefaultsToPOJO.js +52 -0
  11. package/lib/helpers/model/castBulkWrite.js +1 -1
  12. package/lib/helpers/model/pushNestedArrayPaths.js +15 -0
  13. package/lib/helpers/populate/markArraySubdocsPopulated.js +1 -0
  14. package/lib/helpers/projection/hasIncludedChildren.js +1 -0
  15. package/lib/helpers/promiseOrCallback.js +24 -15
  16. package/lib/helpers/update/applyTimestampsToChildren.js +6 -2
  17. package/lib/index.js +2 -1
  18. package/lib/internal.js +3 -1
  19. package/lib/model.js +237 -95
  20. package/lib/options/SchemaArrayOptions.js +19 -0
  21. package/lib/options/SchemaNumberOptions.js +2 -0
  22. package/lib/options/SchemaObjectIdOptions.js +1 -0
  23. package/lib/plugins/trackTransaction.js +2 -2
  24. package/lib/query.js +30 -11
  25. package/lib/schema/SubdocumentPath.js +10 -0
  26. package/lib/schema/array.js +2 -1
  27. package/lib/schema/documentarray.js +14 -1
  28. package/lib/schema/string.js +3 -0
  29. package/lib/schema.js +22 -3
  30. package/lib/schematype.js +5 -1
  31. package/lib/statemachine.js +23 -9
  32. package/lib/types/buffer.js +23 -21
  33. package/lib/types/map.js +2 -0
  34. package/lib/utils.js +8 -0
  35. package/lib/validoptions.js +1 -0
  36. package/package.json +14 -14
  37. package/{build-browser.js → scripts/build-browser.js} +1 -1
  38. package/types/connection.d.ts +7 -1
  39. package/types/document.d.ts +8 -1
  40. package/types/expressions.d.ts +1 -1
  41. package/types/index.d.ts +31 -28
  42. package/types/inferschematype.d.ts +3 -20
  43. package/types/models.d.ts +53 -49
  44. package/types/mongooseoptions.d.ts +6 -0
  45. package/types/schemaoptions.d.ts +15 -4
  46. package/types/utility.d.ts +19 -0
  47. package/types/virtuals.d.ts +14 -0
package/lib/connection.js CHANGED
@@ -491,6 +491,9 @@ Connection.prototype.transaction = function transaction(fn, options) {
491
491
  doc.set(doc.schema.options.versionKey, state.versionKey);
492
492
  }
493
493
 
494
+ if (state.modifiedPaths.length > 0 && doc.$__.activePaths.states.modify == null) {
495
+ doc.$__.activePaths.states.modify = {};
496
+ }
494
497
  for (const path of state.modifiedPaths) {
495
498
  doc.$__.activePaths.paths[path] = 'modify';
496
499
  doc.$__.activePaths.states.modify[path] = true;
@@ -550,8 +553,8 @@ Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) {
550
553
  // init-ed. It is sufficiently common to call `dropDatabase()` after
551
554
  // `mongoose.connect()` but before creating models that we want to
552
555
  // support this. See gh-6796
553
- for (const name of Object.keys(this.models)) {
554
- delete this.models[name].$init;
556
+ for (const model of Object.values(this.models)) {
557
+ delete model.$init;
555
558
  }
556
559
  this.db.dropDatabase(cb);
557
560
  });
@@ -840,6 +843,11 @@ Connection.prototype.openUri = function(uri, options, callback) {
840
843
  );
841
844
  }
842
845
 
846
+ for (const model of Object.values(this.models)) {
847
+ // Errors handled internally, so safe to ignore error
848
+ model.init(function $modelInitNoop() {});
849
+ }
850
+
843
851
  return this.$initialConnection;
844
852
  };
845
853
 
@@ -961,6 +969,13 @@ Connection.prototype.close = function(force, callback) {
961
969
  this.$wasForceClosed = !!force;
962
970
  }
963
971
 
972
+ for (const model of Object.values(this.models)) {
973
+ // If manually disconnecting, make sure to clear each model's `$init`
974
+ // promise, so Mongoose knows to re-run `init()` in case the
975
+ // connection is re-opened. See gh-12047.
976
+ delete model.$init;
977
+ }
978
+
964
979
  return promiseOrCallback(callback, cb => {
965
980
  this._close(force, false, cb);
966
981
  });
@@ -1102,7 +1117,7 @@ Connection.prototype.collection = function(name, options) {
1102
1117
  * @param {Function} fn plugin callback
1103
1118
  * @param {Object} [opts] optional options
1104
1119
  * @return {Connection} this
1105
- * @see plugins ./plugins.html
1120
+ * @see plugins /docs/plugins
1106
1121
  * @api public
1107
1122
  */
1108
1123
 
@@ -1462,6 +1477,11 @@ Connection.prototype.setClient = function setClient(client) {
1462
1477
  this._connectionString = client.s.url;
1463
1478
  _setClient(this, client, {}, client.s.options.dbName);
1464
1479
 
1480
+ for (const model of Object.values(this.models)) {
1481
+ // Errors handled internally, so safe to ignore error
1482
+ model.init(function $modelInitNoop() {});
1483
+ }
1484
+
1465
1485
  return this;
1466
1486
  };
1467
1487
 
@@ -20,6 +20,13 @@ class ChangeStream extends EventEmitter {
20
20
  this.pipeline = pipeline;
21
21
  this.options = options;
22
22
 
23
+ if (options && options.hydrate && !options.model) {
24
+ throw new Error(
25
+ 'Cannot create change stream with `hydrate: true` ' +
26
+ 'unless calling `Model.watch()`'
27
+ );
28
+ }
29
+
23
30
  // This wrapper is necessary because of buffering.
24
31
  changeStreamThunk((err, driverChangeStream) => {
25
32
  if (err != null) {
@@ -46,7 +53,12 @@ class ChangeStream extends EventEmitter {
46
53
  });
47
54
 
48
55
  ['close', 'change', 'end', 'error'].forEach(ev => {
49
- this.driverChangeStream.on(ev, data => this.emit(ev, data));
56
+ this.driverChangeStream.on(ev, data => {
57
+ if (data != null && data.fullDocument != null && this.options && this.options.hydrate) {
58
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
59
+ }
60
+ this.emit(ev, data);
61
+ });
50
62
  });
51
63
  });
52
64
 
@@ -69,6 +81,32 @@ class ChangeStream extends EventEmitter {
69
81
  }
70
82
 
71
83
  next(cb) {
84
+ if (this.options && this.options.hydrate) {
85
+ if (cb != null) {
86
+ const originalCb = cb;
87
+ cb = (err, data) => {
88
+ if (err != null) {
89
+ return originalCb(err);
90
+ }
91
+ if (data.fullDocument != null) {
92
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
93
+ }
94
+ return originalCb(null, data);
95
+ };
96
+ }
97
+
98
+ let maybePromise = this.driverChangeStream.next(cb);
99
+ if (maybePromise && typeof maybePromise.then === 'function') {
100
+ maybePromise = maybePromise.then(data => {
101
+ if (data.fullDocument != null) {
102
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
103
+ }
104
+ return data;
105
+ });
106
+ }
107
+ return maybePromise;
108
+ }
109
+
72
110
  return this.driverChangeStream.next(cb);
73
111
  }
74
112
 
package/lib/document.js CHANGED
@@ -18,6 +18,8 @@ const ValidatorError = require('./error/validator');
18
18
  const VirtualType = require('./virtualtype');
19
19
  const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren');
20
20
  const promiseOrCallback = require('./helpers/promiseOrCallback');
21
+ const castNumber = require('./cast/number');
22
+ const applyDefaults = require('./helpers/document/applyDefaults');
21
23
  const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
22
24
  const compile = require('./helpers/document/compile').compile;
23
25
  const defineKey = require('./helpers/document/compile').defineKey;
@@ -93,7 +95,11 @@ function Document(obj, fields, skipId, options) {
93
95
  }
94
96
 
95
97
  this.$__ = new InternalCache();
96
- this.$isNew = 'isNew' in options ? options.isNew : true;
98
+
99
+ // Avoid setting `isNew` to `true`, because it is `true` by default
100
+ if (options.isNew != null && options.isNew !== true) {
101
+ this.$isNew = options.isNew;
102
+ }
97
103
 
98
104
  if (options.priorDoc != null) {
99
105
  this.$__.priorDoc = options.priorDoc;
@@ -116,13 +122,12 @@ function Document(obj, fields, skipId, options) {
116
122
  const schema = this.$__schema;
117
123
 
118
124
  if (typeof fields === 'boolean' || fields === 'throw') {
119
- this.$__.strictMode = fields;
125
+ if (fields !== true) {
126
+ this.$__.strictMode = fields;
127
+ }
120
128
  fields = undefined;
121
- } else {
129
+ } else if (schema.options.strict !== true) {
122
130
  this.$__.strictMode = schema.options.strict;
123
- if (fields != null) {
124
- this.$__.selected = fields;
125
- }
126
131
  }
127
132
 
128
133
  const requiredPaths = schema.requiredPaths(true);
@@ -134,9 +139,9 @@ function Document(obj, fields, skipId, options) {
134
139
 
135
140
  // determine if this doc is a result of a query with
136
141
  // excluded fields
137
- if (utils.isPOJO(fields)) {
142
+ if (utils.isPOJO(fields) && Object.keys(fields).length > 0) {
138
143
  exclude = isExclusive(fields);
139
- this.$__.fields = fields;
144
+ this.$__.selected = fields;
140
145
  this.$__.exclude = exclude;
141
146
  }
142
147
 
@@ -150,7 +155,7 @@ function Document(obj, fields, skipId, options) {
150
155
  // By default, defaults get applied **before** setting initial values
151
156
  // Re: gh-6155
152
157
  if (defaults) {
153
- $__applyDefaults(this, fields, exclude, hasIncludedChildren, true, null);
158
+ applyDefaults(this, fields, exclude, hasIncludedChildren, true, null);
154
159
  }
155
160
  }
156
161
  if (obj) {
@@ -174,7 +179,7 @@ function Document(obj, fields, skipId, options) {
174
179
  this.$__.skipDefaults = options.skipDefaults;
175
180
  }
176
181
  } else if (defaults) {
177
- $__applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
182
+ applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
178
183
  }
179
184
 
180
185
  if (!this.$__.strictMode && obj) {
@@ -260,6 +265,12 @@ Object.defineProperty(Document.prototype, 'errors', {
260
265
  }
261
266
  });
262
267
 
268
+ /*!
269
+ * ignore
270
+ */
271
+
272
+ Document.prototype.$isNew = true;
273
+
263
274
  /*!
264
275
  * Document exposes the NodeJS event emitter API, so you can use
265
276
  * `on`, `once`, etc.
@@ -441,122 +452,6 @@ Object.defineProperty(Document.prototype, '$op', {
441
452
  }
442
453
  });
443
454
 
444
- /*!
445
- * ignore
446
- */
447
-
448
- function $__applyDefaults(doc, fields, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) {
449
- const paths = Object.keys(doc.$__schema.paths);
450
- const plen = paths.length;
451
-
452
- for (let i = 0; i < plen; ++i) {
453
- let def;
454
- let curPath = '';
455
- const p = paths[i];
456
-
457
- if (p === '_id' && doc.$__.skipId) {
458
- continue;
459
- }
460
-
461
- const type = doc.$__schema.paths[p];
462
- const path = type.splitPath();
463
- const len = path.length;
464
- let included = false;
465
- let doc_ = doc._doc;
466
- for (let j = 0; j < len; ++j) {
467
- if (doc_ == null) {
468
- break;
469
- }
470
-
471
- const piece = path[j];
472
- curPath += (!curPath.length ? '' : '.') + piece;
473
-
474
- if (exclude === true) {
475
- if (curPath in fields) {
476
- break;
477
- }
478
- } else if (exclude === false && fields && !included) {
479
- const hasSubpaths = type.$isSingleNested || type.$isMongooseDocumentArray;
480
- if (curPath in fields || (hasSubpaths && hasIncludedChildren != null && hasIncludedChildren[curPath])) {
481
- included = true;
482
- } else if (hasIncludedChildren != null && !hasIncludedChildren[curPath]) {
483
- break;
484
- }
485
- }
486
-
487
- if (j === len - 1) {
488
- if (doc_[piece] !== void 0) {
489
- break;
490
- }
491
-
492
- if (typeof type.defaultValue === 'function') {
493
- if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
494
- break;
495
- }
496
- if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
497
- break;
498
- }
499
- } else if (!isBeforeSetters) {
500
- // Non-function defaults should always run **before** setters
501
- continue;
502
- }
503
-
504
- if (pathsToSkip && pathsToSkip[curPath]) {
505
- break;
506
- }
507
-
508
- if (fields && exclude !== null) {
509
- if (exclude === true) {
510
- // apply defaults to all non-excluded fields
511
- if (p in fields) {
512
- continue;
513
- }
514
-
515
- try {
516
- def = type.getDefault(doc, false);
517
- } catch (err) {
518
- doc.invalidate(p, err);
519
- break;
520
- }
521
-
522
- if (typeof def !== 'undefined') {
523
- doc_[piece] = def;
524
- doc.$__.activePaths.default(p);
525
- }
526
- } else if (included) {
527
- // selected field
528
- try {
529
- def = type.getDefault(doc, false);
530
- } catch (err) {
531
- doc.invalidate(p, err);
532
- break;
533
- }
534
-
535
- if (typeof def !== 'undefined') {
536
- doc_[piece] = def;
537
- doc.$__.activePaths.default(p);
538
- }
539
- }
540
- } else {
541
- try {
542
- def = type.getDefault(doc, false);
543
- } catch (err) {
544
- doc.invalidate(p, err);
545
- break;
546
- }
547
-
548
- if (typeof def !== 'undefined') {
549
- doc_[piece] = def;
550
- doc.$__.activePaths.default(p);
551
- }
552
- }
553
- } else {
554
- doc_ = doc_[piece];
555
- }
556
- }
557
- }
558
- }
559
-
560
455
  /*!
561
456
  * ignore
562
457
  */
@@ -792,10 +687,11 @@ Document.prototype.$__init = function(doc, opts) {
792
687
  this.$emit('init', this);
793
688
  this.constructor.emit('init', this);
794
689
 
795
- const hasIncludedChildren = this.$__.exclude === false && this.$__.fields ?
796
- $__hasIncludedChildren(this.$__.fields) :
690
+ const hasIncludedChildren = this.$__.exclude === false && this.$__.selected ?
691
+ $__hasIncludedChildren(this.$__.selected) :
797
692
  null;
798
- $__applyDefaults(this, this.$__.fields, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);
693
+
694
+ applyDefaults(this, this.$__.selected, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);
799
695
 
800
696
  return this;
801
697
  };
@@ -863,7 +759,13 @@ function init(self, obj, doc, opts, prefix) {
863
759
 
864
760
  if (schemaType && !wasPopulated) {
865
761
  try {
866
- doc[i] = schemaType.cast(obj[i], self, true);
762
+ if (opts && opts.setters) {
763
+ // Call applySetters with `init = false` because otherwise setters are a noop
764
+ const overrideInit = false;
765
+ doc[i] = schemaType.applySetters(obj[i], self, overrideInit);
766
+ } else {
767
+ doc[i] = schemaType.cast(obj[i], self, true);
768
+ }
867
769
  } catch (e) {
868
770
  self.invalidate(e.path, new ValidatorError({
869
771
  path: e.path,
@@ -1649,7 +1551,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
1649
1551
  return true;
1650
1552
  }
1651
1553
 
1652
- if (val === void 0 && path in this.$__.activePaths.states.default) {
1554
+ if (val === void 0 && path in this.$__.activePaths.getStatePaths('default')) {
1653
1555
  // we're just unsetting the default value which was never saved
1654
1556
  return false;
1655
1557
  }
@@ -1669,7 +1571,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
1669
1571
  if (!constructing &&
1670
1572
  val !== null &&
1671
1573
  val !== undefined &&
1672
- path in this.$__.activePaths.states.default &&
1574
+ path in this.$__.activePaths.getStatePaths('default') &&
1673
1575
  deepEqual(val, schema.getDefault(this, constructing))) {
1674
1576
  // a path with a default was $unset on the server
1675
1577
  // and the user is setting it to the same value again
@@ -1702,6 +1604,12 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
1702
1604
  schema, val, priorVal);
1703
1605
 
1704
1606
  if (shouldModify) {
1607
+ if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[path]) {
1608
+ delete this.$__.primitiveAtomics[path];
1609
+ if (Object.keys(this.$__.primitiveAtomics).length === 0) {
1610
+ delete this.$__.primitiveAtomics;
1611
+ }
1612
+ }
1705
1613
  this.markModified(pathToMark);
1706
1614
 
1707
1615
  // handle directly setting arrays (gh-1126)
@@ -1772,6 +1680,69 @@ Document.prototype.$__getValue = function(path) {
1772
1680
  return utils.getValue(path, this._doc);
1773
1681
  };
1774
1682
 
1683
+ /**
1684
+ * Increments the numeric value at `path` by the given `val`.
1685
+ * When you call `save()` on this document, Mongoose will send a
1686
+ * [`$inc`](https://www.mongodb.com/docs/manual/reference/operator/update/inc/)
1687
+ * as opposed to a `$set`.
1688
+ *
1689
+ * #### Example:
1690
+ *
1691
+ * const schema = new Schema({ counter: Number });
1692
+ * const Test = db.model('Test', schema);
1693
+ *
1694
+ * const doc = await Test.create({ counter: 0 });
1695
+ * doc.$inc('counter', 2);
1696
+ * await doc.save(); // Sends a `{ $inc: { counter: 2 } }` to MongoDB
1697
+ * doc.counter; // 2
1698
+ *
1699
+ * doc.counter += 2;
1700
+ * await doc.save(); // Sends a `{ $set: { counter: 2 } }` to MongoDB
1701
+ *
1702
+ * @param {String|Array} path path or paths to update
1703
+ * @param {Number} val increment `path` by this value
1704
+ * @return {Document} this
1705
+ */
1706
+
1707
+ Document.prototype.$inc = function $inc(path, val) {
1708
+ if (val == null) {
1709
+ val = 1;
1710
+ }
1711
+
1712
+ if (Array.isArray(path)) {
1713
+ path.forEach((p) => this.$inc(p, val));
1714
+ return this;
1715
+ }
1716
+
1717
+ const schemaType = this.$__path(path);
1718
+ if (schemaType == null) {
1719
+ if (this.$__.strictMode === 'throw') {
1720
+ throw new StrictModeError(path);
1721
+ } else if (this.$__.strictMode === true) {
1722
+ return this;
1723
+ }
1724
+ } else if (schemaType.instance !== 'Number') {
1725
+ this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path));
1726
+ return this;
1727
+ }
1728
+
1729
+ try {
1730
+ val = castNumber(val);
1731
+ } catch (err) {
1732
+ this.invalidate(path, new MongooseError.CastError('number', val, path, err));
1733
+ }
1734
+
1735
+ const currentValue = this.$__getValue(path);
1736
+
1737
+ this.$__setValue(path, currentValue + val);
1738
+
1739
+ this.$__.primitiveAtomics = this.$__.primitiveAtomics || {};
1740
+ this.$__.primitiveAtomics[path] = { $inc: val };
1741
+ this.markModified(path);
1742
+
1743
+ return this;
1744
+ };
1745
+
1775
1746
  /**
1776
1747
  * Sets a raw value for a path (no casting, setters, transformations)
1777
1748
  *
@@ -1981,7 +1952,7 @@ Document.prototype.$ignore = function(path) {
1981
1952
  */
1982
1953
 
1983
1954
  Document.prototype.directModifiedPaths = function() {
1984
- return Object.keys(this.$__.activePaths.states.modify);
1955
+ return Object.keys(this.$__.activePaths.getStatePaths('modify'));
1985
1956
  };
1986
1957
 
1987
1958
  /**
@@ -2065,7 +2036,7 @@ function _isEmpty(v) {
2065
2036
  Document.prototype.modifiedPaths = function(options) {
2066
2037
  options = options || {};
2067
2038
 
2068
- const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
2039
+ const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify'));
2069
2040
  const result = new Set();
2070
2041
 
2071
2042
  let i = 0;
@@ -2144,7 +2115,7 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
2144
2115
 
2145
2116
  Document.prototype.isModified = function(paths, modifiedPaths) {
2146
2117
  if (paths) {
2147
- const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
2118
+ const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify'));
2148
2119
  if (directModifiedPaths.length === 0) {
2149
2120
  return false;
2150
2121
  }
@@ -2202,7 +2173,7 @@ Document.prototype.$isDefault = function(path) {
2202
2173
  }
2203
2174
 
2204
2175
  if (typeof path === 'string' && path.indexOf(' ') === -1) {
2205
- return this.$__.activePaths.states.default.hasOwnProperty(path);
2176
+ return this.$__.activePaths.getStatePaths('default').hasOwnProperty(path);
2206
2177
  }
2207
2178
 
2208
2179
  let paths = path;
@@ -2210,7 +2181,7 @@ Document.prototype.$isDefault = function(path) {
2210
2181
  paths = paths.split(' ');
2211
2182
  }
2212
2183
 
2213
- return paths.some(path => this.$__.activePaths.states.default.hasOwnProperty(path));
2184
+ return paths.some(path => this.$__.activePaths.getStatePaths('default').hasOwnProperty(path));
2214
2185
  };
2215
2186
 
2216
2187
  /**
@@ -2264,7 +2235,7 @@ Document.prototype.isDirectModified = function(path) {
2264
2235
  }
2265
2236
 
2266
2237
  if (typeof path === 'string' && path.indexOf(' ') === -1) {
2267
- return this.$__.activePaths.states.modify.hasOwnProperty(path);
2238
+ return this.$__.activePaths.getStatePaths('modify').hasOwnProperty(path);
2268
2239
  }
2269
2240
 
2270
2241
  let paths = path;
@@ -2272,7 +2243,7 @@ Document.prototype.isDirectModified = function(path) {
2272
2243
  paths = paths.split(' ');
2273
2244
  }
2274
2245
 
2275
- return paths.some(path => this.$__.activePaths.states.modify.hasOwnProperty(path));
2246
+ return paths.some(path => this.$__.activePaths.getStatePaths('modify').hasOwnProperty(path));
2276
2247
  };
2277
2248
 
2278
2249
  /**
@@ -2289,7 +2260,7 @@ Document.prototype.isInit = function(path) {
2289
2260
  }
2290
2261
 
2291
2262
  if (typeof path === 'string' && path.indexOf(' ') === -1) {
2292
- return this.$__.activePaths.states.init.hasOwnProperty(path);
2263
+ return this.$__.activePaths.getStatePaths('init').hasOwnProperty(path);
2293
2264
  }
2294
2265
 
2295
2266
  let paths = path;
@@ -2297,7 +2268,7 @@ Document.prototype.isInit = function(path) {
2297
2268
  paths = paths.split(' ');
2298
2269
  }
2299
2270
 
2300
- return paths.some(path => this.$__.activePaths.states.init.hasOwnProperty(path));
2271
+ return paths.some(path => this.$__.activePaths.getStatePaths('init').hasOwnProperty(path));
2301
2272
  };
2302
2273
 
2303
2274
  /**
@@ -2533,7 +2504,7 @@ Document.prototype.$validate = Document.prototype.validate;
2533
2504
  */
2534
2505
 
2535
2506
  function _evaluateRequiredFunctions(doc) {
2536
- const requiredFields = Object.keys(doc.$__.activePaths.states.require);
2507
+ const requiredFields = Object.keys(doc.$__.activePaths.getStatePaths('require'));
2537
2508
  let i = 0;
2538
2509
  const len = requiredFields.length;
2539
2510
  for (i = 0; i < len; ++i) {
@@ -2561,7 +2532,7 @@ function _getPathsToValidate(doc) {
2561
2532
 
2562
2533
  _evaluateRequiredFunctions(doc);
2563
2534
  // only validate required fields when necessary
2564
- let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) {
2535
+ let paths = new Set(Object.keys(doc.$__.activePaths.getStatePaths('require')).filter(function(path) {
2565
2536
  if (!doc.$__isSelected(path) && !doc.$isModified(path)) {
2566
2537
  return false;
2567
2538
  }
@@ -2571,9 +2542,9 @@ function _getPathsToValidate(doc) {
2571
2542
  return true;
2572
2543
  }));
2573
2544
 
2574
- Object.keys(doc.$__.activePaths.states.init).forEach(addToPaths);
2575
- Object.keys(doc.$__.activePaths.states.modify).forEach(addToPaths);
2576
- Object.keys(doc.$__.activePaths.states.default).forEach(addToPaths);
2545
+ Object.keys(doc.$__.activePaths.getStatePaths('init')).forEach(addToPaths);
2546
+ Object.keys(doc.$__.activePaths.getStatePaths('modify')).forEach(addToPaths);
2547
+ Object.keys(doc.$__.activePaths.getStatePaths('default')).forEach(addToPaths);
2577
2548
  function addToPaths(p) { paths.add(p); }
2578
2549
 
2579
2550
  const subdocs = doc.$getAllSubdocs();
@@ -3296,8 +3267,8 @@ Document.prototype.$__reset = function reset() {
3296
3267
 
3297
3268
  this.$__.backup = {};
3298
3269
  this.$__.backup.activePaths = {
3299
- modify: Object.assign({}, this.$__.activePaths.states.modify),
3300
- default: Object.assign({}, this.$__.activePaths.states.default)
3270
+ modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')),
3271
+ default: Object.assign({}, this.$__.activePaths.getStatePaths('default'))
3301
3272
  };
3302
3273
  this.$__.backup.validationError = this.$__.validationError;
3303
3274
  this.$__.backup.errors = this.$errors;
@@ -4246,6 +4217,7 @@ Document.prototype.equals = function(doc) {
4246
4217
  *
4247
4218
  * #### Example:
4248
4219
  *
4220
+ * // Given a document, `populate()` lets you pull in referenced docs
4249
4221
  * await doc.populate([
4250
4222
  * 'stories',
4251
4223
  * { path: 'fans', sort: { name: -1 } }
@@ -4254,12 +4226,15 @@ Document.prototype.equals = function(doc) {
4254
4226
  * doc.stories[0].title; // 'Casino Royale'
4255
4227
  * doc.populated('fans'); // Array of ObjectIds
4256
4228
  *
4257
- * await doc.populate('fans', '-email');
4258
- * doc.fans[0].email // not populated
4229
+ * // If the referenced doc has been deleted, `populate()` will
4230
+ * // remove that entry from the array.
4231
+ * await Story.delete({ title: 'Casino Royale' });
4232
+ * await doc.populate('stories'); // Empty array
4259
4233
  *
4260
- * await doc.populate('author fans', '-email');
4261
- * doc.author.email // not populated
4262
- * doc.fans[0].email // not populated
4234
+ * // You can also pass additional query options to `populate()`,
4235
+ * // like projections:
4236
+ * await doc.populate('fans', '-email');
4237
+ * doc.fans[0].email // undefined because of 2nd param `select`
4263
4238
  *
4264
4239
  * @param {String|Object|Array} path either the path to populate or an object specifying all parameters, or either an array of those
4265
4240
  * @param {Object|String} [select] Field selection for the population query
@@ -4275,7 +4250,7 @@ Document.prototype.equals = function(doc) {
4275
4250
  * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
4276
4251
  * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
4277
4252
  * @param {Function} [callback] Callback
4278
- * @see population ./populate.html
4253
+ * @see population /docs/populate
4279
4254
  * @see Query#select #query_Query-select
4280
4255
  * @see Model.populate #model_Model-populate
4281
4256
  * @memberOf Document
@@ -5,6 +5,7 @@
5
5
  * Mongoose-specific errors.
6
6
  *
7
7
  * #### Example:
8
+ *
8
9
  * const Model = mongoose.model('Test', new mongoose.Schema({ answer: Number }));
9
10
  * const doc = new Model({ answer: 'not a number' });
10
11
  * const err = doc.validateSync();
@@ -52,6 +52,15 @@ class ValidationError extends MongooseError {
52
52
  * add message
53
53
  */
54
54
  addError(path, error) {
55
+ if (error instanceof ValidationError) {
56
+ const { errors } = error;
57
+ for (const errorPath of Object.keys(errors)) {
58
+ this.addError(`${path}.${errorPath}`, errors[errorPath]);
59
+ }
60
+
61
+ return;
62
+ }
63
+
55
64
  this.errors[path] = error;
56
65
  this.message = this._message + ': ' + _generateMessage(this);
57
66
  }