mongoose 9.1.6 → 9.2.1

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/lib/model.js CHANGED
@@ -50,6 +50,7 @@ const immediate = require('./helpers/immediate');
50
50
  const internalToObjectOptions = require('./options').internalToObjectOptions;
51
51
  const isDefaultIdIndex = require('./helpers/indexes/isDefaultIdIndex');
52
52
  const isIndexEqual = require('./helpers/indexes/isIndexEqual');
53
+ const isIndexSpecEqual = require('./helpers/indexes/isIndexSpecEqual');
53
54
  const isTimeseriesIndex = require('./helpers/indexes/isTimeseriesIndex');
54
55
  const {
55
56
  getRelatedDBIndexes,
@@ -63,6 +64,7 @@ const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscrim
63
64
  const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
64
65
  const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
65
66
  const setDottedPath = require('./helpers/path/setDottedPath');
67
+ const { buildMiddlewareFilter } = require('./helpers/buildMiddlewareFilter');
66
68
  const util = require('util');
67
69
  const utils = require('./utils');
68
70
  const minimize = require('./helpers/minimize');
@@ -366,9 +368,9 @@ function _createSaveOptions(doc, options) {
366
368
 
367
369
  Model.prototype.$__save = async function $__save(options) {
368
370
  try {
369
- await this._execDocumentPreHooks('save', options);
371
+ await this._execDocumentPreHooks('save', options, [options]);
370
372
  } catch (error) {
371
- await this._execDocumentPostHooks('save', error);
373
+ await this._execDocumentPostHooks('save', options, error);
372
374
  return;
373
375
  }
374
376
 
@@ -466,7 +468,7 @@ Model.prototype.$__save = async function $__save(options) {
466
468
  }
467
469
  } catch (err) {
468
470
  const error = this.$__schema._transformDuplicateKeyError(err);
469
- await this._execDocumentPostHooks('save', error);
471
+ await this._execDocumentPostHooks('save', options, error);
470
472
  return;
471
473
  }
472
474
 
@@ -501,7 +503,7 @@ Model.prototype.$__save = async function $__save(options) {
501
503
  this.$__undoReset();
502
504
  const err = this.$__.$versionError ||
503
505
  new VersionError(this, version, this.$__.modifiedPaths);
504
- await this._execDocumentPostHooks('save', err);
506
+ await this._execDocumentPostHooks('save', options, err);
505
507
  return;
506
508
  }
507
509
 
@@ -513,7 +515,7 @@ Model.prototype.$__save = async function $__save(options) {
513
515
  if (result != null && numAffected <= 0) {
514
516
  this.$__undoReset();
515
517
  const error = new DocumentNotFoundError(where, this.constructor.modelName, numAffected, result);
516
- await this._execDocumentPostHooks('save', error);
518
+ await this._execDocumentPostHooks('save', options, error);
517
519
  return;
518
520
  }
519
521
  }
@@ -521,7 +523,7 @@ Model.prototype.$__save = async function $__save(options) {
521
523
  this.$__.savedState = {};
522
524
  this.$emit('save', this, numAffected);
523
525
  this.constructor.emit('save', this, numAffected);
524
- await this._execDocumentPostHooks('save');
526
+ await this._execDocumentPostHooks('save', options);
525
527
  };
526
528
 
527
529
  /*!
@@ -565,6 +567,9 @@ function generateVersionError(doc, modifiedPaths, defaultPaths) {
565
567
  * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Restrictions-on-Field-Names)
566
568
  * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
567
569
  * @param {Array} [options.pathsToSave] An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`.
570
+ * @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
571
+ * @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
572
+ * @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
568
573
  * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
569
574
  * @return {Promise}
570
575
  * @api public
@@ -762,8 +767,11 @@ Model.prototype.deleteOne = function deleteOne(options) {
762
767
  }
763
768
  }
764
769
 
770
+ const preFilter = buildMiddlewareFilter(options, 'pre');
771
+ const postFilter = buildMiddlewareFilter(options, 'post');
772
+
765
773
  query.pre(async function queryPreDeleteOne() {
766
- const res = await self.constructor._middleware.execPre('deleteOne', self, [self, options]);
774
+ const res = await self.constructor._middleware.execPre('deleteOne', self, [self, options], { filter: preFilter });
767
775
  // `self` is passed to pre hooks as argument for backwards compatibility, but that
768
776
  // isn't the actual arguments passed to the wrapped function.
769
777
  if (res[0] !== self || res[1] !== options) {
@@ -778,7 +786,7 @@ Model.prototype.deleteOne = function deleteOne(options) {
778
786
  return res;
779
787
  });
780
788
  query.pre(function callSubdocPreHooks() {
781
- return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc])));
789
+ return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc], { filter: preFilter })));
782
790
  });
783
791
  query.pre(function skipIfAlreadyDeleted() {
784
792
  if (self.$__.isDeleted) {
@@ -786,10 +794,10 @@ Model.prototype.deleteOne = function deleteOne(options) {
786
794
  }
787
795
  });
788
796
  query.post(function callSubdocPostHooks() {
789
- return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc])));
797
+ return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc], { filter: postFilter })));
790
798
  });
791
799
  query.post(function queryPostDeleteOne() {
792
- return self.constructor._middleware.execPost('deleteOne', self, [self], {});
800
+ return self.constructor._middleware.execPost('deleteOne', self, [self], { filter: postFilter });
793
801
  });
794
802
  query.transform(function setIsDeleted(result) {
795
803
  if (result?.deletedCount > 0) {
@@ -1146,7 +1154,16 @@ Model.createCollection = async function createCollection(options) {
1146
1154
  throw new MongooseError('Model.createCollection() no longer accepts a callback');
1147
1155
  }
1148
1156
 
1149
- [options] = await this.hooks.execPre('createCollection', this, [options]).catch(err => {
1157
+ const preFilter = buildMiddlewareFilter(options, 'pre');
1158
+ const postFilter = buildMiddlewareFilter(options, 'post');
1159
+
1160
+ // Remove middleware option before passing to MongoDB
1161
+ if (options?.middleware != null) {
1162
+ options = { ...options };
1163
+ delete options.middleware;
1164
+ }
1165
+
1166
+ [options] = await this.hooks.execPre('createCollection', this, [options], { filter: preFilter }).catch(err => {
1150
1167
  if (err instanceof Kareem.skipWrappedFunction) {
1151
1168
  return [err];
1152
1169
  }
@@ -1196,11 +1213,11 @@ Model.createCollection = async function createCollection(options) {
1196
1213
  }
1197
1214
  } catch (err) {
1198
1215
  if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1199
- await this.hooks.execPost('createCollection', this, [null], { error: err });
1216
+ await this.hooks.execPost('createCollection', this, [null], { error: err, filter: postFilter });
1200
1217
  }
1201
1218
  }
1202
1219
 
1203
- await this.hooks.execPost('createCollection', this, [this.$__collection]);
1220
+ await this.hooks.execPost('createCollection', this, [this.$__collection], { filter: postFilter });
1204
1221
 
1205
1222
  return this.$__collection;
1206
1223
  };
@@ -1629,6 +1646,25 @@ function _ensureIndexes(model, options, callback) {
1629
1646
  }
1630
1647
  }
1631
1648
 
1649
+ // Check for duplicate index definitions (gh-15056)
1650
+ const seenIndexes = [];
1651
+ for (const index of indexes) {
1652
+ const fields = index[0];
1653
+ const indexOptions = index[1];
1654
+ if (indexOptions.name == null) {
1655
+ for (const existingIndex of seenIndexes) {
1656
+ if (existingIndex[1].name == null && isIndexSpecEqual(existingIndex[0], fields)) {
1657
+ utils.warn('mongoose: Duplicate schema index on ' + JSON.stringify(fields) +
1658
+ ' for model "' + model.modelName + '". ' +
1659
+ 'This is often due to declaring an index using both "index: true" and "schema.index()". ' +
1660
+ 'Please remove the duplicate index definition.');
1661
+ break;
1662
+ }
1663
+ }
1664
+ }
1665
+ seenIndexes.push(index);
1666
+ }
1667
+
1632
1668
  if (!indexes.length) {
1633
1669
  immediate(function() {
1634
1670
  done();
@@ -2296,10 +2332,10 @@ Model.$where = function $where() {
2296
2332
  * @param {Object} [conditions]
2297
2333
  * @param {Object} [update]
2298
2334
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2299
- * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2335
+ * @param {'before'|'after'} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2300
2336
  * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
2301
2337
  * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2302
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2338
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2303
2339
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
2304
2340
  * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
2305
2341
  * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
@@ -2385,10 +2421,10 @@ Model.findOneAndUpdate = function(conditions, update, options) {
2385
2421
  * @param {Object|Number|String} id value of `_id` to query by
2386
2422
  * @param {Object} [update]
2387
2423
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2388
- * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2424
+ * @param {'before'|'after'} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2389
2425
  * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
2390
2426
  * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2391
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2427
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2392
2428
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
2393
2429
  * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
2394
2430
  * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
@@ -2446,7 +2482,7 @@ Model.findByIdAndUpdate = function(id, update, options) {
2446
2482
  *
2447
2483
  * @param {Object} conditions
2448
2484
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2449
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2485
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2450
2486
  * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
2451
2487
  * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2452
2488
  * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
@@ -2488,7 +2524,7 @@ Model.findOneAndDelete = function(conditions, options) {
2488
2524
  *
2489
2525
  * @param {Object|Number|String} id value of `_id` to query by
2490
2526
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2491
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2527
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2492
2528
  * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
2493
2529
  * @return {Query}
2494
2530
  * @see Model.findOneAndDelete https://mongoosejs.com/docs/api/model.html#Model.findOneAndDelete()
@@ -2523,10 +2559,10 @@ Model.findByIdAndDelete = function(id, options) {
2523
2559
  * @param {Object} filter Replace the first document that matches this filter
2524
2560
  * @param {Object} [replacement] Replace with this document
2525
2561
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
2526
- * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2562
+ * @param {'before'|'after'} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
2527
2563
  * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
2528
2564
  * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
2529
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2565
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
2530
2566
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
2531
2567
  * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
2532
2568
  * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
@@ -2910,6 +2946,9 @@ Model.startSession = function() {
2910
2946
  * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
2911
2947
  * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set.
2912
2948
  * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
2949
+ * @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
2950
+ * @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
2951
+ * @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
2913
2952
  * @return {Promise} resolving to the raw result from the MongoDB driver if `options.rawResult` was `true`, or the documents that passed validation, otherwise
2914
2953
  * @api public
2915
2954
  */
@@ -2921,13 +2960,15 @@ Model.insertMany = async function insertMany(arr, options) {
2921
2960
  throw new MongooseError('Model.insertMany() no longer accepts a callback');
2922
2961
  }
2923
2962
 
2963
+ options = options || {};
2964
+ const preFilter = buildMiddlewareFilter(options, 'pre');
2965
+ const postFilter = buildMiddlewareFilter(options, 'post');
2966
+
2924
2967
  try {
2925
- [arr] = await this._middleware.execPre('insertMany', this, [arr]);
2968
+ [arr] = await this._middleware.execPre('insertMany', this, [arr], { filter: preFilter });
2926
2969
  } catch (error) {
2927
- await this._middleware.execPost('insertMany', this, [arr], { error });
2970
+ await this._middleware.execPost('insertMany', this, [arr], { error, filter: postFilter });
2928
2971
  }
2929
-
2930
- options = options || {};
2931
2972
  const ThisModel = this;
2932
2973
  const limit = options.limit || 1000;
2933
2974
  const rawResult = !!options.rawResult;
@@ -3104,7 +3145,7 @@ Model.insertMany = async function insertMany(arr, options) {
3104
3145
  decorateBulkWriteResult(error, validationErrors, results);
3105
3146
  }
3106
3147
 
3107
- await this._middleware.execPost('insertMany', this, [arr], { error });
3148
+ await this._middleware.execPost('insertMany', this, [arr], { error, filter: postFilter });
3108
3149
  }
3109
3150
 
3110
3151
  if (!lean) {
@@ -3152,7 +3193,8 @@ Model.insertMany = async function insertMany(arr, options) {
3152
3193
  });
3153
3194
  }
