mongoose 8.20.0 → 9.0.0-rc0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/eslint.config.mjs +198 -0
  2. package/lib/aggregate.js +17 -73
  3. package/lib/cast/bigint.js +1 -1
  4. package/lib/cast/double.js +1 -1
  5. package/lib/cast/uuid.js +5 -48
  6. package/lib/cast.js +2 -2
  7. package/lib/connection.js +0 -1
  8. package/lib/cursor/aggregationCursor.js +14 -24
  9. package/lib/cursor/queryCursor.js +7 -14
  10. package/lib/document.js +125 -121
  11. package/lib/drivers/node-mongodb-native/connection.js +3 -10
  12. package/lib/error/objectParameter.js +1 -2
  13. package/lib/error/validation.js +0 -8
  14. package/lib/helpers/clone.js +1 -1
  15. package/lib/helpers/common.js +1 -1
  16. package/lib/helpers/indexes/isIndexEqual.js +0 -1
  17. package/lib/helpers/model/applyDefaultsToPOJO.js +2 -2
  18. package/lib/helpers/model/applyHooks.js +43 -53
  19. package/lib/helpers/model/applyMethods.js +2 -2
  20. package/lib/helpers/model/applyStaticHooks.js +1 -48
  21. package/lib/helpers/model/castBulkWrite.js +1 -1
  22. package/lib/helpers/parallelLimit.js +18 -36
  23. package/lib/helpers/pluralize.js +3 -3
  24. package/lib/helpers/populate/assignRawDocsToIdStructure.js +1 -8
  25. package/lib/helpers/populate/createPopulateQueryFilter.js +1 -1
  26. package/lib/helpers/populate/getModelsMapForPopulate.js +17 -9
  27. package/lib/helpers/populate/getSchemaTypes.js +5 -5
  28. package/lib/helpers/query/cast$expr.js +8 -10
  29. package/lib/helpers/query/castFilterPath.js +1 -1
  30. package/lib/helpers/query/castUpdate.js +14 -12
  31. package/lib/helpers/query/getEmbeddedDiscriminatorPath.js +1 -1
  32. package/lib/helpers/schema/applyPlugins.js +1 -1
  33. package/lib/helpers/schema/getIndexes.js +1 -7
  34. package/lib/helpers/timestamps/setupTimestamps.js +3 -6
  35. package/lib/helpers/updateValidators.js +57 -111
  36. package/lib/model.js +419 -607
  37. package/lib/mongoose.js +41 -13
  38. package/lib/plugins/saveSubdocs.js +24 -51
  39. package/lib/plugins/sharding.js +5 -4
  40. package/lib/plugins/validateBeforeSave.js +3 -13
  41. package/lib/query.js +101 -145
  42. package/lib/queryHelpers.js +2 -2
  43. package/lib/schema/array.js +41 -84
  44. package/lib/schema/documentArray.js +57 -94
  45. package/lib/schema/documentArrayElement.js +16 -11
  46. package/lib/schema/string.js +1 -1
  47. package/lib/schema/subdocument.js +22 -28
  48. package/lib/schema/uuid.js +0 -21
  49. package/lib/schema.js +81 -39
  50. package/lib/schemaType.js +39 -57
  51. package/lib/types/array/index.js +2 -2
  52. package/lib/types/array/methods/index.js +4 -4
  53. package/lib/types/arraySubdocument.js +1 -1
  54. package/lib/types/buffer.js +10 -10
  55. package/lib/types/decimal128.js +1 -1
  56. package/lib/types/documentArray/index.js +1 -1
  57. package/lib/types/documentArray/methods/index.js +5 -3
  58. package/lib/types/double.js +1 -1
  59. package/lib/types/objectid.js +1 -1
  60. package/lib/types/subdocument.js +15 -43
  61. package/lib/types/uuid.js +1 -1
  62. package/lib/utils.js +1 -8
  63. package/lib/validOptions.js +3 -3
  64. package/package.json +11 -24
  65. package/types/connection.d.ts +20 -11
  66. package/types/document.d.ts +95 -26
  67. package/types/index.d.ts +143 -39
  68. package/types/inferhydrateddoctype.d.ts +115 -0
  69. package/types/inferrawdoctype.d.ts +99 -75
  70. package/types/inferschematype.d.ts +17 -3
  71. package/types/middlewares.d.ts +0 -2
  72. package/types/models.d.ts +131 -199
  73. package/types/mongooseoptions.d.ts +6 -5
  74. package/types/pipelinestage.d.ts +1 -1
  75. package/types/query.d.ts +71 -139
  76. package/types/schemaoptions.d.ts +1 -1
  77. package/types/schematypes.d.ts +14 -10
  78. package/types/types.d.ts +3 -4
  79. package/types/utility.d.ts +68 -48
  80. package/types/validation.d.ts +18 -14
  81. package/browser.js +0 -8
  82. package/dist/browser.umd.js +0 -2
  83. package/lib/browser.js +0 -141
  84. package/lib/browserDocument.js +0 -101
  85. package/lib/documentProvider.js +0 -30
  86. package/lib/drivers/browser/binary.js +0 -14
  87. package/lib/drivers/browser/decimal128.js +0 -7
  88. package/lib/drivers/browser/index.js +0 -13
  89. package/lib/drivers/browser/objectid.js +0 -29
  90. package/lib/helpers/promiseOrCallback.js +0 -54
