mongoose 9.0.2 → 9.1.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 (88) hide show
  1. package/lib/aggregate.js +1 -1
  2. package/lib/cast/string.js +1 -1
  3. package/lib/cast.js +7 -15
  4. package/lib/collection.js +2 -2
  5. package/lib/connection.js +20 -14
  6. package/lib/cursor/changeStream.js +5 -5
  7. package/lib/document.js +117 -77
  8. package/lib/drivers/node-mongodb-native/collection.js +5 -17
  9. package/lib/drivers/node-mongodb-native/connection.js +8 -23
  10. package/lib/error/cast.js +1 -1
  11. package/lib/helpers/aggregate/prepareDiscriminatorPipeline.js +4 -4
  12. package/lib/helpers/clone.js +8 -8
  13. package/lib/helpers/common.js +4 -4
  14. package/lib/helpers/cursor/eachAsync.js +1 -1
  15. package/lib/helpers/discriminator/getConstructor.js +1 -1
  16. package/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js +1 -1
  17. package/lib/helpers/discriminator/mergeDiscriminatorSchema.js +2 -2
  18. package/lib/helpers/document/applyDefaults.js +1 -1
  19. package/lib/helpers/document/applyTimestamps.js +2 -1
  20. package/lib/helpers/document/applyVirtuals.js +4 -3
  21. package/lib/helpers/document/cleanModifiedSubpaths.js +1 -1
  22. package/lib/helpers/document/compile.js +4 -4
  23. package/lib/helpers/document/getDeepestSubdocumentForPath.js +1 -1
  24. package/lib/helpers/indexes/decorateDiscriminatorIndexOptions.js +1 -1
  25. package/lib/helpers/indexes/getRelatedIndexes.js +3 -3
  26. package/lib/helpers/model/castBulkWrite.js +5 -9
  27. package/lib/helpers/model/discriminator.js +1 -1
  28. package/lib/helpers/populate/assignRawDocsToIdStructure.js +1 -1
  29. package/lib/helpers/populate/assignVals.js +4 -4
  30. package/lib/helpers/populate/getModelsMapForPopulate.js +25 -23
  31. package/lib/helpers/populate/getSchemaTypes.js +6 -7
  32. package/lib/helpers/printJestWarning.js +1 -1
  33. package/lib/helpers/processConnectionOptions.js +1 -1
  34. package/lib/helpers/query/castUpdate.js +12 -12
  35. package/lib/helpers/query/getEmbeddedDiscriminatorPath.js +2 -2
  36. package/lib/helpers/query/handleImmutable.js +2 -2
  37. package/lib/helpers/query/sanitizeFilter.js +1 -1
  38. package/lib/helpers/schema/applyPlugins.js +1 -1
  39. package/lib/helpers/schema/applyReadConcern.js +1 -1
  40. package/lib/helpers/schema/applyWriteConcern.js +4 -2
  41. package/lib/helpers/schema/getIndexes.js +3 -3
  42. package/lib/helpers/schema/getSubdocumentStrictValue.js +1 -1
  43. package/lib/helpers/schema/handleIdOption.js +1 -1
  44. package/lib/helpers/schema/idGetter.js +1 -1
  45. package/lib/helpers/schematype/handleImmutable.js +1 -1
  46. package/lib/helpers/setDefaultsOnInsert.js +2 -5
  47. package/lib/helpers/timestamps/setDocumentTimestamps.js +2 -2
  48. package/lib/helpers/timestamps/setupTimestamps.js +2 -2
  49. package/lib/helpers/update/applyTimestampsToUpdate.js +10 -9
  50. package/lib/helpers/update/castArrayFilters.js +4 -4
  51. package/lib/helpers/update/decorateUpdateWithVersionKey.js +1 -1
  52. package/lib/helpers/updateValidators.js +4 -4
  53. package/lib/model.js +33 -44
  54. package/lib/mongoose.js +5 -5
  55. package/lib/options/virtualOptions.js +1 -1
  56. package/lib/plugins/saveSubdocs.js +2 -2
  57. package/lib/plugins/trackTransaction.js +3 -4
  58. package/lib/query.js +62 -59
  59. package/lib/queryHelpers.js +9 -12
  60. package/lib/schema/array.js +10 -12
  61. package/lib/schema/buffer.js +6 -6
  62. package/lib/schema/documentArray.js +15 -23
  63. package/lib/schema/documentArrayElement.js +3 -3
  64. package/lib/schema/map.js +1 -1
  65. package/lib/schema/mixed.js +2 -2
  66. package/lib/schema/number.js +22 -4
  67. package/lib/schema/objectId.js +1 -1
  68. package/lib/schema/operators/exists.js +1 -1
  69. package/lib/schema/operators/geospatial.js +1 -1
  70. package/lib/schema/string.js +2 -2
  71. package/lib/schema/subdocument.js +9 -12
  72. package/lib/schema/union.js +1 -1
  73. package/lib/schema.js +27 -28
  74. package/lib/schemaType.js +11 -11
  75. package/lib/types/array/index.js +2 -2
  76. package/lib/types/array/methods/index.js +38 -8
  77. package/lib/types/arraySubdocument.js +12 -2
  78. package/lib/types/buffer.js +1 -1
  79. package/lib/types/documentArray/index.js +2 -2
  80. package/lib/types/documentArray/methods/index.js +5 -5
  81. package/lib/types/map.js +8 -8
  82. package/lib/types/subdocument.js +15 -5
  83. package/lib/utils.js +23 -7
  84. package/package.json +2 -2
  85. package/types/index.d.ts +15 -5
  86. package/types/middlewares.d.ts +11 -0
  87. package/types/models.d.ts +15 -5
  88. package/types/schemaoptions.d.ts +4 -2