3154
3195
 
3155
- return await this._middleware.execPost('insertMany', this, [docAttributes]).then(res => res[0]);
3196
+ const [result] = await this._middleware.execPost('insertMany', this, [docAttributes], { filter: postFilter });
3197
+ return result;
3156
3198
  };
3157
3199
 
3158
3200
  /*!
@@ -3267,6 +3309,9 @@ function _setIsNew(doc, val) {
3267
3309
  * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://www.mongodb.com/docs/manual/core/schema-validation/) for all writes in this bulk.
3268
3310
  * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully. Note that Mongoose will still send all valid operations to the MongoDB server.
3269
3311
  * @param {Boolean|"throw"} [options.strict=null] Overwrites the [`strict` option](https://mongoosejs.com/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk.
3312
+ * @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
3313
+ * @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
3314
+ * @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
3270
3315
  * @return {Promise} resolves to a [`BulkWriteOpResult`](https://mongodb.github.io/node-mongodb-native/4.9/classes/BulkWriteResult.html) if the operation succeeds
3271
3316
  * @api public
3272
3317
  */
@@ -3279,14 +3324,16 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3279
3324
  throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
3280
3325
  }
3281
3326
  options = options || {};
3327
+ const preFilter = buildMiddlewareFilter(options, 'pre');
3328
+ const postFilter = buildMiddlewareFilter(options, 'post');
3282
3329
 
