@depup/mongoose 9.3.3-depup.0 → 9.8.0-depup.0

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 (73) hide show
  1. package/README.md +3 -9
  2. package/changes.json +3 -8
  3. package/eslint.config.mjs +4 -1
  4. package/lib/aggregate.js +54 -24
  5. package/lib/cast.js +1 -1
  6. package/lib/connection.js +20 -8
  7. package/lib/cursor/aggregationCursor.js +47 -16
  8. package/lib/cursor/queryCursor.js +23 -7
  9. package/lib/document.js +240 -105
  10. package/lib/drivers/node-mongodb-native/collection.js +47 -14
  11. package/lib/drivers/node-mongodb-native/connection.js +13 -0
  12. package/lib/error/cast.js +18 -9
  13. package/lib/error/divergentArray.js +1 -1
  14. package/lib/error/index.js +1 -1
  15. package/lib/error/messages.js +1 -0
  16. package/lib/helpers/clone.js +22 -5
  17. package/lib/helpers/document/applyDefaults.js +5 -0
  18. package/lib/helpers/populate/createPopulateQueryFilter.js +9 -1
  19. package/lib/helpers/populate/getModelsMapForPopulate.js +8 -6
  20. package/lib/helpers/populate/splitPopulateQuery.js +81 -0
  21. package/lib/helpers/query/castUpdate.js +4 -0
  22. package/lib/helpers/schema/getKeysInSchemaOrder.js +13 -16
  23. package/lib/helpers/update/applyTimestampsToUpdate.js +4 -2
  24. package/lib/model.js +159 -41
  25. package/lib/mongoose.js +3 -3
  26. package/lib/options/schemaTypeOptions.js +14 -0
  27. package/lib/plugins/saveSubdocs.js +16 -13
  28. package/lib/query.js +208 -102
  29. package/lib/queryHelpers.js +13 -12
  30. package/lib/schema/array.js +4 -6
  31. package/lib/schema/bigint.js +1 -3
  32. package/lib/schema/boolean.js +1 -3
  33. package/lib/schema/buffer.js +1 -3
  34. package/lib/schema/date.js +8 -8
  35. package/lib/schema/decimal128.js +1 -3
  36. package/lib/schema/documentArray.js +3 -5
  37. package/lib/schema/double.js +1 -3
  38. package/lib/schema/int32.js +1 -3
  39. package/lib/schema/map.js +1 -4
  40. package/lib/schema/number.js +2 -4
  41. package/lib/schema/objectId.js +7 -3
  42. package/lib/schema/string.js +20 -5
  43. package/lib/schema/subdocument.js +2 -4
  44. package/lib/schema/union.js +50 -0
  45. package/lib/schema/uuid.js +1 -3
  46. package/lib/schema.js +40 -14
  47. package/lib/schemaType.js +93 -12
  48. package/lib/standardSchema/convertErrorToIssues.js +20 -0
  49. package/lib/stateMachine.js +11 -6
  50. package/lib/tracing.js +38 -0
  51. package/lib/types/array/methods/index.js +4 -4
  52. package/lib/types/documentArray/methods/index.js +117 -3
  53. package/lib/types/subdocument.js +4 -2
  54. package/lib/utils.js +12 -2
  55. package/lib/validOptions.js +1 -0
  56. package/package.json +30 -33
  57. package/{tstyche.config.json → tstyche.json} +2 -1
  58. package/types/augmentations.d.ts +2 -2
  59. package/types/document.d.ts +61 -7
  60. package/types/expressions.d.ts +16 -0
  61. package/types/index.d.ts +25 -7
  62. package/types/inferhydrateddoctype.d.ts +1 -1
  63. package/types/inferrawdoctype.d.ts +6 -3
  64. package/types/inferschematype.d.ts +10 -2
  65. package/types/models.d.ts +30 -13
  66. package/types/mongooseoptions.d.ts +9 -1
  67. package/types/populate.d.ts +52 -0
  68. package/types/query.d.ts +39 -12
  69. package/types/schemaoptions.d.ts +5 -0
  70. package/types/schematypes.d.ts +10 -0
  71. package/types/tracing.d.ts +10 -0
  72. package/types/utility.d.ts +11 -2
  73. package/lib/helpers/createJSONSchemaTypeDefinition.js +0 -24
