mongoose 5.7.4 → 5.7.8

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.
@@ -2,6 +2,19 @@
2
2
 
3
3
  const SchemaTypeOptions = require('./SchemaTypeOptions');
4
4
 
5
+ /**
6
+ * The options defined on a Number schematype.
7
+ *
8
+ * ####Example:
9
+ *
10
+ * const schema = new Schema({ count: Number });
11
+ * schema.path('count').options; // SchemaNumberOptions instance
12
+ *
13
+ * @api public
14
+ * @inherits SchemaTypeOptions
15
+ * @constructor SchemaNumberOptions
16
+ */
17
+
5
18
  class SchemaNumberOptions extends SchemaTypeOptions {}
6
19
 
7
20
  const opts = {
@@ -2,6 +2,19 @@
2
2
 
3
3
  const SchemaTypeOptions = require('./SchemaTypeOptions');
4
4
 
5
+ /**
6
+ * The options defined on an ObjectId schematype.
7
+ *
8
+ * ####Example:
9
+ *
10
+ * const schema = new Schema({ testId: mongoose.ObjectId });
11
+ * schema.path('testId').options; // SchemaObjectIdOptions instance
12
+ *
13
+ * @api public
14
+ * @inherits SchemaTypeOptions
15
+ * @constructor SchemaObjectIdOptions
16
+ */
17
+
5
18
  class SchemaObjectIdOptions extends SchemaTypeOptions {}
6
19
 
7
20
  const opts = {
@@ -2,6 +2,19 @@
2
2
 
3
3
  const SchemaTypeOptions = require('./SchemaTypeOptions');
4
4
 
5
+ /**
6
+ * The options defined on a string schematype.
7
+ *
8
+ * ####Example:
9
+ *
10
+ * const schema = new Schema({ name: String });
11
+ * schema.path('name').options; // SchemaStringOptions instance
12
+ *
13
+ * @api public
14
+ * @inherits SchemaTypeOptions
15
+ * @constructor SchemaStringOptions
16
+ */
17
+
5
18
  class SchemaStringOptions extends SchemaTypeOptions {}
6
19
 
7
20
  const opts = {
@@ -75,6 +88,32 @@ Object.defineProperty(SchemaStringOptions.prototype, 'trim', opts);
75
88
 
76
89
  Object.defineProperty(SchemaStringOptions.prototype, 'uppercase', opts);
77
90
 
91
+ /**
92
+ * If set, Mongoose will add a custom validator that ensures the given
93
+ * string's `length` is at least the given number.
94
+ *
95
+ * @api public
96
+ * @property minlength
97
+ * @memberOf SchemaStringOptions
98
+ * @type Number
99
+ * @instance
100
+ */
101
+
102
+ Object.defineProperty(SchemaStringOptions.prototype, 'minlength', opts);
103
+
104
+ /**
105
+ * If set, Mongoose will add a custom validator that ensures the given
106
+ * string's `length` is at most the given number.
107
+ *
108
+ * @api public
109
+ * @property maxlength
110
+ * @memberOf SchemaStringOptions
111
+ * @type Number
112
+ * @instance
113
+ */
114
+
115
+ Object.defineProperty(SchemaStringOptions.prototype, 'maxlength', opts);
116
+
78
117
  /*!
79
118
  * ignore
80
119
  */
@@ -2,6 +2,18 @@
2
2
 
3
3
  const utils = require('../utils');
4
4
 
5
+ /**
6
+ * The options defined on a schematype.
7
+ *
8
+ * ####Example:
9
+ *
10
+ * const schema = new Schema({ name: String });
11
+ * schema.path('name').options instanceof mongoose.SchemaTypeOptions; // true
12
+ *
13
+ * @api public
14
+ * @constructor SchemaTypeOptions
15
+ */
16
+
5
17
  class SchemaTypeOptions {
6
18
  constructor(obj) {
7
19
  if (obj == null) {
@@ -95,7 +107,8 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'ref', opts);
95
107
  Object.defineProperty(SchemaTypeOptions.prototype, 'select', opts);
96
108
 
97
109
  /**
98
- * If truthy, Mongoose will build an index on this path when the model is
110
+ * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will
111
+ * build an index on this path when the model is
99
112
  * compiled.
100
113
  *
101
114
  * @api public
@@ -108,7 +121,8 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'select', opts);
108
121
  Object.defineProperty(SchemaTypeOptions.prototype, 'index', opts);
109
122
 
110
123
  /**
111
- * If truthy, Mongoose will build a unique index on this path when the
124
+ * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose
125
+ * will build a unique index on this path when the
112
126
  * model is compiled. [The `unique` option is **not** a validator](/docs/validation.html#the-unique-option-is-not-a-validator).
113
127
  *
114
128
  * @api public
@@ -121,7 +135,8 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'index', opts);
121
135
  Object.defineProperty(SchemaTypeOptions.prototype, 'unique', opts);
122
136
 
123
137
  /**
124
- * If truthy, Mongoose will disallow changes to this path once the document
138
+ * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will
139
+ * disallow changes to this path once the document
125
140
  * is saved to the database for the first time. Read more about [immutability in Mongoose here](http://thecodebarbarian.com/whats-new-in-mongoose-5-6-immutable-properties.html).
126
141
  *
127
142
  * @api public
@@ -134,7 +149,8 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'unique', opts);
134
149
  Object.defineProperty(SchemaTypeOptions.prototype, 'immutable', opts);
135
150
 
136
151
  /**
137
- * If truthy, Mongoose will build a sparse index on this path.
152
+ * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will
153
+ * build a sparse index on this path.
138
154
  *
139
155
  * @api public
140
156
  * @property sparse
@@ -146,7 +162,8 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'immutable', opts);
146
162
  Object.defineProperty(SchemaTypeOptions.prototype, 'sparse', opts);
147
163
 
148
164
  /**
149
- * If truthy, Mongoose will build a text index on this path.
165
+ * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose
166
+ * will build a text index on this path.
150
167
  *
151
168
  * @api public
152
169
  * @property text
package/lib/query.js CHANGED
@@ -1941,7 +1941,7 @@ Query.prototype._find = wrapThunk(function(callback) {
1941
1941
  * // Using callbacks
1942
1942
  * Movie.find({ year: { $gte: 1980, $lte: 1989 } }, function(err, arr) {});
1943
1943
  *
1944
- * @param {Object} [filter] mongodb selector. If not specified, returns all documents.
1944
+ * @param {Object|ObjectId} [filter] mongodb selector. If not specified, returns all documents.
1945
1945
  * @param {Function} [callback]
1946
1946
  * @return {Query} this
1947
1947
  * @api public
@@ -107,7 +107,7 @@ exports.getDiscriminatorByValue = getDiscriminatorByValue;
107
107
  * @param {Object} doc
108
108
  * @param {Object} fields
109
109
  *
110
- * @return {Model}
110
+ * @return {Document}
111
111
  */
112
112
  exports.createModel = function createModel(model, doc, fields, userProvidedFields) {
113
113
  model.hooks.execPreSync('createModel', doc);
@@ -274,17 +274,19 @@ SingleNestedPath.prototype.doValidateSync = function(value, scope, options) {
274
274
  * const shapeSchema = Schema({ name: String }, { discriminatorKey: 'kind' });
275
275
  * const schema = Schema({ shape: shapeSchema });
276
276
  *
277
- * const singleNestedPath = parentSchema.path('child');
277
+ * const singleNestedPath = parentSchema.path('shape');
278
278
  * singleNestedPath.discriminator('Circle', Schema({ radius: Number }));
279
279
  *
280
280
  * @param {String} name
281
281
  * @param {Schema} schema fields to add to the schema for instances of this sub-class
282
+ * @param {String} [value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
283
+ * @return {Function} the constructor Mongoose will use for creating instances of this discriminator model
282
284
  * @see discriminators /docs/discriminators.html
283
285
  * @api public
284
286
  */
285
287
 
286
- SingleNestedPath.prototype.discriminator = function(name, schema, tiedValue) {
287
- discriminator(this.caster, name, schema, tiedValue);
288
+ SingleNestedPath.prototype.discriminator = function(name, schema, value) {
289
+ discriminator(this.caster, name, schema, value);
288
290
 
289
291
  this.caster.discriminators[name] = _createConstructor(schema, this.caster);
290
292
 
@@ -196,8 +196,8 @@ SchemaArray.prototype.checkRequired = function checkRequired(value, doc) {
196
196
  */
197
197
 
198
198
  SchemaArray.prototype.enum = function() {
199
- let arr = this; /* eslint consistent-this: 0 */
200
- while (true) { /* eslint no-constant-condition: 0 */
199
+ let arr = this;
200
+ while (true) {
201
201
  const instance = get(arr, 'caster.instance');
202
202
  if (instance === 'Array') {
203
203
  arr = arr.caster;
@@ -9,6 +9,7 @@ const CastError = require('../error/cast');
9
9
  const EventEmitter = require('events').EventEmitter;
10
10
  const SchemaType = require('../schematype');
11
11
  const discriminator = require('../helpers/model/discriminator');
12
+ const get = require('../helpers/get');
12
13
  const util = require('util');
13
14
  const utils = require('../utils');
14
15
  const getConstructor = require('../helpers/discriminator/getConstructor');
@@ -29,7 +30,7 @@ let Subdocument;
29
30
  * @api public
30
31
  */
31
32
 
32
- function DocumentArray(key, schema, options, schemaOptions) {
33
+ function DocumentArrayPath(key, schema, options, schemaOptions) {
33
34
  const EmbeddedDocument = _createConstructor(schema, options);
34
35
  EmbeddedDocument.prototype.$basePath = key;
35
36
 
@@ -54,6 +55,17 @@ function DocumentArray(key, schema, options, schemaOptions) {
54
55
  return arr;
55
56
  });
56
57
  }
58
+
59
+ const parentSchemaType = this;
60
+ this.$embeddedSchemaType = new SchemaType(key + '.$', {
61
+ required: get(this, 'schemaOptions.required', false)
62
+ });
63
+ this.$embeddedSchemaType.cast = function(value, doc, init) {
64
+ return parentSchemaType.cast(value, doc, init)[0];
65
+ };
66
+ this.$embeddedSchemaType.$isMongooseDocumentArrayElement = true;
67
+ this.$embeddedSchemaType.caster = this.Constructor;
68
+ this.$embeddedSchemaType.schema = this.schema;
57
69
  }
58
70
 
59
71
  /**
@@ -62,24 +74,23 @@ function DocumentArray(key, schema, options, schemaOptions) {
62
74
  *
63
75
  * @api public
64
76
  */
65
- DocumentArray.schemaName = 'DocumentArray';
77
+ DocumentArrayPath.schemaName = 'DocumentArray';
66
78
 
67
79
  /**
68
80
  * Options for all document arrays.
69
81
  *
70
82
  * - `castNonArrays`: `true` by default. If `false`, Mongoose will throw a CastError when a value isn't an array. If `true`, Mongoose will wrap the provided value in an array before casting.
71
83
  *
72
- * @static options
73
84
  * @api public
74
85
  */
75
86
 
76
- DocumentArray.options = { castNonArrays: true };
87
+ DocumentArrayPath.options = { castNonArrays: true };
77
88
 
78
89
  /*!
79
90
  * Inherits from ArrayType.
80
91
  */
81
- DocumentArray.prototype = Object.create(ArrayType.prototype);
82
- DocumentArray.prototype.constructor = DocumentArray;
92
+ DocumentArrayPath.prototype = Object.create(ArrayType.prototype);
93
+ DocumentArrayPath.prototype.constructor = DocumentArrayPath;
83
94
 
84
95
  /*!
85
96
  * Ignore
@@ -122,11 +133,25 @@ function _createConstructor(schema, options, baseClass) {
122
133
  return EmbeddedDocument;
123
134
  }
124
135
 
125
- /*!
126
- * Ignore
136
+ /**
137
+ * Adds a discriminator to this document array.
138
+ *
139
+ * ####Example:
140
+ * const shapeSchema = Schema({ name: String }, { discriminatorKey: 'kind' });
141
+ * const schema = Schema({ shapes: [shapeSchema] });
142
+ *
143
+ * const docArrayPath = parentSchema.path('shapes');
144
+ * docArrayPath.discriminator('Circle', Schema({ radius: Number }));
145
+ *
146
+ * @param {String} name
147
+ * @param {Schema} schema fields to add to the schema for instances of this sub-class
148
+ * @param {String} [value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
149
+ * @see discriminators /docs/discriminators.html
150
+ * @return {Function} the constructor Mongoose will use for creating instances of this discriminator model
151
+ * @api public
127
152
  */
128
153
 
129
- DocumentArray.prototype.discriminator = function(name, schema, tiedValue) {
154
+ DocumentArrayPath.prototype.discriminator = function(name, schema, tiedValue) {
130
155
  if (typeof name === 'function') {
131
156
  name = utils.getFunctionName(name);
132
157
  }
@@ -155,7 +180,7 @@ DocumentArray.prototype.discriminator = function(name, schema, tiedValue) {
155
180
  * @api private
156
181
  */
157
182
 
158
- DocumentArray.prototype.doValidate = function(array, fn, scope, options) {
183
+ DocumentArrayPath.prototype.doValidate = function(array, fn, scope, options) {
159
184
  // lazy load
160
185
  MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentarray'));
161
186
 
@@ -231,7 +256,7 @@ DocumentArray.prototype.doValidate = function(array, fn, scope, options) {
231
256
  * @api private
232
257
  */
233
258
 
234
- DocumentArray.prototype.doValidateSync = function(array, scope) {
259
+ DocumentArrayPath.prototype.doValidateSync = function(array, scope) {
235
260
  const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope);
236
261
  if (schemaTypeError != null) {
237
262
  schemaTypeError.$isArrayValidatorError = true;
@@ -277,7 +302,7 @@ DocumentArray.prototype.doValidateSync = function(array, scope) {
277
302
  * ignore
278
303
  */
279
304
 
280
- DocumentArray.prototype.getDefault = function(scope) {
305
+ DocumentArrayPath.prototype.getDefault = function(scope) {
281
306
  let ret = typeof this.defaultValue === 'function'
282
307
  ? this.defaultValue.call(scope)
283
308
  : this.defaultValue;
@@ -316,7 +341,7 @@ DocumentArray.prototype.getDefault = function(scope) {
316
341
  * @api private
317
342
  */
318
343
 
319
- DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
344
+ DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) {
320
345
  // lazy load
321
346
  MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentarray'));
322
347
 
@@ -326,7 +351,7 @@ DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
326
351
  const _opts = { transform: false, virtuals: false };
327
352
 
328
353
  if (!Array.isArray(value)) {
329
- if (!init && !DocumentArray.options.castNonArrays) {
354
+ if (!init && !DocumentArrayPath.options.castNonArrays) {
330
355
  throw new CastError('DocumentArray', util.inspect(value), this.path);
331
356
  }
332
357
  // gh-2442 mark whole array as modified if we're initializing a doc from
@@ -416,7 +441,7 @@ DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
416
441
  * ignore
417
442
  */
418
443
 
419
- DocumentArray.prototype.clone = function() {
444
+ DocumentArrayPath.prototype.clone = function() {
420
445
  const options = Object.assign({}, this.options);
421
446
  const schematype = new this.constructor(this.path, this.schema, options, this.schemaOptions);
422
447
  schematype.validators = this.validators.slice();
@@ -429,7 +454,7 @@ DocumentArray.prototype.clone = function() {
429
454
  * Scopes paths selected in a query to this array.
430
455
  * Necessary for proper default application of subdocument values.
431
456
  *
432
- * @param {DocumentArray} array - the array to scope `fields` paths
457
+ * @param {DocumentArrayPath} array - the array to scope `fields` paths
433
458
  * @param {Object|undefined} fields - the root fields selected in the query
434
459
  * @param {Boolean|undefined} init - if we are being created part of a query result
435
460
  */
@@ -469,4 +494,4 @@ function scopePaths(array, fields, init) {
469
494
  * Module exports.
470
495
  */
471
496
 
472
- module.exports = DocumentArray;
497
+ module.exports = DocumentArrayPath;
@@ -43,7 +43,12 @@ SchemaString.schemaName = 'String';
43
43
  */
44
44
  SchemaString.prototype = Object.create(SchemaType.prototype);
45
45
  SchemaString.prototype.constructor = SchemaString;
46
- SchemaString.prototype.OptionsConstructor = SchemaStringOptions;
46
+ Object.defineProperty(SchemaString.prototype, 'OptionsConstructor', {
47
+ configurable: false,
48
+ enumerable: false,
49
+ writable: false,
50
+ value: SchemaStringOptions
51
+ });
47
52
 
48
53
  /*!
49
54
  * ignore
@@ -574,17 +579,23 @@ function handleArray(val) {
574
579
  });
575
580
  }
576
581
 
577
- SchemaString.prototype.$conditionalHandlers =
578
- utils.options(SchemaType.prototype.$conditionalHandlers, {
579
- $all: handleArray,
580
- $gt: handleSingle,
581
- $gte: handleSingle,
582
- $lt: handleSingle,
583
- $lte: handleSingle,
584
- $options: String,
585
- $regex: handleSingle,
586
- $not: handleSingle
587
- });
582
+ const $conditionalHandlers = utils.options(SchemaType.prototype.$conditionalHandlers, {
583
+ $all: handleArray,
584
+ $gt: handleSingle,
585
+ $gte: handleSingle,
586
+ $lt: handleSingle,
587
+ $lte: handleSingle,
588
+ $options: String,
589
+ $regex: handleSingle,
590
+ $not: handleSingle
591
+ });
592
+
593
+ Object.defineProperty(SchemaString.prototype, '$conditionalHandlers', {
594
+ configurable: false,
595
+ enumerable: false,
596
+ writable: false,
597
+ value: Object.freeze($conditionalHandlers)
598
+ });
588
599
 
589
600
  /**
590
601
  * Casts contents for queries.
package/lib/schema.js CHANGED
@@ -402,7 +402,6 @@ Schema.prototype.add = function add(obj, prefix) {
402
402
  // the `_id` option. This behavior never worked before 5.4.11 but numerous
403
403
  // codebases use it (see gh-7516, gh-7512).
404
404
  if (obj._id === false && prefix == null) {
405
- delete obj._id;
406
405
  this.options._id = false;
407
406
  }
408
407
 
@@ -417,6 +416,10 @@ Schema.prototype.add = function add(obj, prefix) {
417
416
  throw new TypeError('Invalid value for schema path `' + fullPath +
418
417
  '`, got value "' + obj[key] + '"');
419
418
  }
419
+ // Retain `_id: false` but don't set it as a path, re: gh-8274.
420
+ if (key === '_id' && obj[key] === false) {
421
+ continue;
422
+ }
420
423
 
421
424
  if (Array.isArray(obj[key]) && obj[key].length === 1 && obj[key][0] == null) {
422
425
  throw new TypeError('Invalid value for schema Array path `' + fullPath +
@@ -1179,16 +1182,7 @@ function getPositionalPathType(self, path) {
1179
1182
 
1180
1183
  if (i === last && val && !/\D/.test(subpath)) {
1181
1184
  if (val.$isMongooseDocumentArray) {
1182
- const oldVal = val;
1183
- val = new SchemaType(subpath, {
1184
- required: get(val, 'schemaOptions.required', false)
1185
- });
1186
- val.cast = function(value, doc, init) {
1187
- return oldVal.cast(value, doc, init)[0];
1188
- };
1189
- val.$isMongooseDocumentArrayElement = true;
1190
- val.caster = oldVal.caster;
1191
- val.schema = oldVal.schema;
1185
+ val = val.$embeddedSchemaType;
1192
1186
  } else if (val instanceof MongooseTypes.Array) {
1193
1187
  // StringSchema, NumberSchema, etc
1194
1188
  val = val.caster;
@@ -1637,8 +1631,8 @@ Schema.prototype.indexes = function() {
1637
1631
  * @param {String|Model} [options.ref] model name or model instance. Marks this as a [populate virtual](populate.html#populate-virtuals).
1638
1632
  * @param {String|Function} [options.localField] Required for populate virtuals. See [populate virtual docs](populate.html#populate-virtuals) for more information.
1639
1633
  * @param {String|Function} [options.foreignField] Required for populate virtuals. See [populate virtual docs](populate.html#populate-virtuals) for more information.
1640
- * @param {Boolean|Function} [options.justOne=false] Only works with populate virtuals. If truthy, will be a single doc or `null`. Otherwise, the populate virtual will be an array.
1641
- * @param {Boolean} [options.count=false] Only works with populate virtuals. If truthy, this populate virtual will contain the number of documents rather than the documents themselves when you `populate()`.
1634
+ * @param {Boolean|Function} [options.justOne=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), will be a single doc or `null`. Otherwise, the populate virtual will be an array.
1635
+ * @param {Boolean} [options.count=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), this populate virtual will contain the number of documents rather than the documents themselves when you `populate()`.
1642
1636
  * @return {VirtualType}
1643
1637
  */
1644
1638
 
package/lib/schematype.js CHANGED
@@ -29,7 +29,7 @@ const ValidatorError = MongooseError.ValidatorError;
29
29
  * schema.path('name') instanceof SchemaType; // true
30
30
  *
31
31
  * @param {String} path
32
- * @param {Object} [options]
32
+ * @param {SchemaTypeOptions} [options] See [SchemaTypeOptions docs](/docs/api/schematypeoptions.html)
33
33
  * @param {String} [instance]
34
34
  * @api public
35
35
  */
@@ -664,7 +664,7 @@ SchemaType.prototype.get = function(fn) {
664
664
  * Product.on('error', handleError);
665
665
  *
666
666
  * @param {RegExp|Function|Object} obj validator function, or hash describing options
667
- * @param {Function} [obj.validator] validator function. If the validator function returns `undefined` or a truthy value, validation succeeds. If it returns falsy (except `undefined`) or throws an error, validation fails.
667
+ * @param {Function} [obj.validator] validator function. If the validator function returns `undefined` or a truthy value, validation succeeds. If it returns [falsy](https://masteringjs.io/tutorials/fundamentals/falsy) (except `undefined`) or throws an error, validation fails.
668
668
  * @param {String|Function} [obj.message] optional error message. If function, should return the error message as a string
669
669
  * @param {Boolean} [obj.propsParameter=false] If true, Mongoose will pass the validator properties object (with the `validator` function, `message`, etc.) as the 2nd arg to the validator function. This is disabled by default because many validators [rely on positional args](https://github.com/chriso/validator.js#validators), so turning this on may cause unpredictable behavior in external validators.
670
670
  * @param {String|Function} [errorMsg] optional error message. If function, should return the error message as a string
@@ -1265,7 +1265,7 @@ SchemaType._isRef = function(self, value, doc, init) {
1265
1265
  // - setting / pushing values after population
1266
1266
  const path = doc.$__fullPath(self.path);
1267
1267
  const owner = doc.ownerDocument ? doc.ownerDocument() : doc;
1268
- ref = owner.populated(path);
1268
+ ref = owner.populated(path) || doc.populated(self.path);
1269
1269
  }
1270
1270
 
1271
1271
  if (ref) {
@@ -628,8 +628,9 @@ class CoreMongooseArray extends Array {
628
628
 
629
629
  _checkManualPopulation(this, arguments);
630
630
 
631
+ const parent = this[arrayParentSymbol];
631
632
  let values = [].map.call(arguments, this._mapCast, this);
632
- values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol], undefined,
633
+ values = this[arraySchemaSymbol].applySetters(values, parent, undefined,
633
634
  undefined, { skipDocumentArrayCast: true });
634
635
  const ret = [].push.apply(this, values);
635
636
 
@@ -723,6 +724,7 @@ class CoreMongooseArray extends Array {
723
724
  * @api public
724
725
  * @method sort
725
726
  * @memberOf MongooseArray
727
+ * @see https://masteringjs.io/tutorials/fundamentals/array-sort
726
728
  */
727
729
 
728
730
  sort() {
@@ -741,6 +743,7 @@ class CoreMongooseArray extends Array {
741
743
  * @api public
742
744
  * @method splice
743
745
  * @memberOf MongooseArray
746
+ * @see https://masteringjs.io/tutorials/fundamentals/array-splice
744
747
  */
745
748
 
746
749
  splice() {
@@ -172,6 +172,64 @@ class CoreDocumentArray extends CoreMongooseArray {
172
172
  }));
173
173
  }
174
174
 
175
+ /**
176
+ * Wraps [`Array#push`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push) with proper change tracking.
177
+ *
178
+ * @param {Object} [args...]
179
+ * @api public
180
+ * @method push
181
+ * @memberOf MongooseDocumentArray
182
+ */
183
+
184
+ push() {
185
+ const ret = super.push.apply(this, arguments);
186
+
187
+ _updateParentPopulated(this);
188
+
189
+ return ret;
190
+ }
191
+
192
+ /**
193
+ * Pulls items from the array atomically.
194
+ *
195
+ * @param {Object} [args...]
196
+ * @api public
197
+ * @method pull
198
+ * @memberOf MongooseDocumentArray
199
+ */
200
+
201
+ pull() {
202
+ const ret = super.pull.apply(this, arguments);
203
+
204
+ _updateParentPopulated(this);
205
+
206
+ return ret;
207
+ }
208
+
209
+ /**
210
+ * Wraps [`Array#shift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking.
211
+ */
212
+
213
+ shift() {
214
+ const ret = super.shift.apply(this, arguments);
215
+
216
+ _updateParentPopulated(this);
217
+
218
+ return ret;
219
+ }
220
+
221
+ /**
222
+ * Wraps [`Array#splice`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice) with proper change tracking and casting.
223
+ */
224
+
225
+ splice() {
226
+ const ret = super.splice.apply(this, arguments);
227
+
228
+ _updateParentPopulated(this);
229
+
230
+ return ret;
231
+ }
232
+
175
233
  /**
176
234
  * Helper for console.log
177
235
  *
@@ -254,6 +312,29 @@ if (util.inspect.custom) {
254
312
  CoreDocumentArray.prototype.inspect;
255
313
  }
256
314
 
315
+ /*!
316
+ * If this is a document array, each element may contain single
317
+ * populated paths, so we need to modify the top-level document's
318
+ * populated cache. See gh-8247, gh-8265.
319
+ */
320
+
321
+ function _updateParentPopulated(arr) {
322
+ const parent = arr[arrayParentSymbol];
323
+ if (parent.$__.populated != null) {
324
+ const populatedPaths = Object.keys(parent.$__.populated).
325
+ filter(p => p.startsWith(arr[arrayPathSymbol] + '.'));
326
+
327
+ for (const path of populatedPaths) {
328
+ const remnant = path.slice((arr[arrayPathSymbol] + '.').length);
329
+ if (!Array.isArray(parent.$__.populated[path].value)) {
330
+ continue;
331
+ }
332
+
333
+ parent.$__.populated[path].value = arr.map(val => val.populated(remnant));
334
+ }
335
+ }
336
+ }
337
+
257
338
  /**
258
339
  * DocumentArray constructor
259
340
  *
@@ -115,6 +115,19 @@ Subdocument.prototype.markModified = function(path) {
115
115
  }
116
116
  };
117
117
 
118
+ Subdocument.prototype.isModified = function(paths, modifiedPaths) {
119
+ if (this.$parent && this.$basePath) {
120
+ if (Array.isArray(paths) || typeof paths === 'string') {
121
+ paths = (Array.isArray(paths) ? paths : paths.split(' '));
122
+ paths = paths.map(p => [this.$basePath, p].join('.'));
123
+ }
124
+
125
+ return this.$parent.isModified(paths, modifiedPaths);
126
+ }
127
+
128
+ return Document.prototype.isModified(paths, modifiedPaths);
129
+ };
130
+
118
131
  /**
119
132
  * Marks a path as valid, removing existing validation errors.
120
133
  *