3283
3330
  try {
3284
- [ops, options] = await this.hooks.execPre('bulkWrite', this, [ops, options]);
3331
+ [ops, options] = await this.hooks.execPre('bulkWrite', this, [ops, options], { filter: preFilter });
3285
3332
  } catch (err) {
3286
3333
  if (err instanceof Kareem.skipWrappedFunction) {
3287
3334
  ops = err;
3288
3335
  } else {
3289
- await this.hooks.execPost('bulkWrite', this, [null], { error: err });
3336
+ await this.hooks.execPost('bulkWrite', this, [null], { error: err, filter: postFilter });
3290
3337
  }
3291
3338
  }
3292
3339
 
@@ -3325,7 +3372,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3325
3372
  try {
3326
3373
  res = await this.$__collection.bulkWrite(ops, options);
3327
3374
  } catch (error) {
3328
- await this.hooks.execPost('bulkWrite', this, [null], { error });
3375
+ await this.hooks.execPost('bulkWrite', this, [null], { error, filter: postFilter });
3329
3376
  }
3330
3377
  } else {
3331
3378
  let validOpIndexes = [];
@@ -3394,7 +3441,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3394
3441
  decorateBulkWriteResult(error, validationErrors, results);
3395
3442
  }
3396
3443
 
3397
- await this.hooks.execPost('bulkWrite', this, [null], { error });
3444
+ await this.hooks.execPost('bulkWrite', this, [null], { error, filter: postFilter });
3398
3445
  }