package/lib/model.js CHANGED
@@ -7,6 +7,10 @@
7
7
  const Aggregate = require('./aggregate');
8
8
  const ChangeStream = require('./cursor/changeStream');
9
9
  const Document = require('./document');
10
+ const { createTracedChannel } = require('./tracing');
11
+ const { trace: traceSave } = createTracedChannel('mongoose:model:save');
12
+ const { trace: traceInsertMany } = createTracedChannel('mongoose:model:insertMany');
13
+ const { trace: traceBulkWrite } = createTracedChannel('mongoose:model:bulkWrite');
10
14
  const DocumentNotFoundError = require('./error/notFound');
11
15
  const EventEmitter = require('events').EventEmitter;
12
16
  const Kareem = require('kareem');
@@ -37,6 +41,7 @@ const applyVirtualsHelper = require('./helpers/document/applyVirtuals');
37
41
  const assignVals = require('./helpers/populate/assignVals');
38
42
  const castBulkWrite = require('./helpers/model/castBulkWrite');
39
43
  const clone = require('./helpers/clone');
44
+ const convertErrorToStandardSchemaIssues = require('./standardSchema/convertErrorToIssues');
40
45
  const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
41
46
  const decorateUpdateWithVersionKey = require('./helpers/update/decorateUpdateWithVersionKey');
42
47
  const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
@@ -66,6 +71,7 @@ const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscrim
66
71
  const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
67
72
  const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
68
73
  const setDottedPath = require('./helpers/path/setDottedPath');
74
+ const splitPopulateQuery = require('./helpers/populate/splitPopulateQuery');
69
75
  const { buildMiddlewareFilter } = require('./helpers/buildMiddlewareFilter');
70
76
  const util = require('util');
71
77
  const utils = require('./utils');
@@ -189,8 +195,11 @@ Model.prototype.db;
189
195
  */
190
196
 
