mongoose 5.4.23 → 5.5.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.
@@ -26,7 +26,13 @@ module.exports = function(schema) {
26
26
 
27
27
  // Validate
28
28
  if (shouldValidate) {
29
- this.validate(function(error) {
29
+ const hasValidateModifiedOnlyOption = options &&
30
+ (typeof options === 'object') &&
31
+ ('validateModifiedOnly' in options);
32
+ const validateOptions = hasValidateModifiedOnlyOption ?
33
+ {validateModifiedOnly: options.validateModifiedOnly} :
34
+ null;
35
+ this.validate(validateOptions, function(error) {
30
36
  return _this.schema.s.hooks.execPost('save:error', _this, [ _this], { error: error }, function(error) {
31
37
  next(error);
32
38
  });
package/lib/query.js CHANGED
@@ -845,6 +845,47 @@ Query.prototype.mod = function() {
845
845
  * @api public
846
846
  */
847
847
 
848
+ /**
849
+ * Get/set the current projection (AKA fields). Pass `null` to remove the
850
+ * current projection.
851
+ *
852
+ * Unlike `projection()`, the `select()` function modifies the current
853
+ * projection in place. This function overwrites the existing projection.
854
+ *
855
+ * ####Example:
856
+ *
857
+ * const q = Model.find();
858
+ * q.projection(); // null
859
+ *
860
+ * q.select('a b');
861
+ * q.projection(); // { a: 1, b: 1 }
862
+ *
863
+ * q.projection({ c: 1 });
864
+ * q.projection(); // { c: 1 }
865
+ *
866
+ * q.projection(null);
867
+ * q.projection(); // null
868
+ *
869
+ *
870
+ * @method projection
871
+ * @memberOf Query
872
+ * @instance
873
+ * @param {Object|null} arg
874
+ * @return {Object} the current projection
875
+ * @api public
876
+ */
877
+
878
+ Query.prototype.projection = function(arg) {
879
+ if (arguments.length === 0) {
880
+ return this._fields;
881
+ }
882
+
883
+ this._fields = {};
884
+ this._userProvidedFields = {};
885
+ this.select(arg);
886
+ return this._fields;
887
+ };
888
+
848
889
  /**
849
890
  * Specifies which document fields to include or exclude (also known as the query "projection")
850
891
  *
@@ -2305,6 +2346,27 @@ Query.prototype.countDocuments = function(conditions, callback) {
2305
2346
  return this;
2306
2347
  };
2307
2348
 
2349
+ /**
2350
+ * Thunk around findOne()
2351
+ *
2352
+ * @param {Function} [callback]
2353
+ * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/
2354
+ * @api private
2355
+ */
2356
+
2357
+ Query.prototype.__distinct = wrapThunk(function __distinct(callback) {
2358
+ this._castConditions();
2359
+
2360
+ if (this.error()) {
2361
+ callback(this.error());
2362
+ return null;
2363
+ }
2364
+
2365
+ // don't pass in the conditions because we already merged them in
2366
+ this._collection.collection.
2367
+ distinct(this._distinct, this._conditions, callback);
2368
+ });
2369
+
2308
2370
  /**
2309
2371
  * Declares or executes a distict() operation.
2310
2372
  *
@@ -2351,16 +2413,16 @@ Query.prototype.distinct = function(field, conditions, callback) {
2351
2413
  this.error(new ObjectParameterError(conditions, 'filter', 'distinct'));
2352
2414
  }
2353
2415
 
2354
- if (callback != null) {
2355
- this._castConditions();
2416
+ if (field != null) {
2417
+ this._distinct = field;
2418
+ }
2419
+ this.op = 'distinct';
2356
2420
 
2357
- if (this.error() != null) {
2358
- callback(this.error());
2359
- return this;
2360
- }
2421
+ if (callback != null) {
2422
+ this.__distinct(callback);
2361
2423
  }
2362
2424
 
2363
- return Query.base.distinct.call(this, {}, field, callback);
2425
+ return this;
2364
2426
  };
2365
2427
 
2366
2428
  /**
@@ -3357,12 +3419,7 @@ Query.prototype._findAndModify = function(type, callback) {
3357
3419
  if (error) {
3358
3420
  return callback(error);
3359
3421
  }
3360
- if (castedDoc && castedDoc.toBSON) {
3361
- castedDoc = castedDoc.toBSON();
3362
- }
3363
- _this._collection.findAndModify(castedQuery, castedDoc, opts, _wrapThunkCallback(_this, function(error, res) {
3364
- return cb(error, res ? res.value : res, res);
3365
- }));
3422
+ _legacyFindAndModify.call(_this, castedQuery, castedDoc, opts, callback);
3366
3423
  };
3367
3424
 
3368
3425
  try {
@@ -3371,12 +3428,7 @@ Query.prototype._findAndModify = function(type, callback) {
3371
3428
  callback(error);
3372
3429
  }
3373
3430
  } else {
3374
- if (castedDoc && castedDoc.toBSON) {
3375
- castedDoc = castedDoc.toBSON();
3376
- }
3377
- this._collection.findAndModify(castedQuery, castedDoc, opts, _wrapThunkCallback(_this, function(error, res) {
3378
- return cb(error, res ? res.value : res, res);
3379
- }));
3431
+ _legacyFindAndModify.call(_this, castedQuery, castedDoc, opts, callback);
3380
3432
  }
3381
3433
 
3382
3434
  return this;
@@ -3393,6 +3445,23 @@ function _completeOneLean(doc, res, opts, callback) {
3393
3445
  return callback(null, doc);
3394
3446
  }
3395
3447
 
3448
+
3449
+ /*!
3450
+ * ignore
3451
+ */
3452
+
3453
+ const _legacyFindAndModify = util.deprecate(function(filter, update, opts, cb) {
3454
+ if (update && update.toBSON) {
3455
+ update = update.toBSON();
3456
+ }
3457
+ const collection = this._collection;
3458
+ collection.findAndModify(filter, update, opts, _wrapThunkCallback(this, function(error, res) {
3459
+ return cb(error, res ? res.value : res, res);
3460
+ }));
3461
+ }, 'Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the ' +
3462
+ '`useFindAndModify` option set to false are deprecated. See: ' +
3463
+ 'https://mongoosejs.com/docs/deprecations.html#-findandmodify-');
3464
+
3396
3465
  /*!
3397
3466
  * Override mquery.prototype._mergeUpdate to handle mongoose objects in
3398
3467
  * updates.
@@ -3654,9 +3723,16 @@ Query.prototype._replaceOne = wrapThunk(function(callback) {
3654
3723
  * @param {Object} [doc] the update command
3655
3724
  * @param {Object} [options]
3656
3725
  * @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.
3657
- * @param {Function} [callback] optional, params are (error, writeOpResult)
3726
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3727
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3728
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3729
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
3730
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3731
+ * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
3732
+ * @param {Function} [callback] params are (error, writeOpResult)
3658
3733
  * @return {Query} this
3659
3734
  * @see Model.update #model_Model.update
3735
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3660
3736
  * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
3661
3737
  * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3662
3738
  * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
@@ -3714,9 +3790,15 @@ Query.prototype.update = function(conditions, doc, options, callback) {
3714
3790
  * @param {Object} [options]
3715
3791
  * @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.
3716
3792
  * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3717
- * @param {Function} [callback] optional params are (error, writeOpResult)
3793
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3794
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3795
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
3796
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3797
+ * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
3798
+ * @param {Function} [callback] params are (error, writeOpResult)
3718
3799
  * @return {Query} this
3719
3800
  * @see Model.update #model_Model.update
3801
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3720
3802
  * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
3721
3803
  * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3722
3804
  * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
@@ -3775,9 +3857,15 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
3775
3857
  * @param {Object} [options]
3776
3858
  * @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.
3777
3859
  * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3860
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3861
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3862
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
3863
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3864
+ * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
3778
3865
  * @param {Function} [callback] params are (error, writeOpResult)
3779
3866
  * @return {Query} this
3780
3867
  * @see Model.update #model_Model.update
3868
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3781
3869
  * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
3782
3870
  * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3783
3871
  * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
@@ -3832,10 +3920,17 @@ Query.prototype.updateOne = function(conditions, doc, options, callback) {
3832
3920
  * @param {Object} [criteria]
3833
3921
  * @param {Object} [doc] the update command
3834
3922
  * @param {Object} [options]
3923
+ * @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.
3835
3924
  * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3836
- * @param {Function} [callback] optional params are (error, writeOpResult)
3925
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3926
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3927
+ * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern)
3928
+ * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server.
3929
+ * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
3930
+ * @param {Function} [callback] params are (error, writeOpResult)
3837
3931
  * @return {Query} this
3838
3932
  * @see Model.update #model_Model.update
3933
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3839
3934
  * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
3840
3935
  * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3841
3936
  * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
@@ -4241,10 +4336,10 @@ function castDoc(query, overwrite) {
4241
4336
  * })
4242
4337
  *
4243
4338
  * Kitten.find().populate({
4244
- * path: 'owner'
4245
- * , select: 'name'
4246
- * , match: { color: 'black' }
4247
- * , options: { sort: { name: -1 }}
4339
+ * path: 'owner',
4340
+ * select: 'name',
4341
+ * match: { color: 'black' },
4342
+ * options: { sort: { name: -1 } }
4248
4343
  * }).exec(function (err, kittens) {
4249
4344
  * console.log(kittens[0].owner.name) // Zoopa
4250
4345
  * })
@@ -4254,13 +4349,21 @@ function castDoc(query, overwrite) {
4254
4349
  * console.log(kittens[0].owner.name) // Zoopa
4255
4350
  * })
4256
4351
  *
4257
- * Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback.
4352
+ * Paths are populated after the query executes and a response is received. A
4353
+ * separate query is then executed for each path specified for population. After
4354
+ * a response for each query has also been returned, the results are passed to
4355
+ * the callback.
4258
4356
  *
4259
4357
  * @param {Object|String} path either the path to populate or an object specifying all parameters
4260
4358
  * @param {Object|String} [select] Field selection for the population query
4261
4359
  * @param {Model} [model] The model you wish to use for population. If not specified, populate will look up the model by the name in the Schema's `ref` field.
4262
4360
  * @param {Object} [match] Conditions for the population query
4263
4361
  * @param {Object} [options] Options for the population query (sort, etc)
4362
+ * @param {String} [options.path=null] The path to populate.
4363
+ * @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
4364
+ * @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options).
4365
+ * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
4366
+ * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://docs.mongodb.com/manual/tutorial/query-documents/), or a function that returns a filter object.
4264
4367
  * @see population ./populate.html
4265
4368
  * @see Query#select #query_Query-select
4266
4369
  * @see Model.populate #model_Model.populate
@@ -4504,13 +4607,16 @@ Query.prototype.cursor = function cursor(opts) {
4504
4607
  this.setOptions(opts);
4505
4608
  }
4506
4609
 
4610
+ const options = Object.assign({}, this.options, {
4611
+ projection: this.projection()
4612
+ });
4507
4613
  try {
4508
4614
  this.cast(this.model);
4509
4615
  } catch (err) {
4510
- return (new QueryCursor(this, this.options))._markError(err);
4616
+ return (new QueryCursor(this, options))._markError(err);
4511
4617
  }
4512
4618
 
4513
- return new QueryCursor(this, this.options);
4619
+ return new QueryCursor(this, options);
4514
4620
  };
4515
4621
 
4516
4622
  // the rest of these are basically to support older Mongoose syntax with mquery
@@ -146,6 +146,56 @@ SchemaArray.options = { castNonArrays: true };
146
146
  SchemaArray.prototype = Object.create(SchemaType.prototype);
147
147
  SchemaArray.prototype.constructor = SchemaArray;
148
148
 
149
+ /*!
150
+ * ignore
151
+ */
152
+
153
+ SchemaArray._checkRequired = SchemaType.prototype.checkRequired;
154
+
155
+ /**
156
+ * Override the function the required validator uses to check whether an array
157
+ * passes the `required` check.
158
+ *
159
+ * ####Example:
160
+ *
161
+ * // Require non-empty array to pass `required` check
162
+ * mongoose.Schema.Types.Array.checkRequired(v => Array.isArray(v) && v.length);
163
+ *
164
+ * const M = mongoose.model({ arr: { type: Array, required: true } });
165
+ * new M({ arr: [] }).validateSync(); // `null`, validation fails!
166
+ *
167
+ * @param {Function} fn
168
+ * @return {Function}
169
+ * @function checkRequired
170
+ * @static
171
+ * @api public
172
+ */
173
+
174
+ SchemaArray.checkRequired = SchemaType.checkRequired;
175
+
176
+ /**
177
+ * Check if the given value satisfies the `required` validator.
178
+ *
179
+ * @param {Any} value
180
+ * @param {Document} doc
181
+ * @return {Boolean}
182
+ * @api public
183
+ */
184
+
185
+ SchemaArray.prototype.checkRequired = function checkRequired(value, doc) {
186
+ if (SchemaType._isRef(this, value, doc, true)) {
187
+ return !!value;
188
+ }
189
+
190
+ // `require('util').inherits()` does **not** copy static properties, and
191
+ // plugins like mongoose-float use `inherits()` for pre-ES6.
192
+ const _checkRequired = typeof this.constructor.checkRequired == 'function' ?
193
+ this.constructor.checkRequired() :
194
+ SchemaArray.checkRequired();
195
+
196
+ return _checkRequired(value);
197
+ };
198
+
149
199
  /**
150
200
  * Adds an enum validator if this is an array of strings. Equivalent to
151
201
  * `SchemaString.prototype.enum()`
@@ -13,6 +13,8 @@ const util = require('util');
13
13
  const utils = require('../utils');
14
14
  const getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
15
15
 
16
+ const arrayParentSymbol = require('../helpers/symbols').arrayParentSymbol;
17
+
16
18
  let MongooseDocumentArray;
17
19
  let Subdocument;
18
20
 
@@ -207,8 +209,8 @@ DocumentArray.prototype.doValidate = function(array, fn, scope, options) {
207
209
  // If you set the array index directly, the doc might not yet be
208
210
  // a full fledged mongoose subdoc, so make it into one.
209
211
  if (!(doc instanceof Subdocument)) {
210
- doc = array[i] = new _this.casterConstructor(doc, array, undefined,
211
- undefined, i);
212
+ const Constructor = getConstructor(_this, array[i]);
213
+ doc = array[i] = new Constructor(doc, array, undefined, undefined, i);
212
214
  }
213
215
 
214
216
  doc.$__validate(callback);
@@ -255,8 +257,8 @@ DocumentArray.prototype.doValidateSync = function(array, scope) {
255
257
  // If you set the array index directly, the doc might not yet be
256
258
  // a full fledged mongoose subdoc, so make it into one.
257
259
  if (!(doc instanceof Subdocument)) {
258
- doc = array[i] = new this.casterConstructor(doc, array, undefined,
259
- undefined, i);
260
+ const Constructor = getConstructor(this, array[i]);
261
+ doc = array[i] = new Constructor(doc, array, undefined, undefined, i);
260
262
  }
261
263
 
262
264
  const subdocValidateError = doc.validateSync();
@@ -290,15 +292,16 @@ DocumentArray.prototype.getDefault = function(scope) {
290
292
  }
291
293
 
292
294
  ret = new MongooseDocumentArray(ret, this.path, scope);
293
- const _parent = ret._parent;
294
- ret._parent = null;
295
+ const _parent = ret[arrayParentSymbol];
296
+ ret[arrayParentSymbol] = null;
295
297
 
296
298
  for (let i = 0; i < ret.length; ++i) {
297
- ret[i] = new this.Constructor(ret[i], ret, undefined,
299
+ const Constructor = getConstructor(this, ret[i]);
300
+ ret[i] = new Constructor(ret[i], ret, undefined,
298
301
  undefined, i);
299
302
  }
300
303
 
301
- ret._parent = _parent;
304
+ ret[arrayParentSymbol] = _parent;
302
305
 
303
306
  return ret;
304
307
  };
@@ -349,24 +352,11 @@ DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
349
352
  continue;
350
353
  }
351
354
 
352
- let Constructor = this.casterConstructor;
353
- if (Constructor.discriminators &&
354
- Constructor.schema &&
355
- Constructor.schema.options &&
356
- typeof value[i][Constructor.schema.options.discriminatorKey] === 'string') {
357
- if (Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]]) {
358
- Constructor = Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]];
359
- } else {
360
- const constructorByValue = getDiscriminatorByValue(Constructor, value[i][Constructor.schema.options.discriminatorKey]);
361
- if (constructorByValue) {
362
- Constructor = constructorByValue;
363
- }
364
- }
365
- }
355
+ const Constructor = getConstructor(this, value[i]);
366
356
 
367
357
  // Check if the document has a different schema (re gh-3701)
368
358
  if ((value[i].$__) &&
369
- value[i].schema !== Constructor.schema) {
359
+ !(value[i] instanceof Constructor)) {
370
360
  value[i] = value[i].toObject({ transform: false, virtuals: false });
371
361
  }
372
362
 
@@ -417,6 +407,30 @@ DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
417
407
  return value;
418
408
  };
419
409
 
410
+ /*!
411
+ * Find the correct subdoc constructor, taking into account discriminators
412
+ */
413
+
414
+ function getConstructor(docArray, subdoc) {
415
+ let Constructor = docArray.casterConstructor;
416
+ const schema = Constructor.schema;
417
+ const discriminatorKey = schema.options.discriminatorKey;
418
+ const discriminatorValue = subdoc[discriminatorKey];
419
+ if (Constructor.discriminators && typeof discriminatorValue === 'string') {
420
+ if (Constructor.discriminators[discriminatorValue]) {
421
+ Constructor = Constructor.discriminators[discriminatorValue];
422
+ } else {
423
+ const constructorByValue = getDiscriminatorByValue(Constructor,
424
+ discriminatorValue);
425
+ if (constructorByValue) {
426
+ Constructor = constructorByValue;
427
+ }
428
+ }
429
+ }
430
+
431
+ return Constructor;
432
+ }
433
+
420
434
  /*!
421
435
  * ignore
422
436
  */
@@ -432,12 +446,12 @@ DocumentArray.prototype.clone = function() {
432
446
  */
433
447
 
434
448
  function _clearListeners(arr) {
435
- if (arr == null || arr._parent == null) {
449
+ if (arr == null || arr[arrayParentSymbol] == null) {
436
450
  return;
437
451
  }
438
452
 
439
453
  for (const key in arr._handlers) {
440
- arr._parent.removeListener(key, arr._handlers[key]);
454
+ arr[arrayParentSymbol].removeListener(key, arr._handlers[key]);
441
455
  }
442
456
  }
443
457
 
package/lib/schema.js CHANGED
@@ -1049,11 +1049,12 @@ Schema.prototype.setupTimestamp = function(timestamps) {
1049
1049
 
1050
1050
  _setTimestampsOnUpdate[symbols.builtInMiddleware] = true;
1051
1051
 
1052
- this.pre('findOneAndUpdate', _setTimestampsOnUpdate);
1053
- this.pre('replaceOne', _setTimestampsOnUpdate);
1054
- this.pre('update', _setTimestampsOnUpdate);
1055
- this.pre('updateOne', _setTimestampsOnUpdate);
1056
- this.pre('updateMany', _setTimestampsOnUpdate);
1052
+ const opts = { query: true, model: false };
1053
+ this.pre('findOneAndUpdate', opts, _setTimestampsOnUpdate);
1054
+ this.pre('replaceOne', opts, _setTimestampsOnUpdate);
1055
+ this.pre('update', opts, _setTimestampsOnUpdate);
1056
+ this.pre('updateOne', opts, _setTimestampsOnUpdate);
1057
+ this.pre('updateMany', opts, _setTimestampsOnUpdate);
1057
1058
 
1058
1059
  function _setTimestampsOnUpdate(next) {
1059
1060
  const now = this.model.base.now();
package/lib/schematype.js CHANGED
@@ -10,6 +10,7 @@ const $type = require('./schema/operators/type');
10
10
  const get = require('./helpers/get');
11
11
  const immediate = require('./helpers/immediate');
12
12
  const schemaTypeSymbol = require('./helpers/symbols').schemaTypeSymbol;
13
+ const util = require('util');
13
14
  const utils = require('./utils');
14
15
  const validatorErrorSymbol = require('./helpers/symbols').validatorErrorSymbol;
15
16
 
@@ -50,6 +51,21 @@ function SchemaType(path, options, instance) {
50
51
  if (this[prop] && typeof this[prop] === 'function') {
51
52
  // { unique: true, index: true }
52
53
  if (prop === 'index' && this._index) {
54
+ if (options.index === false) {
55
+ const index = this._index;
56
+ if (typeof index === 'object' && index != null) {
57
+ if (index.unique) {
58
+ throw new Error('Path "' + this.path + '" may not have `index` ' +
59
+ 'set to false and `unique` set to true');
60
+ }
61
+ if (index.sparse) {
62
+ throw new Error('Path "' + this.path + '" may not have `index` ' +
63
+ 'set to false and `sparse` set to true');
64
+ }
65
+ }
66
+
67
+ this._index = false;
68
+ }
53
69
  continue;
54
70
  }
55
71
 
@@ -262,6 +278,14 @@ SchemaType.prototype.unique = function(bool) {
262
278
  */
263
279
 
264
280
  SchemaType.prototype.text = function(bool) {
281
+ if (this._index === false) {
282
+ if (!bool) {
283
+ return;
284
+ }
285
+ throw new Error('Path "' + this.path + '" may not have `index` set to ' +
286
+ 'false and `text` set to true');
287
+ }
288
+
265
289
  if (this._index === null || this._index === undefined ||
266
290
  typeof this._index === 'boolean') {
267
291
  this._index = {};
@@ -287,8 +311,15 @@ SchemaType.prototype.text = function(bool) {
287
311
  */
288
312
 
289
313
  SchemaType.prototype.sparse = function(bool) {
290
- if (this._index === null || this._index === undefined ||
291
- typeof this._index === 'boolean') {
314
+ if (this._index === false) {
315
+ if (!bool) {
316
+ return;
317
+ }
318
+ throw new Error('Path "' + this.path + '" may not have `index` set to ' +
319
+ 'false and `sparse` set to true');
320
+ }
321
+
322
+ if (this._index == null || typeof this._index === 'boolean') {
292
323
  this._index = {};
293
324
  } else if (typeof this._index === 'string') {
294
325
  this._index = {type: this._index};
@@ -597,6 +628,11 @@ SchemaType.prototype.validate = function(obj, message, type) {
597
628
  }
598
629
  properties = {message: message, type: type, validator: obj};
599
630
  }
631
+
632
+ if (properties.isAsync) {
633
+ handleIsAsync();
634
+ }
635
+
600
636
  this.validators.push(properties);
601
637
  return this;
602
638
  }
@@ -620,6 +656,15 @@ SchemaType.prototype.validate = function(obj, message, type) {
620
656
  return this;
621
657
  };
622
658
 
659
+ /*!
660
+ * ignore
661
+ */
662
+
663
+ const handleIsAsync = util.deprecate(function handleIsAsync() {},
664
+ 'Mongoose: the `isAsync` option for custom validators is deprecated. Make ' +
665
+ 'your async validators return a promise instead: ' +
666
+ 'https://mongoosejs.com/docs/validation.html#async-custom-validators');
667
+
623
668
  /**
624
669
  * Adds a required validator to this SchemaType. The validator gets added
625
670
  * to the front of this SchemaType's validators array using `unshift()`.
@@ -906,7 +951,7 @@ SchemaType.prototype.select = function select(val) {
906
951
  * @api private
907
952
  */
908
953
 
909
- SchemaType.prototype.doValidate = function(value, fn, scope) {
954
+ SchemaType.prototype.doValidate = function(value, fn, scope, options) {
910
955
  let err = false;
911
956
  const path = this.path;
912
957
  let count = this.validators.length;
@@ -945,7 +990,7 @@ SchemaType.prototype.doValidate = function(value, fn, scope) {
945
990
  let ok;
946
991
 
947
992
  const validatorProperties = utils.clone(v);
948
- validatorProperties.path = path;
993
+ validatorProperties.path = options && options.path ? options.path : path;
949
994
  validatorProperties.value = value;
950
995
 
951
996
  if (validator instanceof RegExp) {
@@ -1041,7 +1086,7 @@ function asyncValidate(validator, scope, value, props, cb) {
1041
1086
  * @api private
1042
1087
  */
1043
1088
 
1044
- SchemaType.prototype.doValidateSync = function(value, scope) {
1089
+ SchemaType.prototype.doValidateSync = function(value, scope, options) {
1045
1090
  let err = null;
1046
1091
  const path = this.path;
1047
1092
  const count = this.validators.length;
@@ -1077,7 +1122,7 @@ SchemaType.prototype.doValidateSync = function(value, scope) {
1077
1122
 
1078
1123
  const validator = v.validator;
1079
1124
  const validatorProperties = utils.clone(v);
1080
- validatorProperties.path = path;
1125
+ validatorProperties.path = options && options.path ? options.path : path;
1081
1126
  validatorProperties.value = value;
1082
1127
  let ok;
1083
1128