3399
3446
 
3400
3447
  if (validationErrors.length > 0) {
@@ -3411,7 +3458,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3411
3458
  }
3412
3459
  }
3413
3460
 
3414
- await this.hooks.execPost('bulkWrite', this, [res]);
3461
+ await this.hooks.execPost('bulkWrite', this, [res], { filter: postFilter });
3415
3462
 
3416
3463
  return res;
3417
3464
  };
@@ -3438,6 +3485,9 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3438
3485
  * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
3439
3486
  * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
3440
3487
  * @param {Boolean} [options.validateBeforeSave=true] set to `false` to skip Mongoose validation on all documents
3488
+ * @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
3489
+ * @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
3490
+ * @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
3441
3491
  * @return {BulkWriteResult} the return value from `bulkWrite()`
3442
3492
  */
3443
3493
  Model.bulkSave = async function bulkSave(documents, options) {
@@ -3492,7 +3542,7 @@ Model.bulkSave = async function bulkSave(documents, options) {
3492
3542
  successfulDocuments.push(document);
3493
3543
  }
3494
3544
  }
3495
- await Promise.all(successfulDocuments.map(document => handleSuccessfulWrite(document)));
3545
+ await Promise.all(successfulDocuments.map(document => handleSuccessfulWrite(document, options)));
3496
3546
 