@@ -26,7 +26,7 @@ const getConstructor = require('../helpers/discriminator/getConstructor');
26
26
  */
27
27
 
28
28
  function SchemaDocumentArrayElement(path, schema, options, parentSchema) {
29
- this.$parentSchemaType = options && options.$parentSchemaType;
29
+ this.$parentSchemaType = options?.$parentSchemaType;
30
30
  if (!this.$parentSchemaType) {
31
31
  throw new MongooseError('Cannot create DocumentArrayElement schematype without a parent');
32
32
  }
@@ -35,7 +35,7 @@ function SchemaDocumentArrayElement(path, schema, options, parentSchema) {
35
35
  SchemaType.call(this, path, options, 'DocumentArrayElement', parentSchema);
36
36
 
37
37
  this.$isMongooseDocumentArrayElement = true;
38
- this.Constructor = options && options.Constructor;
38
+ this.Constructor = options?.Constructor;
39
39
  this.schema = schema;
40
40
  }
41
41
 
@@ -76,7 +76,7 @@ SchemaDocumentArrayElement.prototype.doValidate = async function doValidate(valu
76
76
  const Constructor = getConstructor(this.Constructor, value);
77
77
 
78
78
  if (value && !(value instanceof Constructor)) {
79
- value = new Constructor(value, scope, null, null, options && options.index != null ? options.index : null);
79
+ value = new Constructor(value, scope, null, null, options?.index ?? null);
80
80
  }
81
81
 
82
82
  return SchemaSubdocument.prototype.doValidate.call(this, value, scope, options);
package/lib/schema/map.js CHANGED
@@ -172,7 +172,7 @@ SchemaMap.prototype._createNestedSchemaType = function _createNestedSchemaType(s
172
172
  let _mapType = { type: {} };
173
173
  if (utils.hasUserDefinedProperty(obj, 'of')) {
174
174
  const isInlineSchema = utils.isPOJO(obj.of) &&
175
- Object.keys(obj.of).length > 0 &&
175
+ utils.hasOwnKeys(obj.of) &&
176
176
  !utils.hasUserDefinedProperty(obj.of, schema.options.typeKey);
177
177
  if (isInlineSchema) {
178
178
  _mapType = { [schema.options.typeKey]: new Schema(obj.of) };
@@ -21,12 +21,12 @@ const utils = require('../utils');
21
21
  */
22
22
 
23
23
  function SchemaMixed(path, options, _schemaOptions, parentSchema) {
24
- if (options && options.default) {
24
+ if (options?.default) {
25
25
  const def = options.default;
26
26
  if (Array.isArray(def) && def.length === 0) {
27
27
  // make sure empty array defaults are handled
28
28
  options.default = Array;
29
- } else if (!options.shared && isObject(def) && Object.keys(def).length === 0) {
29
+ } else if (!options.shared && isObject(def) && utils.hasOwnKeys(def) === false) {
30
30
  // prevent odd "shared" objects between documents
31
31
  options.default = function() {
32
32
  return {};
@@ -221,7 +221,7 @@ SchemaNumber.prototype.min = function(value, message) {
221
221
  }, this);
222
222
  }
223
223
 
224
- if (value !== null && value !== undefined) {
224
+ if (value != null) {
225
225
  let msg = message || MongooseError.messages.Number.min;
226
226
  msg = msg.replace(/{MIN}/, value);
227
227
  this.validators.push({
@@ -275,7 +275,7 @@ SchemaNumber.prototype.max = function(value, message) {
275
275
  }, this);
276
276
  }
277
277
 
278
- if (value !== null && value !== undefined) {
278
+ if (value != null) {
279
279
  let msg = message || MongooseError.messages.Number.max;
280
280
  msg = msg.replace(/{MAX}/, value);
281
281
  this.validators.push({
@@ -335,7 +335,25 @@ SchemaNumber.prototype.enum = function(values, message) {
335
335
  }
336
336
 
337
337
  if (utils.isPOJO(values)) {
338
- values = Object.values(values);
338
+ // TypeScript numeric enums produce objects with reverse
339
+ // mappings, e.g. { 0: 'Zero', 1: 'One', Zero: 0, One: 1 }.
340
+ // Object.values on that will yield ['Zero','One',0,1].
341
+ // For Number schema enums we only want the numeric values,
342
+ // otherwise casting the name strings to Number will throw.
343
+ const keys = Object.keys(values).sort();
344
+ const objVals = Object.values(values).sort();
345
+ // If keys and values are equal and half of values are numbers,
346
+ // this is likely a TS enum with reverse mapping, so use only the numbers.
347
+ if (
348
+ keys.length === objVals.length &&
349
+ keys.every((k, i) => k === String(objVals[i])) &&
350
+ objVals.filter(v => typeof v === 'number').length === Math.floor(objVals.length / 2)
351
+ ) {
352
+ values = objVals.filter(v => typeof v === 'number');
353
+ } else {
354
+ // Avoid sorting the values to preserve user-specified order
355
+ values = Object.values(values);
356
+ }
339
357
  }
340
358
  message = message || MongooseError.messages.Number.enum;
341
359
  }
@@ -376,7 +394,7 @@ SchemaNumber.prototype.cast = function(value, doc, init, prev, options) {
376
394
  }
377
395
  }
378
396
 
379
- const val = value && typeof value._id !== 'undefined' ?
397
+ const val = value?._id !== undefined ?
380
398
  value._id : // documents
381
399
  value;
382
400
 
@@ -29,7 +29,7 @@ let Document;
29
29
 
30
30
  function SchemaObjectId(key, options, _schemaOptions, parentSchema) {
31
31
  const isKeyHexStr = typeof key === 'string' && key.length === 24 && /^[a-f0-9]+$/i.test(key);
32
- const suppressWarning = options && options.suppressWarning;
32
+ const suppressWarning = options?.suppressWarning;
33
33
  if ((isKeyHexStr || typeof key === 'undefined') && !suppressWarning) {
34
34
  utils.warn('mongoose: To create a new ObjectId please try ' +
35
35
  '`Mongoose.Types.ObjectId` instead of using ' +
@@ -7,6 +7,6 @@ const castBoolean = require('../../cast/boolean');
7
7
  */
8
8
 
9
9
  module.exports = function(val) {
10
- const path = this != null ? this.path : null;
10
+ const path = this?.path ?? null;
11
11
  return castBoolean(val, path);
12
12
  };
@@ -25,7 +25,7 @@ function cast$near(val) {
25
25
 
26
26
  _castMinMaxDistance(this, val);
27
27
 
28
- if (val && val.$geometry) {
28
+ if (val?.$geometry) {
29
29
  return cast$geometry(val, this);
30
30
  }
31
31
 
@@ -416,7 +416,7 @@ SchemaString.prototype.minlength = function(value, message) {
416
416
  }, this);
417
417
  }
418
418
 
419
- if (value !== null && value !== undefined) {
419
+ if (value != null) {
420
420
  let msg = message || MongooseError.messages.String.minlength;
421
421
  msg = msg.replace(/{MINLENGTH}/, value);
422
422
  this.validators.push({
@@ -472,7 +472,7 @@ SchemaString.prototype.maxlength = function(value, message) {
472
472
  }, this);
473
473
  }
474
474
 
475
- if (value !== null && value !== undefined) {
475
+ if (value != null) {
476
476
  let msg = message || MongooseError.messages.String.maxlength;
477
477
  msg = msg.replace(/{MAXLENGTH}/, value);
478
478
  this.validators.push({
@@ -41,8 +41,7 @@ function SchemaSubdocument(schema, path, options, parentSchema) {
41
41
  if (schema.options.timeseries) {
42
42
  throw new InvalidSchemaOptionError(path, 'timeseries');
43
43
  }
44
- const schemaTypeIdOption = SchemaSubdocument.defaultOptions &&
45
- SchemaSubdocument.defaultOptions._id;
44
+ const schemaTypeIdOption = SchemaSubdocument.defaultOptions?._id;
46
45
  if (schemaTypeIdOption != null) {
47
46
  options = options || {};
48
47
  options._id = schemaTypeIdOption;
@@ -87,7 +86,7 @@ function _createConstructor(schema, baseClass, options) {
87
86
 
88
87
  schema._preCompile();
89
88
 
90
- const proto = baseClass != null ? baseClass.prototype : SubdocumentType.prototype;
89
+ const proto = baseClass?.prototype ?? SubdocumentType.prototype;
91
90
  _embedded.prototype = Object.create(proto);
92
91
  _embedded.prototype.$__setSchema(schema);
93
92
  _embedded.prototype.constructor = _embedded;
@@ -177,7 +176,7 @@ Object.defineProperty(SchemaSubdocument.prototype, '$conditionalHandlers', {
177
176
  */
178
177
 
179
178
  SchemaSubdocument.prototype.cast = function(val, doc, init, priorVal, options) {
180
- if (val && val.$isSingleNested && val.parent === doc) {
179
+ if (val?.$isSingleNested && val.parent === doc) {
181
180
  return val;
182
181
  }
183
182
 
@@ -192,7 +191,7 @@ SchemaSubdocument.prototype.cast = function(val, doc, init, priorVal, options) {
192
191
  let subdoc;
193
192
 
194
193
  // Only pull relevant selected paths and pull out the base path
195
- const parentSelected = doc && doc.$__ && doc.$__.selected;
194
+ const parentSelected = doc?.$__?.selected;
196
195
  const path = this.path;
197
196
  const selected = parentSelected == null ? null : Object.keys(parentSelected).reduce((obj, key) => {
198
197
  if (key.startsWith(path + '.')) {
@@ -216,7 +215,7 @@ SchemaSubdocument.prototype.cast = function(val, doc, init, priorVal, options) {
216
215
  applyDefaults(subdoc, selected, exclude);
217
216
  } else {
218
217
  options = Object.assign({}, options, { priorDoc: priorVal });
219
- if (Object.keys(val).length === 0) {
218
+ if (utils.hasOwnKeys(val) === false) {
220
219
  return new Constructor({}, selected, doc, options);
221
220
  }
222
221
 
@@ -256,9 +255,7 @@ SchemaSubdocument.prototype.castForQuery = function($conditional, val, context,
256
255
  val = this._applySetters(val, context);
257
256
  }
258
257
 
259
- const overrideStrict = options != null && options.strict != null ?
260
- options.strict :
261
- void 0;
258
+ const overrideStrict = options?.strict ?? void 0;
262
259
 
263
260
  try {
264
261
  val = new Constructor(val, overrideStrict);
@@ -282,10 +279,10 @@ SchemaSubdocument.prototype.doValidate = async function doValidate(value, scope,
282
279
  const Constructor = getConstructor(this.Constructor, value);
283
280
 
284
281
  if (value && !(value instanceof Constructor)) {
285
- value = new Constructor(value, null, (scope != null && scope.$__ != null) ? scope : null);
282
+ value = new Constructor(value, null, scope?.$__ != null ? scope : null);
286
283
  }
287
284
 
288
- if (options && options.skipSchemaValidators) {
285
+ if (options?.skipSchemaValidators) {
289
286
  if (!value) {
290
287
  return;
291
288
  }
@@ -305,7 +302,7 @@ SchemaSubdocument.prototype.doValidate = async function doValidate(value, scope,
305
302
  */
306
303
 
307
304
  SchemaSubdocument.prototype.doValidateSync = function(value, scope, options) {
308
- if (!options || !options.skipSchemaValidators) {
305
+ if (!options?.skipSchemaValidators) {
309
306
  const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, value, scope);
310
307
  if (schemaTypeError) {
311
308
  return schemaTypeError;
@@ -24,7 +24,7 @@ class Union extends SchemaType {
24
24
  */
25
25
  constructor(key, options, schemaOptions, parentSchema) {
26
26
  super(key, options, 'Union', parentSchema);
27
- if (!options || !Array.isArray(options.of) || options.of.length === 0) {
27
+ if (!Array.isArray(options?.of) || options.of.length === 0) {
28
28
  throw new Error('Union schema type requires an array of types');
29
29
  }
30
30
  this.schemaTypes = options.of.map(obj => parentSchema.interpretAsType(key, obj, schemaOptions));
package/lib/schema.js CHANGED
@@ -78,7 +78,7 @@ const numberRE = /^\d+$/;
78
78
  * - [validateBeforeSave](https://mongoosejs.com/docs/guide.html#validateBeforeSave) - bool - defaults to `true`
79
79
  * - [validateModifiedOnly](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) - bool - defaults to `false`
80
80
  * - [versionKey](https://mongoosejs.com/docs/guide.html#versionKey): string or object - defaults to "__v"
81
- * - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool or string[] or { exclude: string[] } - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html). Set to string array to enable optimistic concurrency for only certain fields, or `{ exclude: string[] }` to define a list of fields to ignore for optimistic concurrency.
81
+ * - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool or string[] or { exclude: string[] } - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html) for all fields. Set to a string array to enable optimistic concurrency only for the specified fields; note that this **replaces** the default array versioning behavior. Set to `{ exclude: string[] }` to enable optimistic concurrency for all fields except the specified ones; this also replaces the default array versioning.
82
82
  * - [collation](https://mongoosejs.com/docs/guide.html#collation): object - defaults to null (which means use no collation)
83
83
  * - [timeseries](https://mongoosejs.com/docs/guide.html#timeseries): object - defaults to null (which means this schema's collection won't be a timeseries collection)
84
84
  * - [selectPopulatedPaths](https://mongoosejs.com/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true`
@@ -88,6 +88,7 @@ const numberRE = /^\d+$/;
88
88
  * - [virtuals](https://mongoosejs.com/docs/tutorials/virtuals.html#virtuals-via-schema-options): object - virtuals to define, alias for [`.virtual`](https://mongoosejs.com/docs/api/schema.html#Schema.prototype.virtual())
89
89
  * - [collectionOptions]: object with options passed to [`createCollection()`](https://www.mongodb.com/docs/manual/reference/method/db.createCollection/) when calling `Model.createCollection()` or `autoCreate` set to true.
90
90
  * - [encryptionType]: the encryption type for the schema. Valid options are `csfle` or `queryableEncryption`. See https://mongoosejs.com/docs/field-level-encryption.
91
+ * - [lean]: boolean - set to true to make all queries use [lean](https://mongoosejs.com/docs/tutorials/lean.html) by default. Defaults to false.
91
92
  *
92
93
  * #### Options for Nested Schemas:
93
94
  *
@@ -120,11 +121,11 @@ function Schema(obj, options) {
120
121
  this.callQueue = [];
121
122
  this._indexes = [];
122
123
  this._searchIndexes = [];
123
- this.methods = (options && options.methods) || {};
124
+ this.methods = options?.methods || {};
124
125
  this.methodOptions = {};
125
- this.statics = (options && options.statics) || {};
126
+ this.statics = options?.statics || {};
126
127
  this.tree = {};
127
- this.query = (options && options.query) || {};
128
+ this.query = options?.query || {};
128
129
  this.childSchemas = [];
129
130
  this.plugins = [];
130
131
  // For internal debugging. Do not use this to try to save a schema in MDB.
@@ -147,7 +148,7 @@ function Schema(obj, options) {
147
148
  }
148
149
 
149
150
  // build virtual paths
150
- if (options && options.virtuals) {
151
+ if (options?.virtuals) {
151
152
  const virtuals = options.virtuals;
152
153
  const pathNames = Object.keys(virtuals);
153
154
  for (const pathName of pathNames) {
@@ -165,7 +166,7 @@ function Schema(obj, options) {
165
166
  }
166
167
 
167
168
  // check if _id's value is a subdocument (gh-2276)
168
- const _idSubDoc = obj && obj._id && utils.isObject(obj._id);
169
+ const _idSubDoc = obj?._id && utils.isObject(obj._id);
169
170
 
170
171
  // ensure the documents get an auto _id unless disabled
171
172
  const auto_id = !this.paths['_id'] &&
@@ -602,7 +603,7 @@ Schema.prototype.omit = function(paths, options) {
602
603
 
603
604
  Schema.prototype.defaultOptions = function(options) {
604
605
  this._userProvidedOptions = options == null ? {} : clone(options);
605
- const baseOptions = this.base && this.base.options || {};
606
+ const baseOptions = this.base?.options || {};
606
607
  const strict = 'strict' in baseOptions ? baseOptions.strict : true;
607
608
  const strictQuery = 'strictQuery' in baseOptions ? baseOptions.strictQuery : false;
608
609
  const id = 'id' in baseOptions ? baseOptions.id : true;
@@ -722,9 +723,7 @@ Schema.prototype._defaultToObjectOptions = function(json) {
722
723
  return this._defaultToObjectOptionsMap[path];
723
724
  }
724
725
 
725
- const baseOptions = this.base &&
726
- this.base.options &&
727
- this.base.options[path] || {};
726
+ const baseOptions = this.base?.options?.[path] || {};
728
727
  const schemaOptions = this.options[path] || {};
729
728
  // merge base default options with Schema's set default options if available.
730
729
  // `clone` is necessary here because `utils.options` directly modifies the second input.
@@ -771,7 +770,7 @@ Schema.prototype.encryptionType = function encryptionType(encryptionType) {
771
770
  */
772
771
 
773
772
  Schema.prototype.add = function add(obj, prefix) {
774
- if (obj instanceof Schema || (obj != null && obj.instanceOfSchema)) {
773
+ if (obj instanceof Schema || obj?.instanceOfSchema) {
775
774
  merge(this, obj);
776
775
  return this;
777
776
  }
@@ -811,7 +810,7 @@ Schema.prototype.add = function add(obj, prefix) {
811
810
  let isMongooseTypeString = false;
812
811
  if (typeof val === 'string') {
813
812
  // Handle the case in which the type is specified as a string (eg. 'date', 'oid', ...)
814
- const MongooseTypes = this.base != null ? this.base.Schema.Types : Schema.Types;
813
+ const MongooseTypes = this.base?.Schema.Types ?? Schema.Types;
815
814
  const upperVal = val.charAt(0).toUpperCase() + val.substring(1);
816
815
  isMongooseTypeString = MongooseTypes[upperVal] != null;
817
816
  }
@@ -824,7 +823,7 @@ Schema.prototype.add = function add(obj, prefix) {
824
823
  `a valid type at path \`${key}\`. See ` +
825
824
  'https://bit.ly/mongoose-schematypes for a list of valid schema types.');
826
825
  }
827
- if (val instanceof VirtualType || (val.constructor && val.constructor.name || null) === 'VirtualType') {
826
+ if (val instanceof VirtualType || (val.constructor?.name ?? null) === 'VirtualType') {
828
827
  this.virtual(val);
829
828
  continue;
830
829
  }
@@ -841,13 +840,13 @@ Schema.prototype.add = function add(obj, prefix) {
841
840
  this.nested[prefix.substring(0, prefix.length - 1)] = true;
842
841
  }
843
842
  this.path(prefix + key, val);
844
- if (val[0] != null && !(val[0].instanceOfSchema) && utils.isPOJO(val[0].discriminators)) {
843
+ if (!val[0]?.instanceOfSchema && utils.isPOJO(val[0]?.discriminators)) {
845
844
  const schemaType = this.path(prefix + key);
846
845
  for (const key in val[0].discriminators) {
847
846
  schemaType.discriminator(key, val[0].discriminators[key]);
848
847
  }
849
848
  }
850
- } else if (Object.keys(val).length < 1) {
849
+ } else if (utils.hasOwnKeys(val) === false) {
851
850
  // Special-case: {} always interpreted as Mixed path so leaf at this node
852
851
  if (prefix) {
853
852
  this.nested[prefix.substring(0, prefix.length - 1)] = true;
@@ -862,7 +861,7 @@ Schema.prototype.add = function add(obj, prefix) {
862
861
  } else {
863
862
  // There IS a bona-fide type key that may also be a POJO
864
863
  const _typeDef = val[typeKey];
865
- if (isPOJO(_typeDef) && Object.keys(_typeDef).length > 0) {
864
+ if (isPOJO(_typeDef) && utils.hasOwnKeys(_typeDef)) {
866
865
  // If a POJO is the value of a type key, make it a subdocument
867
866
  if (prefix) {
868
867
  this.nested[prefix.substring(0, prefix.length - 1)] = true;
@@ -893,7 +892,7 @@ Schema.prototype.add = function add(obj, prefix) {
893
892
  this.nested[prefix.substring(0, prefix.length - 1)] = true;
894
893
  }
895
894
  this.path(prefix + key, val);
896
- if (val != null && !(val.instanceOfSchema) && utils.isPOJO(val.discriminators)) {
895
+ if (!val?.instanceOfSchema && utils.isPOJO(val?.discriminators)) {
897
896
  const schemaType = this.path(prefix + key);
898
897
  for (const key in val.discriminators) {
899
898
  schemaType.discriminator(key, val.discriminators[key]);
@@ -965,7 +964,7 @@ Schema.prototype._removeEncryptedField = function _removeEncryptedField(path) {
965
964
  * @returns {boolean}
966
965
  */
967
966
  Schema.prototype._hasEncryptedFields = function _hasEncryptedFields() {
968
- return Object.keys(this.encryptedFields).length > 0;
967
+ return utils.hasOwnKeys(this.encryptedFields);
969
968
  };
970
969
 
971
970
  /**
@@ -1585,8 +1584,8 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
1585
1584
 
1586
1585
  // If this schema has an associated Mongoose object, use the Mongoose object's
1587
1586
  // copy of SchemaTypes re: gh-7158 gh-6933
1588
- const MongooseTypes = this.base != null ? this.base.Schema.Types : Schema.Types;
1589
- const Types = this.base != null ? this.base.Types : require('./types');
1587
+ const MongooseTypes = this.base?.Schema.Types ?? Schema.Types;
1588
+ const Types = this.base?.Types ?? require('./types');
1590
1589
 
1591
1590
  if (!utils.isPOJO(obj) && !(obj instanceof SchemaTypeOptions)) {
1592
1591
  const constructorName = utils.getFunctionName(obj.constructor);
@@ -1626,7 +1625,7 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
1626
1625
  : type[0];
1627
1626
 
1628
1627
  // new Schema({ path: [new Schema({ ... })] })
1629
- if (cast && cast.instanceOfSchema) {
1628
+ if (cast?.instanceOfSchema) {
1630
1629
  if (!(cast instanceof Schema)) {
1631
1630
  if (this.options._isMerging) {
1632
1631
  cast = new Schema(cast);
@@ -1660,7 +1659,7 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
1660
1659
  }
1661
1660
  if (typeof cast !== 'undefined') {
1662
1661
  if (Array.isArray(cast) || cast.type === Array || cast.type == 'Array') {
1663
- if (cast && cast.type == 'Array') {
1662
+ if (cast?.type == 'Array') {
1664
1663
  cast.type = Array;
1665
1664
  }
1666
1665
  return new MongooseTypes.Array(path, this.interpretAsType(path, cast, options), obj, null, this);
@@ -1674,7 +1673,7 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
1674
1673
  if (typeof cast === 'string') {
1675
1674
  cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)];
1676
1675
  } else if (utils.isPOJO(castFromTypeKey)) {
1677
- if (Object.keys(castFromTypeKey).length) {
1676
+ if (utils.hasOwnKeys(castFromTypeKey)) {
1678
1677
  // The `minimize` and `typeKey` options propagate to child schemas
1679
1678
  // declared inline, like `{ arr: [{ val: { $type: String } }] }`.
1680
1679
  // See gh-3560
@@ -1747,7 +1746,7 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
1747
1746
  return new MongooseTypes.Array(path, cast || MongooseTypes.Mixed, obj, options, this);
1748
1747
  }
1749
1748
 
1750
- if (type && type.instanceOfSchema) {
1749
+ if (type?.instanceOfSchema) {
1751
1750
  return new MongooseTypes.Subdocument(type, path, obj, this);
1752
1751
  }
1753
1752
 
@@ -2008,7 +2007,7 @@ function getPositionalPathType(self, path, cleanPath) {
2008
2007
  continue;
2009
2008
  }
2010
2009
 
2011
- if (!(val && val.schema)) {
2010
+ if (!val?.schema) {
2012
2011
  val = undefined;
2013
2012
  break;
2014
2013
  }
@@ -2206,7 +2205,7 @@ Schema.prototype.plugin = function(fn, opts) {
2206
2205
  }
2207
2206
 
2208
2207
 
2209
- if (opts && opts.deduplicate) {
2208
+ if (opts?.deduplicate) {
2210
2209
  for (const plugin of this.plugins) {
2211
2210
  if (plugin.fn === fn) {
2212
2211
  return this;
@@ -2655,9 +2654,9 @@ Schema.prototype.virtual = function(name, options) {
2655
2654
  return mem[part];
2656
2655
  }, this.tree);
2657
2656
 
2658
- if (options && options.applyToArray && parts.length > 1) {
2657
+ if (options?.applyToArray && parts.length > 1) {
2659
2658
  const path = this.path(parts.slice(0, -1).join('.'));
2660
- if (path && path.$isMongooseArray) {
2659
+ if (path?.$isMongooseArray) {
2661
2660
  return path.virtual(parts[parts.length - 1], options);
2662
2661
  } else {
2663
2662
  throw new MongooseError(`Path "${path}" is not an array`);
package/lib/schemaType.js CHANGED
@@ -416,7 +416,7 @@ SchemaType.prototype.default = function(val) {
416
416
  return void 0;
417
417
  }
418
418
 
419
- if (val != null && val.instanceOfSchema) {
419
+ if (val?.instanceOfSchema) {
420
420
  throw new MongooseError('Cannot set default value of path `' + this.path +
421
421
  '` to a mongoose Schema instance.');
422
422
  }
@@ -1112,7 +1112,7 @@ SchemaType.prototype.required = function(required, message) {
1112
1112
  this.isRequired = true;
1113
1113
 
1114
1114
  this.requiredValidator = function(v) {
1115
- const cachedRequired = this && this.$__ && this.$__.cachedRequired;
1115
+ const cachedRequired = this?.$__?.cachedRequired;
1116
1116
 
1117
1117
  // no validation when this path wasn't selected in the query.
1118
1118
  if (cachedRequired != null && !this.$__isSelected(_this.path) && !this[documentIsModified](_this.path)) {
@@ -1209,12 +1209,12 @@ SchemaType.prototype.getDefault = function(scope, init, options) {
1209
1209
  ret = this.defaultValue;
1210
1210
  }
1211
1211
 
1212
- if (ret !== null && ret !== undefined) {
1213
- if (typeof ret === 'object' && (!this.options || !this.options.shared)) {
1212
+ if (ret != null) {
1213
+ if (typeof ret === 'object' && !this.options?.shared) {
1214
1214
  ret = clone(ret);
1215
1215
  }
1216
1216
 
1217
- if (options && options.skipCast) {
1217
+ if (options?.skipCast) {
1218
1218
  return this._applySetters(ret, scope);
1219
1219
  }
1220
1220
 
@@ -1359,7 +1359,7 @@ SchemaType.prototype.doValidate = async function doValidate(value, scope, option
1359
1359
  let ok;
1360
1360
 
1361
1361
  const validatorProperties = isSimpleValidator(v) ? Object.assign({}, v) : clone(v);
1362
- validatorProperties.path = options && options.path ? options.path : path;
1362
+ validatorProperties.path = options?.path || path;
1363
1363
  validatorProperties.fullPath = this.$fullPath;
1364
1364
  validatorProperties.value = value;
1365
1365
  if (typeof value === 'string') {
@@ -1399,7 +1399,7 @@ SchemaType.prototype.doValidate = async function doValidate(value, scope, option
1399
1399
  }
1400
1400
  }
1401
1401
 
1402
- if (ok != null && typeof ok.then === 'function') {
1402
+ if (typeof ok?.then === 'function') {
1403
1403
  promises.push(
1404
1404
  ok.then(
1405
1405
  function(ok) {
@@ -1485,7 +1485,7 @@ SchemaType.prototype.doValidateSync = function(value, scope, options) {
1485
1485
 
1486
1486
  const validator = v.validator;
1487
1487
  const validatorProperties = isSimpleValidator(v) ? Object.assign({}, v) : clone(v);
1488
- validatorProperties.path = options && options.path ? options.path : path;
1488
+ validatorProperties.path = options?.path || path;
1489
1489
  validatorProperties.fullPath = this.$fullPath;
1490
1490
  validatorProperties.value = value;
1491
1491
  if (typeof value === 'string') {
@@ -1524,7 +1524,7 @@ SchemaType.prototype.doValidateSync = function(value, scope, options) {
1524
1524
 
1525
1525
  // Skip any validators that return a promise, we can't handle those
1526
1526
  // synchronously
1527
- if (ok != null && typeof ok.then === 'function') {
1527
+ if (typeof ok?.then === 'function') {
1528
1528
  continue;
1529
1529
  }
1530
1530
  err = _validate(ok, validatorProperties);
@@ -1549,9 +1549,9 @@ SchemaType.prototype.doValidateSync = function(value, scope, options) {
1549
1549
 
1550
1550
  SchemaType._isRef = function(self, value, doc, init) {
1551
1551
  // fast path
1552
- let ref = init && self.options && (self.options.ref || self.options.refPath);
1552
+ let ref = init && (self.options?.ref || self.options?.refPath);
1553
1553
 
1554
- if (!ref && doc && doc.$__ != null) {
1554
+ if (!ref && doc?.$__ != null) {
1555
1555
  // checks for
1556
1556
  // - this populated with adhoc model and no ref was set in schema OR
1557
1557
  // - setting / pushing values after population
@@ -72,7 +72,7 @@ function MongooseArray(values, path, doc, schematype) {
72
72
  internals[arrayAtomicsSymbol] = values[arrayAtomicsSymbol];
73
73
  }
74
74
 
75
- if (doc != null && doc.$__) {
75
+ if (doc?.$__) {
76
76
  internals[arrayParentSymbol] = doc;
77
77
  internals[arraySchemaSymbol] = schematype || doc.schema.path(path);
78
78
  }
@@ -85,7 +85,7 @@ function MongooseArray(values, path, doc, schematype) {
85
85
  if (Object.hasOwn(mongooseArrayMethods, prop)) {
86
86
  return mongooseArrayMethods[prop];
87
87
  }
88
- if (schematype && schematype.virtuals && Object.hasOwn(schematype.virtuals, prop)) {
88
+ if (schematype?.virtuals && Object.hasOwn(schematype.virtuals, prop)) {
89
89
  return schematype.virtuals[prop].applyGetters(undefined, target);
90
90
  }
91
91
  if (typeof prop === 'string' && numberRE.test(prop) && schematype?.embeddedSchemaType != null) {
@@ -10,6 +10,7 @@ const mpath = require('mpath');
10
10
  const utils = require('../../../utils');
11
11
  const isBsonType = require('../../../helpers/isBsonType');
12
12
 
13
+ const arrayAtomicsBackupSymbol = require('../../../helpers/symbols').arrayAtomicsBackupSymbol;
13
14
  const arrayAtomicsSymbol = require('../../../helpers/symbols').arrayAtomicsSymbol;
14
15
  const arrayParentSymbol = require('../../../helpers/symbols').arrayParentSymbol;
15
16
  const arrayPathSymbol = require('../../../helpers/symbols').arrayPathSymbol;
@@ -59,9 +60,9 @@ const methods = {
59
60
  val = val.toObject(opts);
60
61
  } else if (Array.isArray(val)) {
61
62
  val = this.toObject.call(val, opts);
62
- } else if (val != null && Array.isArray(val.$each)) {
63
+ } else if (Array.isArray(val?.$each)) {
63
64
  val.$each = this.toObject.call(val.$each, opts);
64
- } else if (val != null && typeof val.valueOf === 'function') {
65
+ } else if (typeof val?.valueOf === 'function') {
65
66
  val = val.valueOf();
66
67
  }
67
68
 
@@ -75,6 +76,36 @@ const methods = {
75
76
  return ret;
76
77
  },
77
78
 
79
+ /**
80
+ * Public API for getting atomics. Alias for $__getAtomics() that can be
81
+ * implemented by custom container types.
82
+ *
83
+ * @return {Array}
84
+ * @method getAtomics
85
+ * @memberOf MongooseArray
86
+ * @instance
87
+ * @api public
88
+ */
89
+
90
+ getAtomics() {
91
+ return this.$__getAtomics();
92
+ },
93
+
94
+ /**
95
+ * Clears all pending atomic operations. Called by Mongoose after save().
96
+ *
97
+ * @return {void}
98
+ * @method clearAtomics
99
+ * @memberOf MongooseArray
100
+ * @instance
101
+ * @api public
102
+ */
103
+
104
+ clearAtomics() {
105
+ this[arrayAtomicsBackupSymbol] = this[arrayAtomicsSymbol];
106
+ this[arrayAtomicsSymbol] = {};
107
+ },
108
+
78
109
  /*!
79
110
  * ignore
80
111
  */
@@ -230,7 +261,7 @@ const methods = {
230
261
  populated = parent.$populated(this[arrayPathSymbol], true);
231
262
  }
232
263
 
233
- if (populated && value !== null && value !== undefined) {
264
+ if (populated && value != null) {
234
265
  // cast to the populated Models schema
235
266
  Model = populated.options[populateModelSymbol];
236
267
  if (Model == null) {
@@ -246,8 +277,7 @@ const methods = {
246
277
 
247
278
  // gh-2399
248
279
  // we should cast model only when it's not a discriminator
249
- const isDisc = value.schema && value.schema.discriminatorMapping &&
250
- value.schema.discriminatorMapping.key !== undefined;
280
+ const isDisc = value.schema?.discriminatorMapping?.key !== undefined;
251
281
  if (!isDisc) {
252
282
  value = new Model(value);
253
283
  }
@@ -338,7 +368,7 @@ const methods = {
338
368
 
339
369
  // check for impossible $atomic combos (Mongo denies more than one
340
370
  // $atomic op on a single path
341
- if (atomics.$set || Object.keys(atomics).length && !(op in atomics)) {
371
+ if (atomics.$set || utils.hasOwnKeys(atomics) && !(op in atomics)) {
342
372
  // a different op was previously registered.
343
373
  // save the entire thing.
344
374
  this[arrayAtomicsSymbol] = { $set: this };
@@ -895,7 +925,7 @@ const methods = {
895
925
 
896
926
  toObject(options) {
897
927
  const arr = utils.isMongooseArray(this) ? this.__array : this;
898
- if (options && options.depopulate) {
928
+ if (options?.depopulate) {
899
929
  options = clone(options);
900
930
  options._isNested = true;
901
931
  // Ensure return value is a vanilla array, because in Node.js 6+ `map()`
@@ -995,7 +1025,7 @@ function _minimizePath(obj, parts, i) {
995
1025
  }
996
1026
 
997
1027
  _minimizePath(obj[parts[0]], parts, i + 1);
998
- if (obj[parts[0]] != null && typeof obj[parts[0]] === 'object' && Object.keys(obj[parts[0]]).length === 0) {
1028
+ if (obj[parts[0]] != null && typeof obj[parts[0]] === 'object' && utils.hasOwnKeys(obj[parts[0]]) === false) {
999
1029
  delete obj[parts[0]];
1000
1030
  }
1001
1031
  }