package/lib/model.js CHANGED
@@ -104,7 +104,9 @@ const saveToObjectOptions = Object.assign({}, internalToObjectOptions, {
104
104
  *
105
105
  * @param {Object} doc values for initial set
106
106
  * @param {Object} [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()).
107
- * @param {Boolean} [skipId=false] optional boolean. If true, mongoose doesn't add an `_id` field to the document.
107
+ * @param {Object} [options] optional object containing the options for the document.
108
+ * @param {Boolean} [options.defaults=true] if `false`, skip applying default values to this document.
109
+ * @param {Boolean} [options.skipId=false] By default, Mongoose document if one is not provided and the document's schema does not override Mongoose's default `_id`. Set `skipId` to `true` to skip this generation step.
108
110
  * @inherits Document https://mongoosejs.com/docs/api/document.html
109
111
  * @event `error`: If listening to this event, 'error' is emitted when a document was saved and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model.
110
112
  * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
@@ -113,7 +115,7 @@ const saveToObjectOptions = Object.assign({}, internalToObjectOptions, {
113
115
  * @api public
114
116
  */
115
117
 
116
- function Model(doc, fields, skipId) {
118
+ function Model(doc, fields, options) {
117
119
  if (fields instanceof Schema) {
118
120
  throw new TypeError('2nd argument to `Model` constructor must be a POJO or string, ' +
119
121
  '**not** a schema. Make sure you\'re calling `mongoose.model()`, not ' +
@@ -124,7 +126,7 @@ function Model(doc, fields, skipId) {
124
126
  '**not** a string. Make sure you\'re calling `mongoose.model()`, not ' +
125
127
  '`mongoose.Model()`.');
126
128
  }
127
- Document.call(this, doc, fields, skipId);
129
+ Document.call(this, doc, fields, options);
128
130
  }
129
131
 
130
132
  /**
@@ -316,11 +318,10 @@ function _applyCustomWhere(doc, where) {
316
318
  /*!
317
319
  * ignore
318
320
  */
319
-
320
- Model.prototype.$__handleSave = function(options, callback) {
321
+ function _createSaveOptions(doc, options) {
321
322
  const saveOptions = {};
322
323
 
323
- applyWriteConcern(this.$__schema, options);
324
+ applyWriteConcern(doc.$__schema, options);
324
325
  if (typeof options.writeConcern !== 'undefined') {
325
326
  saveOptions.writeConcern = {};
326
327
  if ('w' in options.writeConcern) {
@@ -347,215 +348,180 @@ Model.prototype.$__handleSave = function(options, callback) {
347
348
  saveOptions.checkKeys = options.checkKeys;
348
349
  }
349
350
 
350
- const session = this.$session();
351
- const asyncLocalStorage = this[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore();
351
+ const session = doc.$session();
352
+ const asyncLocalStorage = doc[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore();
352
353
  if (session != null) {
353
354
  saveOptions.session = session;
354
355
  } else if (!options.hasOwnProperty('session') && asyncLocalStorage?.session != null) {
355
356
  // Only set session from asyncLocalStorage if `session` option wasn't originally passed in options
356
357
  saveOptions.session = asyncLocalStorage.session;
357
358
  }
358
- if (this.$isNew) {
359
- // send entire doc
360
- const obj = this.toObject(saveToObjectOptions);
361
- if ((obj || {})._id === void 0) {
362
- // documents must have an _id else mongoose won't know
363
- // what to update later if more changes are made. the user
364
- // wouldn't know what _id was generated by mongodb either
365
- // nor would the ObjectId generated by mongodb necessarily
366
- // match the schema definition.
367
- immediate(function() {
368
- callback(new MongooseError('document must have an _id before saving'));
369
- });
370
- return;
371
- }
372
359
 
373
- this.$__version(true, obj);
374
- this[modelCollectionSymbol].insertOne(obj, saveOptions).then(
375
- ret => callback(null, ret),
376
- err => {
377
- _setIsNew(this, true);
360
+ return saveOptions;
361
+ }
378
362
 
379
- callback(err, null);
380
- }
381
- );
363
+ /*!
364
+ * ignore
365
+ */
382
366
 
383
- this.$__reset();
384
- _setIsNew(this, false);
385
- // Make it possible to retry the insert
386
- this.$__.inserting = true;
367
+ Model.prototype.$__save = async function $__save(options) {
368
+ try {
369
+ await this._execDocumentPreHooks('save', options);
370
+ } catch (error) {
371
+ await this._execDocumentPostHooks('save', error);
387
372
  return;
388
373
  }
389
374
 
390
- // Make sure we don't treat it as a new object on error,
391
- // since it already exists
392
- this.$__.inserting = false;
393
- const delta = this.$__delta();
394
375
 
395
- if (options.pathsToSave) {
396
- for (const key in delta[1]['$set']) {
397
- if (options.pathsToSave.includes(key)) {
398
- continue;
399
- } else if (options.pathsToSave.some(pathToSave => key.slice(0, pathToSave.length) === pathToSave && key.charAt(pathToSave.length) === '.')) {
400
- continue;
401
- } else {
402
- delete delta[1]['$set'][key];
376
+ let result = null;
377
+ let where = null;
378
+ try {
379
+ const saveOptions = _createSaveOptions(this, options);
380
+
381
+ if (this.$isNew) {
382
+ // send entire doc
383
+ const obj = this.toObject(saveToObjectOptions);
384
+ if ((obj || {})._id === void 0) {
385
+ // documents must have an _id else mongoose won't know
386
+ // what to update later if more changes are made. the user
387
+ // wouldn't know what _id was generated by mongodb either
388
+ // nor would the ObjectId generated by mongodb necessarily
389
+ // match the schema definition.
390
+ throw new MongooseError('document must have an _id before saving');
403
391
  }
404
- }
405
- }
406
- if (delta) {
407
- if (delta instanceof MongooseError) {
408
- callback(delta);
409
- return;
410
- }
411
-
412
- const where = this.$__where(delta[0]);
413
- if (where instanceof MongooseError) {
414
- callback(where);
415
- return;
416
- }
417
-
418
- _applyCustomWhere(this, where);
419
392
 
420
- const update = delta[1];
421
- if (this.$__schema.options.minimize) {
422
- for (const updateOp of Object.values(update)) {
423
- if (updateOp == null) {
424
- continue;
425
- }
426
- for (const key of Object.keys(updateOp)) {
427
- if (updateOp[key] == null || typeof updateOp[key] !== 'object') {
393
+ this.$__version(true, obj);
394
+ this.$__reset();
395
+ _setIsNew(this, false);
396
+ // Make it possible to retry the insert
397
+ this.$__.inserting = true;
398
+ result = await this[modelCollectionSymbol].insertOne(obj, saveOptions).catch(err => {
399
+ _setIsNew(this, true);
400
+ throw err;
401
+ });
402
+ } else {
403
+ // Make sure we don't treat it as a new object on error,
404
+ // since it already exists
405
+ this.$__.inserting = false;
406
+ const delta = this.$__delta();
407
+
408
+ if (options.pathsToSave) {
409
+ for (const key in delta[1]['$set']) {
410
+ if (options.pathsToSave.includes(key)) {
428
411
  continue;
429
- }
430
- if (!utils.isPOJO(updateOp[key])) {
412
+ } else if (options.pathsToSave.some(pathToSave => key.slice(0, pathToSave.length) === pathToSave && key.charAt(pathToSave.length) === '.')) {
431
413
  continue;
432
- }
433
- minimize(updateOp[key]);
434
- if (Object.keys(updateOp[key]).length === 0) {
435
- delete updateOp[key];
436
- update.$unset = update.$unset || {};
437
- update.$unset[key] = 1;
414
+ } else {
415
+ delete delta[1]['$set'][key];
438
416
  }
439
417
  }
440
418
  }
441
- }
419
+ if (delta) {
420
+ where = this.$__where(delta[0]);
421
+ _applyCustomWhere(this, where);
422
+
423
+ const update = delta[1];
424
+ if (this.$__schema.options.minimize) {
425
+ for (const updateOp of Object.values(update)) {
426
+ if (updateOp == null) {
427
+ continue;
428
+ }
429
+ for (const key of Object.keys(updateOp)) {
430
+ if (updateOp[key] == null || typeof updateOp[key] !== 'object') {
431
+ continue;
432
+ }
433
+ if (!utils.isPOJO(updateOp[key])) {
434
+ continue;
435
+ }
436
+ minimize(updateOp[key]);
437
+ if (Object.keys(updateOp[key]).length === 0) {
438
+ delete updateOp[key];
439
+ update.$unset = update.$unset || {};
440
+ update.$unset[key] = 1;
441
+ }
442
+ }
443
+ }
444
+ }
442
445
 
443
- this[modelCollectionSymbol].updateOne(where, update, saveOptions).then(
444
- ret => {
445
- if (ret == null) {
446
- ret = { $where: where };
447
- } else {
448
- ret.$where = where;
446
+ // store the modified paths before the document is reset
447
+ this.$__.modifiedPaths = this.modifiedPaths();
448
+ this.$__reset();
449
+
450
+ _setIsNew(this, false);
451
+ result = await this[modelCollectionSymbol].updateOne(where, update, saveOptions).catch(err => {
452
+ this.$__undoReset();
453
+ throw err;
454
+ });
455
+ } else {
456
+ where = this.$__where();
457
+ _applyCustomWhere(this, where);
458
+ if (this.$__.version) {
459
+ this.$__version(where, delta);
449
460
  }
450
- callback(null, ret);
451
- },
452
- err => {
453
- this.$__undoReset();
454
461
 
455
- callback(err);
462
+ applyReadConcern(this.$__schema, saveOptions);
463
+ result = await this.constructor.collection.findOne(where, saveOptions)
464
+ .then(documentExists => ({ matchedCount: !documentExists ? 0 : 1 }));
456
465
  }
457
- );
458
- } else {
459
- handleEmptyUpdate.call(this);
466
+ }
467
+ } catch (err) {
468
+ const error = this.$__schema._transformDuplicateKeyError(err);
469
+ await this._execDocumentPostHooks('save', error);
460
470
  return;
461
471
  }
462
472
 
463
- // store the modified paths before the document is reset in case we need to generate version error.
464
- this.$__.modifiedPaths = this.modifiedPaths().concat(Object.keys(this.$__.activePaths.getStatePaths('default')));
465
- this.$__reset();
466
-
467
- _setIsNew(this, false);
468
-
469
- function handleEmptyUpdate() {
470
- const optionsWithCustomValues = Object.assign({}, options, saveOptions);
471
- const where = this.$__where();
472
- const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
473
- if (optimisticConcurrency && !Array.isArray(optimisticConcurrency)) {
474
- const key = this.$__schema.options.versionKey;
475
- const val = this.$__getValue(key);
476
- if (val != null) {
477
- where[key] = val;
473
+ let numAffected = 0;
474
+ const writeConcern = options != null ?
475
+ options.writeConcern != null ?
476
+ options.writeConcern.w :
477
+ options.w :
478
+ 0;
479
+ if (writeConcern !== 0) {
480
+ // Skip checking if write succeeded if writeConcern is set to
481
+ // unacknowledged writes, because otherwise `numAffected` will always be 0
482
+ if (result != null) {
483
+ if (Array.isArray(result)) {
484
+ numAffected = result.length;
485
+ } else if (result.matchedCount != null) {
486
+ numAffected = result.matchedCount;
487
+ } else {
488
+ numAffected = result;
478
489
  }
479
490
  }
480
491
 
481
- applyReadConcern(this.$__schema, optionsWithCustomValues);
482
- this.constructor.collection.findOne(where, optionsWithCustomValues)
483
- .then(documentExists => {
484
- const matchedCount = !documentExists ? 0 : 1;
485
- callback(null, { $where: where, matchedCount });
486
- })
487
- .catch(callback);
488
- }
489
- };
490
-
491
- /*!
492
- * ignore
493
- */
494
-
495
- Model.prototype.$__save = function(options, callback) {
496
- this.$__handleSave(options, (error, result) => {
497
- if (error) {
498
- error = this.$__schema._transformDuplicateKeyError(error);
499
- const hooks = this.$__schema.s.hooks;
500
- return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
501
- callback(error, this);
502
- });
503
- }
504
- let numAffected = 0;
505
- const writeConcern = options != null ?
506
- options.writeConcern != null ?
507
- options.writeConcern.w :
508
- options.w :
509
- 0;
510
- if (writeConcern !== 0) {
511
- // Skip checking if write succeeded if writeConcern is set to
512
- // unacknowledged writes, because otherwise `numAffected` will always be 0
513
- if (result != null) {
514
- if (Array.isArray(result)) {
515
- numAffected = result.length;
516
- } else if (result.matchedCount != null) {
517
- numAffected = result.matchedCount;
518
- } else {
519
- numAffected = result;
520
- }
492
+ const versionBump = this.$__.version;
493
+ // was this an update that required a version bump?
494
+ if (versionBump && !this.$__.inserting) {
495
+ const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
496
+ this.$__.version = undefined;
497
+ const key = this.$__schema.options.versionKey;
498
+ const version = this.$__getValue(key) || 0;
499
+ if (numAffected <= 0) {
500
+ // the update failed. pass an error back
501
+ this.$__undoReset();
502
+ const err = this.$__.$versionError ||
503
+ new VersionError(this, version, this.$__.modifiedPaths);
504
+ await this._execDocumentPostHooks('save', err);
505
+ return;
521
506
  }
522
507
 
523
- const versionBump = this.$__.version;
524
- // was this an update that required a version bump?
525
- if (versionBump && !this.$__.inserting) {
526
- const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
527
- this.$__.version = undefined;
528
- const key = this.$__schema.options.versionKey;
529
- const version = this.$__getValue(key) || 0;
530
- if (numAffected <= 0) {
531
- // the update failed. pass an error back
532
- this.$__undoReset();
533
- const err = this.$__.$versionError ||
534
- new VersionError(this, version, this.$__.modifiedPaths);
535
- return callback(err, this);
536
- }
537
-
538
- // increment version if was successful
539
- if (doIncrement) {
540
- this.$__setValue(key, version + 1);
541
- }
542
- }
543
- if (result != null && numAffected <= 0) {
544
- this.$__undoReset();
545
- error = new DocumentNotFoundError(result.$where,
546
- this.constructor.modelName, numAffected, result);
547
- const hooks = this.$__schema.s.hooks;
548
- return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
549
- callback(error, this);
550
- });
508
+ // increment version if was successful
509
+ if (doIncrement) {
510
+ this.$__setValue(key, version + 1);
551
511
  }
552
512
  }
553
- this.$__.saving = undefined;
554
- this.$__.savedState = {};
555
- this.$emit('save', this, numAffected);
556
- this.constructor.emit('save', this, numAffected);
557
- callback(null, this);
558
- });
513
+ if (result != null && numAffected <= 0) {
514
+ this.$__undoReset();
515
+ const error = new DocumentNotFoundError(where, this.constructor.modelName, numAffected, result);
516
+ await this._execDocumentPostHooks('save', error);
517
+ return;
518
+ }
519
+ }
520
+ this.$__.saving = undefined;
521
+ this.$__.savedState = {};
522
+ this.$emit('save', this, numAffected);
523
+ this.constructor.emit('save', this, numAffected);
524
+ await this._execDocumentPostHooks('save');
559
525
  };
560
526
 
561
527
  /*!
@@ -639,20 +605,17 @@ Model.prototype.save = async function save(options) {
639
605
 
640
606
  this.$__.saveOptions = options;
641
607
 
642
- await new Promise((resolve, reject) => {
643
- this.$__save(options, error => {
644
- this.$__.saving = null;
645
- this.$__.saveOptions = null;
646
- this.$__.$versionError = null;
647
- this.$op = null;
648
- if (error != null) {
649
- this.$__handleReject(error);
650
- return reject(error);
651
- }
652
-
653
- resolve();
654
- });
655
- });
608
+ try {
609
+ await this.$__save(options);
610
+ } catch (error) {
611
+ this.$__handleReject(error);
612
+ throw error;
613
+ } finally {
614
+ this.$__.saving = null;
615
+ this.$__.saveOptions = null;
616
+ this.$__.$versionError = null;
617
+ this.$op = null;
618
+ }
656
619
 
657
620
  return this;
658
621
  };
@@ -750,7 +713,7 @@ Model.prototype.$__where = function _where(where) {
750
713
  }
751
714
 
752
715
  if (this._doc._id === void 0) {
753
- return new MongooseError('No _id found on document!');
716
+ throw new MongooseError('No _id found on document!');
754
717
  }
755
718
 
756
719
  return where;
@@ -791,9 +754,6 @@ Model.prototype.deleteOne = function deleteOne(options) {
791
754
 
792
755
  const self = this;
793
756
  const where = this.$__where();
794
- if (where instanceof Error) {
795
- throw where;
796
- }
797
757
  const query = self.constructor.deleteOne(where, options);
798
758
 
799
759
  if (this.$session() != null) {
@@ -803,36 +763,32 @@ Model.prototype.deleteOne = function deleteOne(options) {
803
763
  }
804
764
 
805
765
  query.pre(async function queryPreDeleteOne() {
806
- await new Promise((resolve, reject) => {
807
- self.constructor._middleware.execPre('deleteOne', self, [self], err => {
808
- if (err) reject(err);
809
- else resolve();
810
- });
811
- });
766
+ const res = await self.constructor._middleware.execPre('deleteOne', self, [self]);
767
+ // `self` is passed to pre hooks as argument for backwards compatibility, but that
768
+ // isn't the actual arguments passed to the wrapped function.
769
+ if (res?.length !== 1 || res[0] !== self) {
770
+ throw new Error('Document deleteOne pre hooks cannot overwrite arguments');
771
+ }
812
772
  // Apply custom where conditions _after_ document deleteOne middleware for
813
773
  // consistency with save() - sharding plugin needs to set $where
814
774
  if (self.$where != null) {
815
775
  this.where(self.$where);
816
776
  }
777
+ return res;
817
778
  });
818
- query.pre(function callSubdocPreHooks(cb) {
819
- each(self.$getAllSubdocs(), (subdoc, cb) => {
820
- subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc], cb);
821
- }, cb);
779
+ query.pre(function callSubdocPreHooks() {
780
+ return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc])));
822
781
  });
823
- query.pre(function skipIfAlreadyDeleted(cb) {
782
+ query.pre(function skipIfAlreadyDeleted() {
824
783
  if (self.$__.isDeleted) {
825
- return cb(Kareem.skipWrappedFunction());
784
+ throw new Kareem.skipWrappedFunction();
826
785
  }
827
- return cb();
828
786
  });
829
- query.post(function callSubdocPostHooks(cb) {
830
- each(self.$getAllSubdocs(), (subdoc, cb) => {
831
- subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc], {}, cb);
832
- }, cb);
787
+ query.post(function callSubdocPostHooks() {
788
+ return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc])));
833
789
  });
834
- query.post(function queryPostDeleteOne(cb) {
835
- self.constructor._middleware.execPost('deleteOne', self, [self], {}, cb);
790
+ query.post(function queryPostDeleteOne() {
791
+ return self.constructor._middleware.execPost('deleteOne', self, [self], {});
836
792
  });
837
793
 
838
794
  return query;
@@ -1183,16 +1139,11 @@ Model.createCollection = async function createCollection(options) {
1183
1139
  throw new MongooseError('Model.createCollection() no longer accepts a callback');
1184
1140
  }
1185
1141
 
1186
- const shouldSkip = await new Promise((resolve, reject) => {
1187
- this.hooks.execPre('createCollection', this, [options], (err) => {
1188
- if (err != null) {
1189
- if (err instanceof Kareem.skipWrappedFunction) {
1190
- return resolve(true);
1191
- }
1192
- return reject(err);
1193
- }
1194
- resolve();
1195
- });
1142
+ [options] = await this.hooks.execPre('createCollection', this, [options]).catch(err => {
1143
+ if (err instanceof Kareem.skipWrappedFunction) {
1144
+ return [err];
1145
+ }
1146
+ throw err;
1196
1147
  });
1197
1148
 
1198
1149
  const collectionOptions = this &&
@@ -1248,31 +1199,16 @@ Model.createCollection = async function createCollection(options) {
1248
1199
  }
1249
1200
 
1250
1201
  try {
1251
- if (!shouldSkip) {
1202
+ if (!(options instanceof Kareem.skipWrappedFunction)) {
1252
1203
  await this.db.createCollection(this.$__collection.collectionName, options);
1253
1204
  }
1254
1205
  } catch (err) {
1255
1206
  if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1256
- await new Promise((resolve, reject) => {
1257
- const _opts = { error: err };
1258
- this.hooks.execPost('createCollection', this, [null], _opts, (err) => {
1259
- if (err != null) {
1260
- return reject(err);
1261
- }
1262
- resolve();
1263
- });
1264
- });
1207
+ await this.hooks.execPost('createCollection', this, [null], { error: err });
1265
1208
  }
1266
1209
  }
1267
1210
 
1268
- await new Promise((resolve, reject) => {
1269
- this.hooks.execPost('createCollection', this, [this.$__collection], (err) => {
1270
- if (err != null) {
1271
- return reject(err);
1272
- }
1273
- resolve();
1274
- });
1275
- });
1211
+ await this.hooks.execPost('createCollection', this, [this.$__collection]);
1276
1212
 
1277
1213
  return this.$__collection;
1278
1214
  };
@@ -1306,7 +1242,6 @@ Model.createCollection = async function createCollection(options) {
1306
1242
  * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create
1307
1243
  *
1308
1244
  * @param {Object} [options] options to pass to `ensureIndexes()`
1309
- * @param {Boolean} [options.background=null] if specified, overrides each index's `background` property
1310
1245
  * @param {Boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher
1311
1246
  * @return {Promise}
1312
1247
  * @api public
@@ -1708,8 +1643,7 @@ function _ensureIndexes(model, options, callback) {
1708
1643
  });
1709
1644
  return;
1710
1645
  }
1711
- // Indexes are created one-by-one to support how MongoDB < 2.4 deals
1712
- // with background indexes.
1646
+ // Indexes are created one-by-one
1713
1647
 
1714
1648
  const indexSingleDone = function(err, fields, options, name) {
1715
1649
  model.emit('index-single-done', err, fields, options, name);
@@ -1761,10 +1695,6 @@ function _ensureIndexes(model, options, callback) {
1761
1695
 
1762
1696
  indexSingleStart(indexFields, options);
1763
1697
 
1764
- if ('background' in options) {
1765
- indexOptions.background = options.background;
1766
- }
1767
-
1768
1698
  // Just in case `createIndex()` throws a sync error
1769
1699
  let promise = null;
1770
1700
  try {
@@ -2890,6 +2820,13 @@ Model.insertOne = async function insertOne(doc, options) {
2890
2820
  Model.watch = function(pipeline, options) {
2891
2821
  _checkContext(this, 'watch');
2892
2822
 
2823
+ options = options || {};
2824
+ const watchOptions = options?.hydrate !== undefined ?
2825
+ utils.omit(options, ['hydrate']) :
2826
+ { ...options };
2827
+ options.model = this;
2828
+
2829
+
2893
2830
  const changeStreamThunk = cb => {
2894
2831
  pipeline = pipeline || [];
2895
2832
  prepareDiscriminatorPipeline(pipeline, this.schema, 'fullDocument');
@@ -2898,18 +2835,15 @@ Model.watch = function(pipeline, options) {
2898
2835
  if (this.closed) {
2899
2836
  return;
2900
2837
  }
2901
- const driverChangeStream = this.$__collection.watch(pipeline, options);
2838
+ const driverChangeStream = this.$__collection.watch(pipeline, watchOptions);
2902
2839
  cb(null, driverChangeStream);
2903
2840
  });
2904
2841
  } else {
2905
- const driverChangeStream = this.$__collection.watch(pipeline, options);
2842
+ const driverChangeStream = this.$__collection.watch(pipeline, watchOptions);
2906
2843
  cb(null, driverChangeStream);
2907
2844
  }
2908
2845
  };
2909
2846
 
2910
- options = options || {};
2911
- options.model = this;
2912
-
2913
2847
  return new ChangeStream(changeStreamThunk, pipeline, options);
2914
2848
  };
2915
2849
 
@@ -2995,37 +2929,14 @@ Model.insertMany = async function insertMany(arr, options) {
2995
2929
  throw new MongooseError('Model.insertMany() no longer accepts a callback');
2996
2930
  }
2997
2931
 
2998
- return new Promise((resolve, reject) => {
2999
- this.$__insertMany(arr, options, (err, res) => {
3000
- if (err != null) {
3001
- return reject(err);
3002
- }
3003
- resolve(res);
3004
- });
3005
- });
3006
- };
3007
-
3008
- /**
3009
- * ignore
3010
- *
3011
- * @param {Array} arr
3012
- * @param {Object} options
3013
- * @param {Function} callback
3014
- * @api private
3015
- * @memberOf Model
3016
- * @method $__insertMany
3017
- * @static
3018
- */
3019
-
3020
- Model.$__insertMany = function(arr, options, callback) {
3021
- const _this = this;
3022
- if (typeof options === 'function') {
3023
- callback = options;
3024
- options = null;
2932
+ try {
2933
+ [arr] = await this._middleware.execPre('insertMany', this, [arr]);
2934
+ } catch (error) {
2935
+ await this._middleware.execPost('insertMany', this, [arr], { error });
3025
2936
  }
3026
2937
 
3027
- callback = callback || utils.noop;
3028
2938
  options = options || {};
2939
+ const ThisModel = this;
3029
2940
  const limit = options.limit || 1000;
3030
2941
  const rawResult = !!options.rawResult;
3031
2942
  const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
@@ -3044,238 +2955,212 @@ Model.$__insertMany = function(arr, options, callback) {
3044
2955
  const validationErrors = [];
3045
2956
  const validationErrorsToOriginalOrder = new Map();
3046
2957
  const results = ordered ? null : new Array(arr.length);
3047
- const toExecute = arr.map((doc, index) =>
3048
- callback => {
3049
- // If option `lean` is set to true bypass validation and hydration
3050
- if (lean) {
3051
- // we have to execute callback at the nextTick to be compatible
3052
- // with parallelLimit, as `results` variable has TDZ issue if we
3053
- // execute the callback synchronously
3054
- return immediate(() => callback(null, doc));
3055
- }
3056
- let createdNewDoc = false;
3057
- if (!(doc instanceof _this)) {
3058
- if (doc != null && typeof doc !== 'object') {
3059
- return callback(new ObjectParameterError(doc, 'arr.' + index, 'insertMany'));
3060
- }
3061
- try {
3062
- doc = new _this(doc);
3063
- createdNewDoc = true;
3064
- } catch (err) {
3065
- return callback(err);
3066
- }
2958
+ async function validateDoc(doc, index) {
2959
+ // If option `lean` is set to true bypass validation and hydration
2960
+ if (lean) {
2961
+ return doc;
2962
+ }
2963
+ let createdNewDoc = false;
2964
+ if (!(doc instanceof ThisModel)) {
2965
+ if (doc != null && typeof doc !== 'object') {
2966
+ throw new ObjectParameterError(doc, 'arr.' + index, 'insertMany');
3067
2967
  }
2968
+ doc = new ThisModel(doc);
2969
+ createdNewDoc = true;
2970
+ }
3068
2971
 
3069
- if (options.session != null) {
3070
- doc.$session(options.session);
3071
- }
3072
- // If option `lean` is set to true bypass validation
3073
- if (lean) {
3074
- // we have to execute callback at the nextTick to be compatible
3075
- // with parallelLimit, as `results` variable has TDZ issue if we
3076
- // execute the callback synchronously
3077
- return immediate(() => callback(null, doc));
3078
- }
3079
- doc.$validate(createdNewDoc ? { _skipParallelValidateCheck: true } : null).then(
3080
- () => { callback(null, doc); },
3081
- error => {
3082
- if (ordered === false) {
3083
- // Add index to validation error so users can identify which document failed
3084
- error.index = index;
3085
- validationErrors.push(error);
3086
- validationErrorsToOriginalOrder.set(error, index);
3087
- results[index] = error;
3088
- return callback(null, null);
3089
- }
3090
- callback(error);
2972
+ if (options.session != null) {
2973
+ doc.$session(options.session);
2974
+ }
2975
+ return doc.$validate(createdNewDoc ? { _skipParallelValidateCheck: true } : null)
2976
+ .then(() => doc)
2977
+ .catch(error => {
2978
+ if (ordered === false) {
2979
+ error.index = index;
2980
+ validationErrors.push(error);
2981
+ validationErrorsToOriginalOrder.set(error, index);
2982
+ results[index] = error;
2983
+ return;
3091
2984
  }
3092
- );
3093
- });
2985
+ throw error;
2986
+ });
2987
+ }
3094
2988
 
3095
- parallelLimit(toExecute, limit, function(error, docs) {
3096
- if (error) {
3097
- callback(error, null);
3098
- return;
3099
- }
2989
+ const docs = await parallelLimit(arr, validateDoc, limit);
3100
2990
 
3101
- const originalDocIndex = new Map();
3102
- const validDocIndexToOriginalIndex = new Map();
3103
- for (let i = 0; i < docs.length; ++i) {
3104
- originalDocIndex.set(docs[i], i);
3105
- }
2991
+ const originalDocIndex = new Map();
2992
+ const validDocIndexToOriginalIndex = new Map();
2993
+ for (let i = 0; i < docs.length; ++i) {
2994
+ originalDocIndex.set(docs[i], i);
2995
+ }
2996
+
2997
+ // We filter all failed pre-validations by removing nulls
2998
+ const docAttributes = docs.filter(function(doc) {
2999
+ return doc != null;
3000
+ });
3001
+ for (let i = 0; i < docAttributes.length; ++i) {
3002
+ validDocIndexToOriginalIndex.set(i, originalDocIndex.get(docAttributes[i]));
3003
+ }
3106
3004
 
3107
- // We filter all failed pre-validations by removing nulls
3108
- const docAttributes = docs.filter(function(doc) {
3109
- return doc != null;
3005
+ // Make sure validation errors are in the same order as the
3006
+ // original documents, so if both doc1 and doc2 both fail validation,
3007
+ // `Model.insertMany([doc1, doc2])` will always have doc1's validation
3008
+ // error before doc2's. Re: gh-12791.
3009
+ if (validationErrors.length > 0) {
3010
+ validationErrors.sort((err1, err2) => {
3011
+ return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
3110
3012
  });
3111
- for (let i = 0; i < docAttributes.length; ++i) {
3112
- validDocIndexToOriginalIndex.set(i, originalDocIndex.get(docAttributes[i]));
3113
- }
3013
+ }
3114
3014
 
3115
- // Make sure validation errors are in the same order as the
3116
- // original documents, so if both doc1 and doc2 both fail validation,
3117
- // `Model.insertMany([doc1, doc2])` will always have doc1's validation
3118
- // error before doc2's. Re: gh-12791.
3119
- if (validationErrors.length > 0) {
3120
- validationErrors.sort((err1, err2) => {
3121
- return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
3122
- });
3015
+ // Quickly escape while there aren't any valid docAttributes
3016
+ if (docAttributes.length === 0) {
3017
+ if (throwOnValidationError) {
3018
+ throw new MongooseBulkWriteError(
3019
+ validationErrors,
3020
+ results,
3021
+ null,
3022
+ 'insertMany'
3023
+ );
3123
3024
  }
3124
-
3125
- // Quickly escape while there aren't any valid docAttributes
3126
- if (docAttributes.length === 0) {
3127
- if (throwOnValidationError) {
3128
- return callback(new MongooseBulkWriteError(
3129
- validationErrors,
3130
- results,
3131
- null,
3132
- 'insertMany'
3133
- ));
3134
- }
3135
- if (rawResult) {
3136
- const res = {
3137
- acknowledged: true,
3138
- insertedCount: 0,
3139
- insertedIds: {}
3140
- };
3141
- decorateBulkWriteResult(res, validationErrors, validationErrors);
3142
- return callback(null, res);
3143
- }
3144
- callback(null, []);
3145
- return;
3025
+ if (rawResult) {
3026
+ const res = {
3027
+ acknowledged: true,
3028
+ insertedCount: 0,
3029
+ insertedIds: {}
3030
+ };
3031
+ decorateBulkWriteResult(res, validationErrors, validationErrors);
3032
+ return res;
3146
3033
  }
3147
- const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
3148
- if (doc.$__schema.options.versionKey) {
3149
- doc[doc.$__schema.options.versionKey] = 0;
3150
- }
3151
- const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
3152
- if (shouldSetTimestamps) {
3153
- doc.initializeTimestamps();
3154
- }
3155
- if (doc.$__hasOnlyPrimitiveValues()) {
3156
- return doc.$__toObjectShallow();
3157
- }
3158
- return doc.toObject(internalToObjectOptions);
3159
- });
3160
-
3161
- _this.$__collection.insertMany(docObjects, options).then(
3162
- res => {
3163
- if (!lean) {
3164
- for (const attribute of docAttributes) {
3165
- attribute.$__reset();
3166
- _setIsNew(attribute, false);
3167
- }
3168
- }
3034
+ return [];
3035
+ }
3036
+ const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
3037
+ if (doc.$__schema.options.versionKey) {
3038
+ doc[doc.$__schema.options.versionKey] = 0;
3039
+ }
3040
+ const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
3041
+ if (shouldSetTimestamps) {
3042
+ doc.initializeTimestamps();
3043
+ }
3044
+ if (doc.$__hasOnlyPrimitiveValues()) {
3045
+ return doc.$__toObjectShallow();
3046
+ }
3047
+ return doc.toObject(internalToObjectOptions);
3048
+ });
3169
3049
 
3170
- if (ordered === false && throwOnValidationError && validationErrors.length > 0) {
3171
- for (let i = 0; i < results.length; ++i) {
3172
- if (results[i] === void 0) {
3173
- results[i] = docs[i];
3174
- }
3175
- }
3176
- return callback(new MongooseBulkWriteError(
3177
- validationErrors,
3178
- results,
3179
- res,
3180
- 'insertMany'
3181
- ));
3182
- }
3050
+ let res;
3051
+ try {
3052
+ res = await this.$__collection.insertMany(docObjects, options);
3053
+ } catch (error) {
3054
+ // `writeErrors` is a property reported by the MongoDB driver,
3055
+ // just not if there's only 1 error.
3056
+ if (error.writeErrors == null &&
3057
+ (error.result && error.result.result && error.result.result.writeErrors) != null) {
3058
+ error.writeErrors = error.result.result.writeErrors;
3059
+ }
3183
3060
 
3184
- if (rawResult) {
3185
- if (ordered === false) {
3186
- for (let i = 0; i < results.length; ++i) {
3187
- if (results[i] === void 0) {
3188
- results[i] = docs[i];
3189
- }
3190
- }
3061
+ // `insertedDocs` is a Mongoose-specific property
3062
+ const hasWriteErrors = error && error.writeErrors;
3063
+ const erroredIndexes = new Set((error && error.writeErrors || []).map(err => err.index));
3191
3064
 
3192
- // Decorate with mongoose validation errors in case of unordered,
3193
- // because then still do `insertMany()`
3194
- decorateBulkWriteResult(res, validationErrors, results);
3195
- }
3196
- return callback(null, res);
3065
+ if (error.writeErrors != null) {
3066
+ for (let i = 0; i < error.writeErrors.length; ++i) {
3067
+ const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
3068
+ error.writeErrors[i] = { ...error.writeErrors[i], index: originalIndex };
3069
+ if (!ordered) {
3070
+ results[originalIndex] = error.writeErrors[i];
3197
3071
  }
3072
+ }
3073
+ }
3198
3074
 
3199
- if (options.populate != null) {
3200
- return _this.populate(docAttributes, options.populate).then(
3201
- docs => { callback(null, docs); },
3202
- err => {
3203
- if (err != null) {
3204
- err.insertedDocs = docAttributes;
3205
- }
3206
- throw err;
3207
- }
3208
- );
3075
+ if (!ordered) {
3076
+ for (let i = 0; i < results.length; ++i) {
3077
+ if (results[i] === void 0) {
3078
+ results[i] = docs[i];
3209
3079
  }
3080
+ }
3210
3081
 
3211
- callback(null, docAttributes);
3212
- },
3213
- error => {
3214
- // `writeErrors` is a property reported by the MongoDB driver,
3215
- // just not if there's only 1 error.
3216
- if (error.writeErrors == null &&
3217
- (error.result && error.result.result && error.result.result.writeErrors) != null) {
3218
- error.writeErrors = error.result.result.writeErrors;
3219
- }
3082
+ error.results = results;
3083
+ }
3220
3084
 
3221
- // `insertedDocs` is a Mongoose-specific property
3222
- const hasWriteErrors = error && error.writeErrors;
3223
- const erroredIndexes = new Set((error && error.writeErrors || []).map(err => err.index));
3085
+ let firstErroredIndex = -1;
3086
+ error.insertedDocs = docAttributes.
3087
+ filter((doc, i) => {
3088
+ const isErrored = !hasWriteErrors || erroredIndexes.has(i);
3224
3089
 
3225
- if (error.writeErrors != null) {
3226
- for (let i = 0; i < error.writeErrors.length; ++i) {
3227
- const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
3228
- error.writeErrors[i] = { ...error.writeErrors[i], index: originalIndex };
3229
- if (!ordered) {
3230
- results[originalIndex] = error.writeErrors[i];
3231
- }
3090
+ if (ordered) {
3091
+ if (firstErroredIndex > -1) {
3092
+ return i < firstErroredIndex;
3232
3093
  }
3233
- }
3234
3094
 
3235
- if (!ordered) {
3236
- for (let i = 0; i < results.length; ++i) {
3237
- if (results[i] === void 0) {
3238
- results[i] = docs[i];
3239
- }
3095
+ if (isErrored) {
3096
+ firstErroredIndex = i;
3240
3097
  }
3098
+ }
3241
3099
 
3242
- error.results = results;
3100
+ return !isErrored;
3101
+ }).
3102
+ map(function setIsNewForInsertedDoc(doc) {
3103
+ if (lean) {
3104
+ return doc;
3243
3105
  }
3106
+ doc.$__reset();
3107
+ _setIsNew(doc, false);
3108
+ return doc;
3109
+ });
3244
3110
 
3245
- let firstErroredIndex = -1;
3246
- error.insertedDocs = docAttributes.
3247
- filter((doc, i) => {
3248
- const isErrored = !hasWriteErrors || erroredIndexes.has(i);
3111
+ if (rawResult && ordered === false) {
3112
+ decorateBulkWriteResult(error, validationErrors, results);
3113
+ }
3249
3114
 
3250
- if (ordered) {
3251
- if (firstErroredIndex > -1) {
3252
- return i < firstErroredIndex;
3253
- }
3115
+ await this._middleware.execPost('insertMany', this, [arr], { error });
3116
+ }
3254
3117
 
3255
- if (isErrored) {
3256
- firstErroredIndex = i;
3257
- }
3258
- }
3118
+ if (!lean) {
3119
+ for (const attribute of docAttributes) {
3120
+ attribute.$__reset();
3121
+ _setIsNew(attribute, false);
3122
+ }
3123
+ }
3259
3124
 
3260
- return !isErrored;
3261
- }).
3262
- map(function setIsNewForInsertedDoc(doc) {
3263
- if (lean) {
3264
- return doc;
3265
- }
3266
- doc.$__reset();
3267
- _setIsNew(doc, false);
3268
- return doc;
3269
- });
3125
+ if (ordered === false && throwOnValidationError && validationErrors.length > 0) {
3126
+ for (let i = 0; i < results.length; ++i) {
3127
+ if (results[i] === void 0) {
3128
+ results[i] = docs[i];
3129
+ }
3130
+ }
3131
+ throw new MongooseBulkWriteError(
3132
+ validationErrors,
3133
+ results,
3134
+ res,
3135
+ 'insertMany'
3136
+ );
3137
+ }
3270
3138
 
3271
- if (rawResult && ordered === false) {
3272
- decorateBulkWriteResult(error, validationErrors, results);
3139
+ if (rawResult) {
3140
+ if (ordered === false) {
3141
+ for (let i = 0; i < results.length; ++i) {
3142
+ if (results[i] === void 0) {
3143
+ results[i] = docs[i];
3273
3144
  }
3145
+ }
3274
3146
 
3275
- callback(error, null);
3147
+ // Decorate with mongoose validation errors in case of unordered,
3148
+ // because then still do `insertMany()`
3149
+ decorateBulkWriteResult(res, validationErrors, results);
3150
+ }
3151
+ return res;
3152
+ }
3153
+
3154
+ if (options.populate != null) {
3155
+ return this.populate(docAttributes, options.populate).catch(err => {
3156
+ if (err != null) {
3157
+ err.insertedDocs = docAttributes;
3276
3158
  }
3277
- );
3278
- });
3159
+ throw err;
3160
+ });
3161
+ }
3162
+
3163
+ return await this._middleware.execPost('insertMany', this, [docAttributes]).then(res => res[0]);
3279
3164
  };
3280
3165
 
3281
3166
  /*!
@@ -3401,20 +3286,15 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3401
3286
  }
3402
3287
  options = options || {};
3403
3288
 
3404
- const shouldSkip = await new Promise((resolve, reject) => {
3405
- this.hooks.execPre('bulkWrite', this, [ops, options], (err) => {
3406
- if (err != null) {
3407
- if (err instanceof Kareem.skipWrappedFunction) {
3408
- return resolve(err);
3409
- }
3410
- return reject(err);
3411
- }
3412
- resolve();
3413
- });
3289
+ [ops, options] = await this.hooks.execPre('bulkWrite', this, [ops, options]).catch(err => {
3290
+ if (err instanceof Kareem.skipWrappedFunction) {
3291
+ return [err];
3292
+ }
3293
+ throw err;
3414
3294
  });
3415
3295
 
3416
- if (shouldSkip) {
3417
- return shouldSkip.args[0];
3296
+ if (ops instanceof Kareem.skipWrappedFunction) {
3297
+ return ops.args[0];
3418
3298
  }
3419
3299
 
3420
3300
  const ordered = options.ordered == null ? true : options.ordered;
@@ -3448,15 +3328,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3448
3328
  try {
3449
3329
  res = await this.$__collection.bulkWrite(ops, options);
3450
3330
  } catch (error) {
3451
- await new Promise((resolve, reject) => {
3452
- const _opts = { error: error };
3453
- this.hooks.execPost('bulkWrite', this, [null], _opts, (err) => {
3454
- if (err != null) {
3455
- return reject(err);
3456
- }
3457
- resolve();
3458
- });
3459
- });
3331
+ await this.hooks.execPost('bulkWrite', this, [null], { error });
3460
3332
  }
3461
3333
  } else {
3462
3334
  let validOpIndexes = [];
@@ -3525,15 +3397,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3525
3397
  decorateBulkWriteResult(error, validationErrors, results);
3526
3398
  }
3527
3399
 
3528
- await new Promise((resolve, reject) => {
3529
- const _opts = { error: error };
3530
- this.hooks.execPost('bulkWrite', this, [null], _opts, (err) => {
3531
- if (err != null) {
3532
- return reject(err);
3533
- }
3534
- resolve();
3535
- });
3536
- });
3400
+ await this.hooks.execPost('bulkWrite', this, [null], { error });
3537
3401
  }
3538
3402
 
3539
3403
  if (validationErrors.length > 0) {
@@ -3550,14 +3414,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3550
3414
  }
3551
3415
  }
3552
3416
 
3553
- await new Promise((resolve, reject) => {
3554
- this.hooks.execPost('bulkWrite', this, [res], (err) => {
3555
- if (err != null) {
3556
- return reject(err);
3557
- }
3558
- resolve();
3559
- });
3560
- });
3417
+ await this.hooks.execPost('bulkWrite', this, [res]);
3561
3418
 
3562
3419
  return res;
3563
3420
  };
@@ -3647,34 +3504,20 @@ Model.bulkSave = async function bulkSave(documents, options) {
3647
3504
  return bulkWriteResult;
3648
3505
  };
3649
3506
 
3650
- function buildPreSavePromise(document, options) {
3651
- return new Promise((resolve, reject) => {
3652
- document.schema.s.hooks.execPre('save', document, [options], (err) => {
3653
- if (err) {
3654
- reject(err);
3655
- return;
3656
- }
3657
- resolve();
3658
- });
3659
- });
3507
+ async function buildPreSavePromise(document, options) {
3508
+ const [newOptions] = await document.schema.s.hooks.execPre('save', document, [options]);
3509
+ if (newOptions !== options) {
3510
+ throw new Error('Cannot overwrite options in pre("save") hook on bulkSave()');
3511
+ }
3660
3512
  }
3661
3513
 
3662
- function handleSuccessfulWrite(document) {
3663
- return new Promise((resolve, reject) => {
3664
- if (document.$isNew) {
3665
- _setIsNew(document, false);
3666
- }
3667
-
3668
- document.$__reset();
3669
- document.schema.s.hooks.execPost('save', document, [document], {}, (err) => {
3670
- if (err) {
3671
- reject(err);
3672
- return;
3673
- }
3674
- resolve();
3675
- });
3514
+ async function handleSuccessfulWrite(document) {
3515
+ if (document.$isNew) {
3516
+ _setIsNew(document, false);
3517
+ }
3676
3518
 
3677
- });
3519
+ document.$__reset();
3520
+ return document.schema.s.hooks.execPost('save', document, [document]);
3678
3521
  }
3679
3522
 
3680
3523
  /**
@@ -3847,7 +3690,7 @@ Model.castObject = function castObject(obj, options) {
3847
3690
  }
3848
3691
  } else {
3849
3692
  cur[pieces[pieces.length - 1]] = [
3850
- Model.castObject.call(schemaType.caster, val)
3693
+ Model.castObject.call(schemaType.Constructor, val)
3851
3694
  ];
3852
3695
  }
3853
3696
 
@@ -3856,7 +3699,7 @@ Model.castObject = function castObject(obj, options) {
3856
3699
  }
3857
3700
  if (schemaType.$isSingleNested || schemaType.$isMongooseDocumentArrayElement) {
3858
3701
  try {
3859
- val = Model.castObject.call(schemaType.caster, val);
3702
+ val = Model.castObject.call(schemaType.Constructor, val);
3860
3703
  } catch (err) {
3861
3704
  if (!options.ignoreCastErrors) {
3862
3705
  error = error || new ValidationError();
@@ -4322,51 +4165,34 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
4322
4165
  }
4323
4166
  }
4324
4167
 
4325
- let remaining = paths.size;
4326
-
4327
- return new Promise((resolve, reject) => {
4328
- if (remaining === 0) {
4329
- return settle();
4168
+ const promises = [];
4169
+ for (const path of paths) {
4170
+ const schemaType = schema.path(path);
4171
+ if (schemaType == null) {
4172
+ continue;
4330
4173
  }
4331
4174
 
4332
- for (const path of paths) {
4333
- const schemaType = schema.path(path);
4334
- if (schemaType == null) {
4335
- _checkDone();
4336
- continue;
4337
- }
4338
-
4339
- const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
4340
- let cur = obj;
4341
- for (let i = 0; i < pieces.length - 1; ++i) {
4342
- cur = cur[pieces[i]];
4343
- }
4344
-
4345
- const val = get(obj, path, void 0);
4346
-
4347
- schemaType.doValidate(val, err => {
4348
- if (err) {
4349
- error = error || new ValidationError();
4350
- error.addError(path, err);
4351
- }
4352
- _checkDone();
4353
- }, context, { path: path });
4175
+ const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
4176
+ let cur = obj;
4177
+ for (let i = 0; i < pieces.length - 1; ++i) {
4178
+ cur = cur[pieces[i]];
4354
4179
  }
4355
4180
 
4356
- function settle() {
4357
- if (error) {
4358
- reject(error);
4359
- } else {
4360
- resolve(obj);
4361
- }
4362
- }
4181
+ const val = get(obj, path, void 0);
4182
+ promises.push(
4183
+ schemaType.doValidate(val, context, { path: path }).catch(err => {
4184
+ error = error || new ValidationError();
4185
+ error.addError(path, err);
4186
+ })
4187
+ );
4188
+ }
4363
4189
 
4364
- function _checkDone() {
4365
- if (--remaining <= 0) {
4366
- return settle();
4367
- }
4368
- }
4369
- });
4190
+ await Promise.all(promises);
4191
+ if (error != null) {
4192
+ throw error;
4193
+ }
4194
+
4195
+ return obj;
4370
4196
  };
4371
4197
 
4372
4198
  /**
@@ -4736,14 +4562,7 @@ function _assign(model, vals, mod, assignmentOpts) {
4736
4562
  if (__val instanceof Document) {
4737
4563
  __val = __val._doc._id;
4738
4564
  }
4739
- if (__val?.constructor?.name === 'Binary' && __val.sub_type === 4 && typeof __val.toUUID === 'function') {
4740
- // Workaround for gh-15315 because Mongoose UUIDs don't use BSON UUIDs yet.
4741
- key = String(__val.toUUID());
4742
- } else if (__val?.constructor?.name === 'Buffer' && __val._subtype === 4 && typeof __val.toUUID === 'function') {
4743
- key = String(__val.toUUID());
4744
- } else {
4745
- key = String(__val);
4746
- }
4565
+ key = String(__val);
4747
4566
  if (rawDocs[key]) {
4748
4567
  if (Array.isArray(rawDocs[key])) {
4749
4568
  rawDocs[key].push(val);
@@ -4766,14 +4585,7 @@ function _assign(model, vals, mod, assignmentOpts) {
4766
4585
  if (_val instanceof Document) {
4767
4586
  _val = _val._doc._id;
4768
4587
  }
4769
- if (_val?.constructor?.name === 'Binary' && _val.sub_type === 4 && typeof _val.toUUID === 'function') {
4770
- // Workaround for gh-15315 because Mongoose UUIDs don't use BSON UUIDs yet.
4771
- key = String(_val.toUUID());
4772
- } else if (_val?.constructor?.name === 'Buffer' && _val._subtype === 4 && typeof _val.toUUID === 'function') {
4773
- key = String(_val.toUUID());
4774
- } else {
4775
- key = String(_val);
4776
- }
4588
+ key = String(_val);
4777
4589
  if (rawDocs[key]) {
4778
4590
  if (Array.isArray(rawDocs[key])) {
4779
4591
  rawDocs[key].push(val);