191
197
  Model.useConnection = function useConnection(connection) {
192
- if (!connection) {
193
- throw new MongooseError('Please provide a connection.');
198
+ if (typeof connection?.model !== 'function' || typeof connection.collection !== 'function' || typeof connection.base?.version !== 'string') {
199
+ throw new MongooseError('`useConnection()` requires a Mongoose connection.');
200
+ }
201
+ if (this.db?.base?.version && this.db?.base?.version !== connection.base?.version) {
202
+ throw new MongooseError(`The connection passed to \`useConnection()\` has a different version of Mongoose (${connection.base?.version}) than the model you are using (${this.db?.base?.version}).`);
194
203
  }
195
204
  if (this.db) {
196
205
  delete this.db.models[this.modelName];
@@ -635,7 +644,7 @@ Model.prototype.save = async function save(options) {
635
644
  if (this.$__.saving) {
636
645
  parallelSave = new ParallelSaveError(this);
637
646
  } else {
638
- this.$__.saving = new ParallelSaveError(this);
647
+ this.$__.saving = true;
639
648
  }
640
649
 
641
650
  options = new SaveOptions(options);
@@ -660,19 +669,29 @@ Model.prototype.save = async function save(options) {
660
669
 
661
670
  this.$__.saveOptions = options;
662
671
 
663
- try {
664
- await this.$__save(options);
665
- } catch (error) {
666
- this.$__handleReject(error);
667
- throw error;
668
- } finally {
669
- this.$__.saving = null;
670
- this.$__.saveOptions = null;
671
- this.$__.$versionError = null;
672
- this.$op = null;
673
- }
674
-
675
- return this;
672
+ const _this = this;
673
+ return traceSave(async function maybeTracedSave() {
674
+ try {
675
+ await _this.$__save(options);
676
+ } catch (error) {
677
+ _this.$__handleReject(error);
678
+ throw error;
679
+ } finally {
680
+ _this.$__.saving = null;
681
+ _this.$__.saveOptions = null;
682
+ _this.$__.$versionError = null;
683
+ _this.$op = null;
684
+ }
685
+
686
+ return _this;
687
+ }, () => ({
688
+ operation: 'save',
689
+ collection: _this.constructor.collection.name,
690
+ database: _this.constructor.db?.name,
691
+ serverAddress: _this.constructor.db?.host,
692
+ serverPort: _this.constructor.db?.port,
693
+ args: { options }
694
+ }));
676
695
  };
677
696
 
678
697
  Model.prototype.$save = Model.prototype.save;
@@ -2322,7 +2341,7 @@ Model.where = function where(path, val) {
2322
2341
  *
2323
2342
  * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
2324
2343
  *
2325
- * Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});
2344
+ * const result = await Blog.$where('this.username.indexOf("val") !== -1').exec();
2326
2345
  *
2327
2346
  * @param {string|Function} argument is a javascript string or anonymous function
2328
2347
  * @method $where
@@ -2415,13 +2434,6 @@ Model.findOneAndUpdate = function(conditions, update, options) {
2415
2434
  fields = options.fields || options.projection;
2416
2435
  }
2417
2436
 
2418
- update = clone(update, {
2419
- depopulate: true,
2420
- _isNested: true
2421
- });
2422
-
2423
- decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);
2424
-
2425
2437
  const mq = new this.Query({}, {}, this, this.$__collection);
2426
2438
  mq.select(fields);
2427
2439
 
@@ -2861,6 +2873,10 @@ Model.create = async function create(doc, options) {
2861
2873
  Model.insertOne = async function insertOne(doc, options) {
2862
2874
  _checkContext(this, 'insertOne');
2863
2875
 
2876
+ if (doc == null || typeof doc !== 'object') {
2877
+ throw new ObjectParameterError(doc, 'doc', 'insertOne');
2878
+ }
2879
+
2864
2880
  const discriminatorKey = this.schema.options.discriminatorKey;
2865
2881
  const Model = this.discriminators && doc[discriminatorKey] != null ?
2866
2882
  this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
@@ -3015,6 +3031,18 @@ Model.insertMany = async function insertMany(arr, options) {
3015
3031
  throw new MongooseError('Model.insertMany() no longer accepts a callback');
3016
3032
  }
3017
3033
 
3034
+ const ThisModel = this;
3035
+ return traceInsertMany(function maybeTracedInsertMany() { return _insertMany.call(ThisModel, arr, options); }, () => ({
3036
+ operation: 'insertMany',
3037
+ collection: ThisModel.collection.name,
3038
+ database: ThisModel.db?.name,
3039
+ serverAddress: ThisModel.db?.host,
3040
+ serverPort: ThisModel.db?.port,
3041
+ args: { docs: arr, options }
3042
+ }));
3043
+ };
3044
+
3045
+ async function _insertMany(arr, options) {
3018
3046
  options = options || {};
3019
3047
  const preFilter = buildMiddlewareFilter(options, 'pre');
3020
3048
  const postFilter = buildMiddlewareFilter(options, 'post');
@@ -3250,7 +3278,7 @@ Model.insertMany = async function insertMany(arr, options) {
3250
3278
 
3251
3279
  const [result] = await this._middleware.execPost('insertMany', this, [docAttributes], { filter: postFilter });
3252
3280
  return result;
3253
- };
3281
+ }
3254
3282
 
3255
3283
  /*!
3256
3284
  * ignore
@@ -3378,6 +3406,19 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3378
3406
  typeof arguments[2] === 'function') {
3379
3407
  throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
3380
3408
  }
3409
+
3410
+ const ThisModel = this;
3411
+ return traceBulkWrite(function maybeTracedBulkWrite() { return _bulkWrite.call(ThisModel, ops, options); }, () => ({
3412
+ operation: 'bulkWrite',
3413
+ collection: ThisModel.collection.name,
3414
+ database: ThisModel.db?.name,
3415
+ serverAddress: ThisModel.db?.host,
3416
+ serverPort: ThisModel.db?.port,
3417
+ args: { ops, options }
3418
+ }));
3419
+ };
3420
+
3421
+ async function _bulkWrite(ops, options) {
3381
3422
  options = options || {};
3382
3423
  const preFilter = buildMiddlewareFilter(options, 'pre');
3383
3424
  const postFilter = buildMiddlewareFilter(options, 'post');
@@ -3516,7 +3557,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3516
3557
  await this.hooks.execPost('bulkWrite', this, [res], { filter: postFilter });
3517
3558
 
3518
3559
  return res;
3519
- };
3560
+ }
3520
3561
 
3521
3562
  /**
3522
3563
  * Takes an array of documents, gets the changes and inserts/updates documents in the database
@@ -3615,12 +3656,16 @@ async function buildPreSavePromise(document, options) {
3615
3656
  }
3616
3657
 
3617
3658
  async function handleSuccessfulWrite(document, options) {
3618
- if (document.$isNew) {
3659
+ const wasNew = document.$isNew;
3660
+ if (wasNew) {
3619
3661
  _setIsNew(document, false);
3620
3662
  }
3621
3663
 
3622
3664
  document.$__reset();
3623
- document._applyVersionIncrement();
3665
+ // version key gets initialized to 0 when inserting a new document so avoid incrementing version key here
3666
+ if (!wasNew) {
3667
+ document._applyVersionIncrement();
3668
+ }
3624
3669
  const postFilter = buildMiddlewareFilter(options, 'post');
3625
3670
  return document.schema.s.hooks.execPost('save', document, [document], { filter: postFilter });
3626
3671
  }
@@ -3861,7 +3906,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3861
3906
  throw new MongooseError(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
3862
3907
  }
3863
3908
  if (options.validateBeforeSave == null || options.validateBeforeSave) {
3864
- const err = document.validateSync();
3909
+ const err = document.$__validateSync();
3865
3910
  if (err != null) {
3866
3911
  throw err;
3867
3912
  }
@@ -3870,6 +3915,12 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3870
3915
 
3871
3916
  const isANewDocument = document.isNew;
3872
3917
  if (isANewDocument) {
3918
+ // Like `$__version()` on insert, initialize the version key so
3919
+ // inserted documents get the same shape as `save()` gives them
3920
+ const versionKey = document.$__schema.options.versionKey;
3921
+ if (versionKey) {
3922
+ document.$__setValue(versionKey, 0);
3923
+ }
3873
3924
  const writeOperation = { insertOne: { document } };
3874
3925
  utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
3875
3926
  return writeOperation;
@@ -4188,13 +4239,24 @@ Model.aggregate = function aggregate(pipeline, options) {
4188
4239
  * age: { type: Number, required: true }
4189
4240
  * });
4190
4241
  *
4242
+ * // Succeeds
4243
+ * await Model.validate({ name: 'John Smith', age: 31 });
4244
+ *
4191
4245
  * try {
4192
- * await Model.validate({ name: null }, ['name'])
4246
+ * await Model.validate({ name: null });
4193
4247
  * } catch (err) {
4194
4248
  * err instanceof mongoose.Error.ValidationError; // true
4195
4249
  * Object.keys(err.errors); // ['name']
4196
4250
  * }
4197
4251
  *
4252
+ * Note: the `pathsToSkip` and `pathsToValidate` options **only** apply to validation, not
4253
+ * casting. This function will still throw an error for values that cannot be casted to the
4254
+ * schema-specified type. Remove any paths you do not want to cast.
4255
+ *
4256
+ * // The following will still throw an error because the value of `age` cannot be
4257
+ * // casted to a number. Remove the `age` property before calling `validate()`.
4258
+ * await Model.validate({ name: 'Test', age: 'not a number' }, ['name']);
4259
+ *
4198
4260
  * @param {object} obj
4199
4261
  * @param {object|Array|string} pathsOrOptions
4200
4262
  * @param {object} [context]
@@ -4299,6 +4361,33 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
4299
4361
  return obj;
4300
4362
  };
4301
4363
 
4364
+ /**
4365
+ * Standard Schema adapter for this model.
4366
+ * Calls [`Model.validate()`](https://mongoosejs.com/docs/api/model.html#Model.validate()) internally with the provided `libraryOptions`
4367
+ * to cast and validate the given value.
4368
+ *
4369
+ * @api public
4370
+ * @property ~standard
4371
+ * @memberOf Model
4372
+ * @static
4373
+ */
4374
+
4375
+ Object.defineProperty(Model, '~standard', {
4376
+ configurable: true,
4377
+ get() {
4378
+ return {
4379
+ version: 1,
4380
+ vendor: 'mongoose',
4381
+ validate: (value, options) => {
4382
+ return this.validate(value, options?.libraryOptions).then(
4383
+ value => ({ value }),
4384
+ error => ({ issues: convertErrorToStandardSchemaIssues(error) })
4385
+ );
4386
+ }
4387
+ };
4388
+ }
4389
+ });
4390
+
4302
4391
  /**
4303
4392
  * Populates document references.
4304
4393
  *
@@ -4465,7 +4554,6 @@ async function _populatePath(model, docs, populateOptions) {
4465
4554
  mod.foreignField.clear();
4466
4555
  mod.foreignField.add(populateOptions.foreignField);
4467
4556
  }
4468
- const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
4469
4557
  if (assignmentOpts.excludeId) {
4470
4558
  // override the exclusion from the query so we can use the _id
4471
4559
  // for document matching during assignment. we'll delete the
@@ -4486,6 +4574,17 @@ async function _populatePath(model, docs, populateOptions) {
4486
4574
  } else if (mod.options.limit != null) {
4487
4575
  assignmentOpts.originalLimit = mod.options.limit;
4488
4576
  }
4577
+
4578
+ // Execute a separate query per document if `perDocumentLimit` is set (gh-7318), or if
4579
+ // there are so many ids that the `$in` filter may overflow MongoDB's 16 MB BSON size
4580
+ // limit on queries (gh-5890).
4581
+ const splitParams = splitPopulateQuery(mod, ids, select, assignmentOpts);
4582
+ if (splitParams != null) {
4583
+ params.push(...splitParams);
4584
+ continue;
4585
+ }
4586
+
4587
+ const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
4489
4588
  params.push([mod, match, select, assignmentOpts]);
4490
4589
  }
4491
4590
  if (!hasOne) {
@@ -4506,6 +4605,9 @@ async function _populatePath(model, docs, populateOptions) {
4506
4605
 
4507
4606
  // Track deferred populates per-param (per model) to avoid mixing them
4508
4607
  const deferredPopulatesPerParam = new Map();
4608
+ // Query results per param. Batches that were split off of a large populate query (gh-5890)
4609
+ // are assigned from only their own query's results rather than the combined `vals`.
4610
+ const valsByParam = [];
4509
4611
 
4510
4612
  if (populateOptions.ordered) {
4511
4613
  // Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
@@ -4513,7 +4615,10 @@ async function _populatePath(model, docs, populateOptions) {
4513
4615
  for (let i = 0; i < params.length; i++) {
4514
4616
  const arr = params[i];
4515
4617
  const { docs, deferredPopulates } = await _execPopulateQuery.apply(null, arr);
4516
- vals = vals.concat(docs);
4618
+ valsByParam.push(docs);
4619
+ if (!arr[0]._assignFromOwnResults) {
4620
+ vals = vals.concat(docs);
4621
+ }
4517
4622
  if (deferredPopulates.length > 0) {
4518
4623
  deferredPopulatesPerParam.set(i, deferredPopulates);
4519
4624
  }
@@ -4528,7 +4633,10 @@ async function _populatePath(model, docs, populateOptions) {
4528
4633
  const results = await Promise.all(promises);
4529
4634
  for (let i = 0; i < results.length; i++) {
4530
4635
  const { docs, deferredPopulates } = results[i];
4531
- vals = vals.concat(docs);
4636
+ valsByParam.push(docs);
4637
+ if (!params[i][0]._assignFromOwnResults) {
4638
+ vals = vals.concat(docs);
4639
+ }
4532
4640
  if (deferredPopulates.length > 0) {
4533
4641
  deferredPopulatesPerParam.set(i, deferredPopulates);
4534
4642
  }
@@ -4536,13 +4644,15 @@ async function _populatePath(model, docs, populateOptions) {
4536
4644
  }
4537
4645
 
4538
4646
 
4539
- for (const arr of params) {
4647
+ for (let i = 0; i < params.length; i++) {
4648
+ const arr = params[i];
4540
4649
  const mod = arr[0];
4541
4650
  const assignmentOpts = arr[3];
4542
- for (const val of vals) {
4651
+ const valsForMod = mod._assignFromOwnResults ? valsByParam[i] : vals;
4652
+ for (const val of valsForMod) {
4543
4653
  mod.options._childDocs.push(val);
4544
4654
  }
4545
- _assign(model, vals, mod, assignmentOpts);
4655
+ _assign(model, valsForMod, mod, assignmentOpts);
4546
4656
  }
4547
4657
 
4548
4658
  // Handle deferred populate for cases with per-document match functions.
@@ -4582,13 +4692,15 @@ async function _populatePath(model, docs, populateOptions) {
4582
4692
  }
4583
4693
  }
4584
4694
 
4585
- for (const arr of params) {
4586
- removeDeselectedForeignField(arr[0].foreignField, arr[0].options, vals);
4695
+ for (let i = 0; i < params.length; i++) {
4696
+ const mod = params[i][0];
4697
+ removeDeselectedForeignField(mod.foreignField, mod.options, mod._assignFromOwnResults ? valsByParam[i] : vals);
4587
4698
  }
4588
- for (const arr of params) {
4589
- const mod = arr[0];
4699
+ for (let i = 0; i < params.length; i++) {
4700
+ const mod = params[i][0];
4590
4701
  if (mod.options?.options?._leanTransform) {
4591
- for (const doc of vals) {
4702
+ const valsForMod = mod._assignFromOwnResults ? valsByParam[i] : vals;
4703
+ for (const doc of valsForMod) {
4592
4704
  mod.options.options._leanTransform(doc);
4593
4705
  }
4594
4706
  }
@@ -4600,6 +4712,12 @@ async function _populatePath(model, docs, populateOptions) {
4600
4712
  */
4601
4713
 
4602
4714
  function _execPopulateQuery(mod, match, select) {
4715
+ // `null` match means `mod`'s documents have no ids to query, possible if a large populate
4716
+ // was split into a query per document (gh-5890). Skip executing a query: `_assign()` still
4717
+ // sets the documents' populated paths to the appropriate default value.
4718
+ if (match == null) {
4719
+ return Promise.resolve({ docs: [], deferredPopulates: [] });
4720
+ }
4603
4721
  let subPopulate = clone(mod.options.populate);
4604
4722
  const queryOptions = {};
4605
4723
  if (mod.options.skip !== undefined) {
package/lib/mongoose.js CHANGED
@@ -228,7 +228,7 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
228
228
  * - `bufferCommands`: enable/disable mongoose's buffering mechanism for all connections and models
229
229
  * - `bufferTimeoutMS`: If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before throwing an error. If not specified, Mongoose will use 10000 (10 seconds).
230
230
  * - `cloneSchemas`: `false` by default. Set to `true` to `clone()` all schemas before compiling into a model.
231
- * - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
231
+ * - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If an object is passed, you can set `color`, `shell`, and `timestamp` options. If `timestamp` is `true`, Mongoose prefixes console debug output with an ISO timestamp in brackets. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
232
232
  * - `id`: If `true`, adds a `id` virtual to all schemas unless overwritten on a per-schema basis.
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.
@@ -398,8 +398,8 @@ Mongoose.prototype.get = Mongoose.prototype.set;
398
398
  * @param {string} [options.user] username for authentication, equivalent to `options.auth.username`. Maintained for backwards compatibility.
399
399
  * @param {string} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
400
400
  * @param {boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
401
- * @param {number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
402
- * @param {number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
401
+ * @param {number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
402
+ * @param {number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
403
403
  * @param {number} [options.socketTimeoutMS=0] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. Defaults to 0, which means Node.js will not time out the socket due to inactivity. A socket may be inactive because of either no activity or a long-running operation. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes.
404
404
  * @param {number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both.
405
405
  * @return {Connection} the created Connection object. Connections are not thenable, so you can't do `await mongoose.createConnection()`. To await use `mongoose.createConnection(uri).asPromise()` instead.
@@ -94,6 +94,20 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'cast', opts);
94
94
 
95
95
  Object.defineProperty(SchemaTypeOptions.prototype, 'required', opts);
96
96
 
97
+ /**
98
+ * Controls whether this path may be set to `null`. By default, Mongoose allows
99
+ * `null` for non-required paths. Set `allowNull: false` to allow `undefined`
100
+ * but disallow `null`.
101
+ *
102
+ * @api public
103
+ * @property allowNull
104
+ * @memberOf SchemaTypeOptions
105
+ * @type {boolean}
106
+ * @instance
107
+ */
108
+
109
+ Object.defineProperty(SchemaTypeOptions.prototype, 'allowNull', opts);
110
+
97
111
  /**
98
112
  * The default value for this path. If a function, Mongoose executes the function
99
113
  * and uses the return value as the default.
@@ -16,7 +16,10 @@ module.exports = function saveSubdocs(schema) {
16
16
  };
17
17
 
18
18
 
19
- async function saveSubdocsPreSave() {
19
+ // These hooks are deliberately not `async` so that they don't allocate a
20
+ // promise and force an extra microtask hop on every `save()` when there are
21
+ // no subdocuments. kareem only awaits hooks that return a promise.
22
+ function saveSubdocsPreSave() {
20
23
  if (this.$isSubdocument) {
21
24
  return;
22
25
  }
@@ -28,15 +31,15 @@ async function saveSubdocsPreSave() {
28
31
  }
29
32
 
30
33
  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
- }
34
+ return Promise.all(subdocs.map(subdoc => subdoc._execDocumentPreHooks('save', options, [options]))).then(() => {
35
+ // Invalidate subdocs cache because subdoc pre hooks can add new subdocuments
36
+ if (this.$__.saveOptions) {
37
+ this.$__.saveOptions.__subdocs = null;
38
+ }
39
+ });
37
40
  }
38
41
 
39
- async function saveSubdocsPostSave() {
42
+ function saveSubdocsPostSave() {
40
43
  if (this.$isSubdocument) {
41
44
  return;
42
45
  }
@@ -53,10 +56,10 @@ async function saveSubdocsPostSave() {
53
56
  promises.push(subdoc._execDocumentPostHooks('save', options));
54
57
  }
55
58
 
56
- await Promise.all(promises);
59
+ return Promise.all(promises);
57
60
  }
58
61
 
59
- async function saveSubdocsPreDeleteOne() {
62
+ function saveSubdocsPreDeleteOne() {
60
63
  const removedSubdocs = this.$__.removedSubdocs;
61
64
  if (!removedSubdocs?.length) {
62
65
  return;
@@ -68,10 +71,10 @@ async function saveSubdocsPreDeleteOne() {
68
71
  promises.push(subdoc._execDocumentPreHooks('deleteOne', options));
69
72
  }
70
73
 
71
- await Promise.all(promises);
74
+ return Promise.all(promises);
72
75
  }
73
76
 
74
- async function saveSubdocsPostDeleteOne() {
77
+ function saveSubdocsPostDeleteOne() {
75
78
  const removedSubdocs = this.$__.removedSubdocs;
76
79
  if (!removedSubdocs?.length) {
77
80
  return;
@@ -84,7 +87,7 @@ async function saveSubdocsPostDeleteOne() {
84
87
  }
85
88
 
86
89
  this.$__.removedSubdocs = null;
87
- await Promise.all(promises);
90
+ return Promise.all(promises);
88
91
  }
89
92
 
90
93