3497
3547
  if (bulkWriteError != null) {
3498
3548
  throw bulkWriteError;
@@ -3502,20 +3552,22 @@ Model.bulkSave = async function bulkSave(documents, options) {
3502
3552
  };
3503
3553
 
3504
3554
  async function buildPreSavePromise(document, options) {
3505
- const [newOptions] = await document.schema.s.hooks.execPre('save', document, [options]);
3555
+ const preFilter = buildMiddlewareFilter(options, 'pre');
3556
+ const [newOptions] = await document.schema.s.hooks.execPre('save', document, [options], { filter: preFilter });
3506
3557
  if (newOptions !== options) {
3507
3558
  throw new Error('Cannot overwrite options in pre("save") hook on bulkSave()');
3508
3559
  }
3509
3560
  }
3510
3561
 
3511
- async function handleSuccessfulWrite(document) {
3562
+ async function handleSuccessfulWrite(document, options) {
3512
3563
  if (document.$isNew) {
3513
3564
  _setIsNew(document, false);
3514
3565
  }
3515
3566
 
3516
3567
  document.$__reset();
3517
3568
  document._applyVersionIncrement();
3518
- return document.schema.s.hooks.execPost('save', document, [document]);
3569
+ const postFilter = buildMiddlewareFilter(options, 'post');
3570
+ return document.schema.s.hooks.execPost('save', document, [document], { filter: postFilter });
3519
3571
  }
3520
3572
 
3521
3573
  /**
@@ -3824,7 +3876,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3824
3876
  * @param {Boolean} [options.setters=false] if true, apply schema setters when hydrating
3825
3877
  * @param {Boolean} [options.hydratedPopulatedDocs=false] if true, populates the docs if passing pre-populated data
3826
3878
  * @param {Boolean} [options.virtuals=false] if true, sets any virtuals present on `obj`
3827
- * @param {Boolean|String} [options.strict=false] configure strict mode for the hydrated document. In particular, if strict is false, fields not in the schema won't be stripped out; if strict is 'throw', `hydrate()` will throw an error if there are any fields that are not in the schema. Defaults to true (silently strip out fields not in the schema).
3879
+ * @param {Boolean|'throw'} [options.strict=false] configure strict mode for the hydrated document. In particular, if strict is false, fields not in the schema won't be stripped out; if strict is 'throw', `hydrate()` will throw an error if there are any fields that are not in the schema. Defaults to true (silently strip out fields not in the schema).
3828
3880
  * @return {Document} document instance
3829
3881
  * @api public
3830
3882
  */
@@ -3875,7 +3927,7 @@ Model.hydrate = function(obj, projection, options) {
3875
3927
  * @param {Object} filter
3876
3928
  * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
3877
3929
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3878
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3930
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3879
3931
  * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3880
3932
  * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
3881
3933
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
@@ -3923,7 +3975,7 @@ Model.updateMany = function updateMany(conditions, update, options) {
3923
3975
  * @param {Object} filter
3924
3976
  * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
3925
3977
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3926
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3978
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3927
3979
  * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3928
3980
  * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
3929
3981
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
@@ -3961,7 +4013,7 @@ Model.updateOne = function updateOne(conditions, doc, options) {
3961
4013
  * @param {Object} filter
3962
4014
  * @param {Object} doc
3963
4015
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3964
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
4016
+ * @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3965
4017
  * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3966
4018
  * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
3967
4019
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
@@ -4002,7 +4054,7 @@ function _update(model, op, conditions, doc, options) {
4002
4054
  }
4003
4055
  options = typeof options === 'function' ? options : clone(options);
4004
4056
 
4005
- const versionKey = model?.schema?.options?.versionKey || null;
4057
+ const versionKey = model?.schema?.options?.versionKey ?? null;
4006
4058
  decorateUpdateWithVersionKey(doc, options, versionKey);
4007
4059
 
4008
4060
  return mq[op](conditions, doc, options);
package/lib/mongoose.js CHANGED
@@ -233,7 +233,8 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
233
233
  * - `maxTimeMS`: If set, attaches [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) to every query
234
234
  * - `objectIdGetter`: `true` by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter.
235
235
  * - `overwriteModels`: Set to `true` to default to overwriting models with the same name when calling `mongoose.model()`, as opposed to throwing an `OverwriteModelError`.
236
- * - `returnOriginal`: If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](https://mongoosejs.com/docs/tutorials/findoneandupdate.html) for more information.
236
+ * - `returnDocument`: Set to `'before'` or `'after'` to set the default value for the `returnDocument` option to `findOneAndUpdate()`, `findByIdAndUpdate()`, and `findOneAndReplace()`. Defaults to `'before'`, which returns the document before the update was applied. Read our [`findOneAndUpdate()` tutorial](https://mongoosejs.com/docs/tutorials/findoneandupdate.html) for more information.
237
+ * - `returnOriginal`: **Deprecated.** Use `returnDocument` instead. If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](https://mongoosejs.com/docs/tutorials/findoneandupdate.html) for more information.
237
238
  * - `runValidators`: `false` by default. Set to true to enable [update validators](https://mongoosejs.com/docs/validation.html#update-validators) for all validators by default.
238
239
  * - `sanitizeFilter`: `false` by default. Set to true to enable the [sanitization of the query filters](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.sanitizeFilter()) against query selector injection attacks by wrapping any nested objects that have a property whose name starts with `$` in a `$eq`.
239
240
  * - `selectPopulatedPaths`: `true` by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
@@ -242,6 +243,8 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
242
243
  * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.immutable) which means you can update the `createdAt`
243
244
  * - `toJSON`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toJSON()), for determining how Mongoose documents get serialized by `JSON.stringify()`
244
245
  * - `toObject`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject())
246
+ * - `transactionAsyncLocalStorage`: `false` by default. If `true`, Mongoose will automatically pass the `session` to any operation executing within a transaction executor function. See [transaction AsyncLocalStorage documentation](https://mongoosejs.com/docs/transactions.html#asynclocalstorage)
247
+ * - `translateAliases`: `false` by default. If `true`, Mongoose will automatically translate aliases to their original paths before sending the query to MongoDB.
245
248
  * - `updatePipeline`: `false` by default. If `true`, allows passing update pipelines (arrays) to update operations by default without explicitly setting `updatePipeline: true` in each query.
246
249
  *
247
250
  * @param {String|Object} key The name of the option or a object of multiple key-value pairs
@@ -313,6 +316,23 @@ Mongoose.prototype.set = function getsetOptions(key, value) {
313
316
  _mongoose.connections.shift();
314
317
  }
315
318
  }
319
+ } else if (optionKey === 'returnOriginal') {
320
+ if (_mongoose.options.returnDocument != null) {
321
+ throw new MongooseError(
322
+ 'Cannot set `returnOriginal` when `returnDocument` is already set. Use `returnDocument` instead.'
323
+ );
324
+ }
325
+ const replacement = optionValue === true ? '\'before\'' : '\'after\'';
326
+ utils.warn(
327
+ 'Mongoose: the `returnOriginal` option for `mongoose.set()` is deprecated. ' +
328
+ 'Use `mongoose.set(\'returnDocument\', ' + replacement + ')` instead.'
329
+ );
330
+ } else if (optionKey === 'returnDocument') {
331
+ if (_mongoose.options.returnOriginal != null) {
332
+ throw new MongooseError(
333
+ 'Cannot set `returnDocument` when `returnOriginal` is already set. Use `returnDocument` instead of `returnOriginal`.'
334
+ );
335
+ }
316
336
  }
317
337
  }
318
338
 
package/lib/options.js CHANGED
@@ -13,5 +13,6 @@ exports.internalToObjectOptions = {
13
13
  flattenDecimals: false,
14
14
  useProjection: false,
15
15
  versionKey: true,
16
- flattenObjectIds: false
16
+ flattenObjectIds: false,
17
+ flattenUUIDs: false
17
18
  };
@@ -1,76 +1,94 @@
1
1
  'use strict';
2
2
 
3
+ const symbols = require('../schema/symbols');
4
+
3
5
  /*!
4
6
  * ignore
5
7
  */
6
8
 
7
9
  module.exports = function saveSubdocs(schema) {
8
10
  const unshift = true;
9
- schema.s.hooks.pre('save', false, async function saveSubdocsPreSave() {
10
- if (this.$isSubdocument) {
11
- return;
12
- }
13
-
14
- const subdocs = this.$getAllSubdocs({ useCache: true });
15
-
16
- if (!subdocs.length) {
17
- return;
18
- }
19
-
20
- const options = this.$__.saveOptions;
21
- await Promise.all(subdocs.map(subdoc => subdoc._execDocumentPreHooks('save', options)));
22
-
23
- // Invalidate subdocs cache because subdoc pre hooks can add new subdocuments
24
- if (this.$__.saveOptions) {
25
- this.$__.saveOptions.__subdocs = null;
26
- }
27
- }, null, unshift);
28
-
29
- schema.s.hooks.pre('save', async function saveSubdocsPreDeleteOne() {
30
- const removedSubdocs = this.$__.removedSubdocs;
31
- if (!removedSubdocs?.length) {
32
- return;
33
- }
34
-
35
- const promises = [];
36
- for (const subdoc of removedSubdocs) {
37
- promises.push(subdoc._execDocumentPreHooks('deleteOne'));
38
- }
39
-
40
- await Promise.all(promises);
41
- });
42
-
43
- schema.s.hooks.post('save', async function saveSubdocsPostDeleteOne() {
44
- const removedSubdocs = this.$__.removedSubdocs;
45
- if (!removedSubdocs?.length) {
46
- return;
47
- }
48
-
49
- const promises = [];
50
- for (const subdoc of removedSubdocs) {
51
- promises.push(subdoc._execDocumentPostHooks('deleteOne'));
52
- }
53
-
54
- this.$__.removedSubdocs = null;
55
- await Promise.all(promises);
56
- });
57
-
58
- schema.s.hooks.post('save', async function saveSubdocsPostSave() {
59
- if (this.$isSubdocument) {
60
- return;
61
- }
62
-
63
- const subdocs = this.$getAllSubdocs({ useCache: true });
64
-
65
- if (!subdocs.length) {
66
- return;
67
- }
68
-
69
- const promises = [];
70
- for (const subdoc of subdocs) {
71
- promises.push(subdoc._execDocumentPostHooks('save'));
72
- }
73
-
74
- await Promise.all(promises);
75
- }, null, unshift);
11
+
12
+ schema.s.hooks.pre('save', false, saveSubdocsPreSave, null, unshift);
13
+ schema.s.hooks.post('save', saveSubdocsPostSave, null, unshift);
14
+ schema.s.hooks.pre('save', saveSubdocsPreDeleteOne);
15
+ schema.s.hooks.post('save', saveSubdocsPostDeleteOne);
76
16
  };
17
+
18
+
19
+ async function saveSubdocsPreSave() {
20
+ if (this.$isSubdocument) {
21
+ return;
22
+ }
23
+
24
+ const subdocs = this.$getAllSubdocs({ useCache: true });
25
+
26
+ if (!subdocs.length) {
27
+ return;
28
+ }
29
+
30
+ const options = this.$__.saveOptions;
31
+ await Promise.all(subdocs.map(subdoc => subdoc._execDocumentPreHooks('save', options, [options])));
32
+
33
+ // Invalidate subdocs cache because subdoc pre hooks can add new subdocuments
34
+ if (this.$__.saveOptions) {
35
+ this.$__.saveOptions.__subdocs = null;
36
+ }
37
+ }
38
+
39
+ async function saveSubdocsPostSave() {
40
+ if (this.$isSubdocument) {
41
+ return;
42
+ }
43
+
44
+ const subdocs = this.$getAllSubdocs({ useCache: true });
45
+
46
+ if (!subdocs.length) {
47
+ return;
48
+ }
49
+
50
+ const options = this.$__.saveOptions;
51
+ const promises = [];
52
+ for (const subdoc of subdocs) {
53
+ promises.push(subdoc._execDocumentPostHooks('save', options));
54
+ }
55
+
56
+ await Promise.all(promises);
57
+ }
58
+
59
+ async function saveSubdocsPreDeleteOne() {
60
+ const removedSubdocs = this.$__.removedSubdocs;
61
+ if (!removedSubdocs?.length) {
62
+ return;
63
+ }
64
+
65
+ const options = this.$__.saveOptions;
66
+ const promises = [];
67
+ for (const subdoc of removedSubdocs) {
68
+ promises.push(subdoc._execDocumentPreHooks('deleteOne', options));
69
+ }
70
+
71
+ await Promise.all(promises);
72
+ }
73
+
74
+ async function saveSubdocsPostDeleteOne() {
75
+ const removedSubdocs = this.$__.removedSubdocs;
76
+ if (!removedSubdocs?.length) {
77
+ return;
78
+ }
79
+
80
+ const options = this.$__.saveOptions;
81
+ const promises = [];
82
+ for (const subdoc of removedSubdocs) {
83
+ promises.push(subdoc._execDocumentPostHooks('deleteOne', options));
84
+ }
85
+
86
+ this.$__.removedSubdocs = null;
87
+ await Promise.all(promises);
88
+ }
89
+
90
+
91
+ saveSubdocsPreSave[symbols.builtInMiddleware] = true;
92
+ saveSubdocsPostSave[symbols.builtInMiddleware] = true;
93
+ saveSubdocsPreDeleteOne[symbols.builtInMiddleware] = true;
94
+ saveSubdocsPostDeleteOne[symbols.builtInMiddleware] = true;