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
package/lib/document.js CHANGED
@@ -149,7 +149,7 @@ function Document(obj, fields, options) {
149
149
 
150
150
  // determine if this doc is a result of a query with
151
151
  // excluded fields
152
- if (utils.isPOJO(fields) && Object.keys(fields).length > 0) {
152
+ if (utils.isPOJO(fields) && utils.hasOwnKeys(fields)) {
153
153
  exclude = isExclusive(fields);
154
154
  this.$__.selected = fields;
155
155
  this.$__.exclude = exclude;
@@ -702,7 +702,7 @@ Document.prototype.$__init = function(doc, opts) {
702
702
  continue;
703
703
  }
704
704
  for (const child of item._childDocs) {
705
- if (child == null || child.$__ == null) {
705
+ if (child?.$__ == null) {
706
706
  continue;
707
707
  }
708
708
  child.$__.parent = this;
@@ -801,7 +801,7 @@ function init(self, obj, doc, opts, prefix) {
801
801
 
802
802
  if (schemaType && !wasPopulated && !opts.hydratedPopulatedDocs) {
803
803
  try {
804
- if (opts && opts.setters) {
804
+ if (opts?.setters) {
805
805
  // Call applySetters with `init = false` because otherwise setters are a noop
806
806
  const overrideInit = false;
807
807
  doc[i] = schemaType.applySetters(value, self, overrideInit, null, opts);
@@ -820,7 +820,7 @@ function init(self, obj, doc, opts, prefix) {
820
820
  } else if (schemaType && opts.hydratedPopulatedDocs) {
821
821
  doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
822
822
 
823
- if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
823
+ if (doc[i]?.$__?.wasPopulated) {
824
824
  self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
825
825
  } else if (Array.isArray(doc[i]) && doc[i].length && doc[i][0]?.$__?.wasPopulated) {
826
826
  self.$populated(path, doc[i].map(populatedDoc => populatedDoc?.$__?.wasPopulated?.value).filter(val => val != null), doc[i][0].$__.wasPopulated.options);
@@ -849,7 +849,7 @@ function init(self, obj, doc, opts, prefix) {
849
849
  * - same as in [Model.updateOne](https://mongoosejs.com/docs/api/model.html#Model.updateOne)
850
850
  *
851
851
  * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne
852
- * @param {Object} doc
852
+ * @param {Object} update
853
853
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
854
854
  * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and the [Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
855
855
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
@@ -860,33 +860,33 @@ function init(self, obj, doc, opts, prefix) {
860
860
  * @instance
861
861
  */
862
862
 
863
- Document.prototype.updateOne = function updateOne(doc, options) {
864
- const query = this.constructor.updateOne({ _id: this._doc._id }, doc, options);
863
+ Document.prototype.updateOne = function updateOne(update, options) {
864
+ const query = this.constructor.updateOne();
865
865
  const self = this;
866
866
  query.pre(async function queryPreUpdateOne() {
867
- const res = await self._execDocumentPreHooks('updateOne', self);
867
+ const res = await self._execDocumentPreHooks('updateOne', self, update, options);
868
868
  // `self` is passed to pre hooks as argument for backwards compatibility, but that
869
869
  // isn't the actual arguments passed to the wrapped function.
870
- if (res?.length !== 1 || res[0] !== self) {
870
+ if (res[0] !== self || res[1] !== update || res[2] !== options) {
871
871
  throw new Error('Document updateOne pre hooks cannot overwrite arguments');
872
872
  }
873
+ query.updateOne({ _id: self._doc._id }, update, options);
873
874
  // Apply custom where conditions _after_ document updateOne middleware for
874
875
  // consistency with save() - sharding plugin needs to set $where
875
876
  if (self.$where != null) {
876
877
  this.where(self.$where);
877
878
  }
879
+ if (self.$session() != null) {
880
+ if (!('session' in query.options)) {
881
+ query.options.session = self.$session();
882
+ }
883
+ }
878
884
  return res;
879
885
  });
880
886
  query.post(function queryPostUpdateOne() {
881
887
  return self._execDocumentPostHooks('updateOne');
882
888
  });
883
889
 
884
- if (this.$session() != null) {
885
- if (!('session' in query.options)) {
886
- query.options.session = this.$session();
887
- }
888
- }
889
-
890
890
  return query;
891
891
  };
892
892
 
@@ -938,14 +938,14 @@ Document.prototype.replaceOne = function replaceOne() {
938
938
 
939
939
  Document.prototype.$session = function $session(session) {
940
940
  if (arguments.length === 0) {
941
- if (this.$__.session != null && this.$__.session.hasEnded) {
941
+ if (this.$__.session?.hasEnded) {
942
942
  this.$__.session = null;
943
943
  return null;
944
944
  }
945
945
  return this.$__.session;
946
946
  }
947
947
 
948
- if (session != null && session.hasEnded) {
948
+ if (session?.hasEnded) {
949
949
  throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' +
950
950
  'called `endSession()` on the session you are passing to `$session()`.');
951
951
  }
@@ -1022,17 +1022,21 @@ Document.prototype.$timestamps = function $timestamps(value) {
1022
1022
  */
1023
1023
 
1024
1024
  Document.prototype.overwrite = function overwrite(obj) {
1025
- const keys = Array.from(new Set(Object.keys(this._doc).concat(Object.keys(obj))));
1025
+ const keys = new Set(Object.keys(this._doc));
1026
+ for (const key of Object.keys(obj)) {
1027
+ keys.add(key);
1028
+ }
1026
1029
 
1030
+ const schemaOptions = this.$__schema.options;
1027
1031
  for (const key of keys) {
1028
1032
  if (key === '_id') {
1029
1033
  continue;
1030
1034
  }
1031
1035
  // Explicitly skip version key
1032
- if (this.$__schema.options.versionKey && key === this.$__schema.options.versionKey) {
1036
+ if (schemaOptions.versionKey && key === schemaOptions.versionKey) {
1033
1037
  continue;
1034
1038
  }
1035
- if (this.$__schema.options.discriminatorKey && key === this.$__schema.options.discriminatorKey) {
1039
+ if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) {
1036
1040
  continue;
1037
1041
  }
1038
1042
  this.$set(key, obj[key]);
@@ -1062,7 +1066,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1062
1066
  type = undefined;
1063
1067
  }
1064
1068
 
1065
- const merge = options && options.merge;
1069
+ const merge = options?.merge;
1066
1070
  const adhoc = type && type !== true;
1067
1071
  const constructing = type === true;
1068
1072
  let adhocs;
@@ -1108,7 +1112,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1108
1112
 
1109
1113
  // `_skipMinimizeTopLevel` is because we may have deleted the top-level
1110
1114
  // nested key to ensure key order.
1111
- const _skipMinimizeTopLevel = options && options._skipMinimizeTopLevel || false;
1115
+ const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false;
1112
1116
  if (len === 0 && _skipMinimizeTopLevel) {
1113
1117
  delete options._skipMinimizeTopLevel;
1114
1118
  if (val) {
@@ -1422,7 +1426,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1422
1426
 
1423
1427
  let didPopulate = false;
1424
1428
  if (refMatches && val instanceof Document && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) {
1425
- const unpopulatedValue = (schema && schema.$isSingleNested) ? schema.cast(val, this) : val._doc._id;
1429
+ const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id;
1426
1430
  this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor });
1427
1431
  val.$__.wasPopulated = { value: unpopulatedValue };
1428
1432
  didPopulate = true;
@@ -1454,7 +1458,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1454
1458
  if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) {
1455
1459
  setterContext = getDeepestSubdocumentForPath(this, parts, this.schema);
1456
1460
  }
1457
- if (options != null && options.overwriteImmutable) {
1461
+ if (options?.overwriteImmutable) {
1458
1462
  val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true });
1459
1463
  } else {
1460
1464
  val = schema.applySetters(val, setterContext, false, priorVal, { path });
@@ -1465,9 +1469,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1465
1469
  !Array.isArray(schema) &&
1466
1470
  schema.$isMongooseDocumentArray &&
1467
1471
  val.length !== 0 &&
1468
- val[0] != null &&
1469
- val[0].$__ != null &&
1470
- val[0].$__.populated != null) {
1472
+ val[0]?.$__?.populated != null) {
1471
1473
  const populatedPaths = Object.keys(val[0].$__.populated);
1472
1474
  for (const populatedPath of populatedPaths) {
1473
1475
  this.$populated(path + '.' + populatedPath,
@@ -1632,7 +1634,7 @@ Document.prototype.set = Document.prototype.$set;
1632
1634
  */
1633
1635
 
1634
1636
  Document.prototype.$__shouldModify = function(pathToMark, path, options, constructing, parts, schema, val, priorVal) {
1635
- if (options && options._skipMarkModified) {
1637
+ if (options?._skipMarkModified) {
1636
1638
  return false;
1637
1639
  }
1638
1640
  if (this.$isNew) {
@@ -1702,9 +1704,9 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
1702
1704
  schema, val, priorVal);
1703
1705
 
1704
1706
  if (shouldModify) {
1705
- if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[path]) {
1707
+ if (this.$__.primitiveAtomics?.[path]) {
1706
1708
  delete this.$__.primitiveAtomics[path];
1707
- if (Object.keys(this.$__.primitiveAtomics).length === 0) {
1709
+ if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) {
1708
1710
  delete this.$__.primitiveAtomics;
1709
1711
  }
1710
1712
  }
@@ -1946,7 +1948,7 @@ Document.prototype.get = function(path, type, options) {
1946
1948
  return obj;
1947
1949
  }
1948
1950
 
1949
- if (schema != null && schema.instance === 'Mixed') {
1951
+ if (schema?.instance === 'Mixed') {
1950
1952
  const virtual = this.$__schema.virtualpath(path);
1951
1953
  if (virtual != null) {
1952
1954
  schema = virtual;
@@ -1963,7 +1965,7 @@ Document.prototype.get = function(path, type, options) {
1963
1965
  }
1964
1966
 
1965
1967
  for (let i = 0, l = pieces.length; i < l; i++) {
1966
- if (obj && obj._doc) {
1968
+ if (obj?._doc) {
1967
1969
  obj = obj._doc;
1968
1970
  }
1969
1971
 
@@ -2291,7 +2293,7 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
2291
2293
 
2292
2294
  Document.prototype.isModified = function(paths, options, modifiedPaths) {
2293
2295
  if (paths) {
2294
- const ignoreAtomics = options && options.ignoreAtomics;
2296
+ const ignoreAtomics = options?.ignoreAtomics;
2295
2297
  const directModifiedPathsObj = this.$__.activePaths.states.modify;
2296
2298
  if (directModifiedPathsObj == null) {
2297
2299
  return false;
@@ -2316,7 +2318,7 @@ Document.prototype.isModified = function(paths, options, modifiedPaths) {
2316
2318
  if (ignoreAtomics) {
2317
2319
  directModifiedPaths = directModifiedPaths.filter(path => {
2318
2320
  const value = this.$__getValue(path);
2319
- if (value != null && value[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) {
2321
+ if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) {
2320
2322
  return false;
2321
2323
  }
2322
2324
  return true;
@@ -2438,7 +2440,7 @@ Document.prototype.isDirectModified = function(path) {
2438
2440
  for (let i = 0; i < pieces.length - 1; ++i) {
2439
2441
  const subpath = pieces.slice(0, i + 1).join('.');
2440
2442
  const subdoc = this.$get(subpath);
2441
- if (subdoc != null && subdoc.$__ != null && subdoc.isDirectModified(pieces.slice(i + 1).join('.'))) {
2443
+ if (subdoc?.$__ != null && subdoc.isDirectModified(pieces.slice(i + 1).join('.'))) {
2442
2444
  return true;
2443
2445
  }
2444
2446
  }
@@ -2656,7 +2658,7 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
2656
2658
  const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1;
2657
2659
  options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' ');
2658
2660
  }
2659
- const _skipParallelValidateCheck = options && options._skipParallelValidateCheck;
2661
+ const _skipParallelValidateCheck = options?._skipParallelValidateCheck;
2660
2662
 
2661
2663
  if (this.$isSubdocument != null) {
2662
2664
  // Skip parallel validate check for subdocuments
@@ -2697,7 +2699,7 @@ function _evaluateRequiredFunctions(doc) {
2697
2699
 
2698
2700
  const p = doc.$__schema.path(path);
2699
2701
 
2700
- if (p != null && typeof p.originalRequiredValue === 'function') {
2702
+ if (typeof p?.originalRequiredValue === 'function') {
2701
2703
  doc.$__.cachedRequired = doc.$__.cachedRequired || {};
2702
2704
  try {
2703
2705
  doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc, doc);
@@ -2757,7 +2759,7 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
2757
2759
  }
2758
2760
  } else if (schemaType.$isMongooseDocumentArray) {
2759
2761
  const arr = doc.$get(path);
2760
- if (arr && arr.length) {
2762
+ if (arr?.length) {
2761
2763
  for (const subdoc of arr) {
2762
2764
  if (subdoc) {
2763
2765
  topLevelSubdocs.push(subdoc);
@@ -2910,7 +2912,7 @@ function _addArrayPathsToValidate(doc, paths) {
2910
2912
  // it unless we have a case of #6364
2911
2913
  (!Array.isArray(_pathType) &&
2912
2914
  _pathType.$isMongooseDocumentArray &&
2913
- !(_pathType && _pathType.schemaOptions && _pathType.schemaOptions.required))) {
2915
+ !_pathType?.schemaOptions?.required)) {
2914
2916
  continue;
2915
2917
  }
2916
2918
 
@@ -2977,7 +2979,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
2977
2979
  (typeof options === 'object') &&
2978
2980
  ('validateModifiedOnly' in options);
2979
2981
 
2980
- const pathsToSkip = (options && options.pathsToSkip) || null;
2982
+ const pathsToSkip = options?.pathsToSkip || null;
2981
2983
 
2982
2984
  let shouldValidateModifiedOnly;
2983
2985
  if (hasValidateModifiedOnlyOption) {
@@ -2986,7 +2988,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
2986
2988
  shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly;
2987
2989
  }
2988
2990
 
2989
- const validateAllPaths = options && options.validateAllPaths;
2991
+ const validateAllPaths = options?.validateAllPaths;
2990
2992
  if (validateAllPaths) {
2991
2993
  if (pathsToSkip) {
2992
2994
  throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`');
@@ -3013,7 +3015,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3013
3015
  delete validationError.errors[errPath];
3014
3016
  }
3015
3017
  }
3016
- if (Object.keys(validationError.errors).length === 0) {
3018
+ if (utils.hasOwnKeys(validationError.errors) === false) {
3017
3019
  validationError = void 0;
3018
3020
  }
3019
3021
  }
@@ -3044,7 +3046,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3044
3046
  // the children as well
3045
3047
  for (const path of paths) {
3046
3048
  const schemaType = this.$__schema.path(path);
3047
- if (!schemaType || !schemaType.$isMongooseArray) {
3049
+ if (!schemaType?.$isMongooseArray) {
3048
3050
  continue;
3049
3051
  }
3050
3052
  const val = this.$__getValue(path);
@@ -3056,7 +3058,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3056
3058
  paths = [...paths];
3057
3059
  doValidateOptionsByPath = {};
3058
3060
  } else {
3059
- const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options && options._nestedValidate);
3061
+ const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate);
3060
3062
  paths = shouldValidateModifiedOnly ?
3061
3063
  pathDetails[0].filter((path) => this.$isModified(path)) :
3062
3064
  pathDetails[0];
@@ -3124,7 +3126,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3124
3126
  let pop;
3125
3127
  if ((pop = _this.$populated(path))) {
3126
3128
  val = pop;
3127
- } else if (val != null && val.$__ != null && val.$__.wasPopulated) {
3129
+ } else if (val?.$__?.wasPopulated) {
3128
3130
  // Array paths, like `somearray.1`, do not show up as populated with `$populated()`,
3129
3131
  // so in that case pull out the document's id
3130
3132
  val = val._doc._id;
@@ -3240,9 +3242,9 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
3240
3242
  shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly;
3241
3243
  }
3242
3244
 
3243
- let pathsToSkip = options && options.pathsToSkip;
3245
+ let pathsToSkip = options?.pathsToSkip;
3244
3246
 
3245
- const validateAllPaths = options && options.validateAllPaths;
3247
+ const validateAllPaths = options?.validateAllPaths;
3246
3248
  if (validateAllPaths) {
3247
3249
  if (pathsToSkip) {
3248
3250
  throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`');
@@ -3268,7 +3270,7 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
3268
3270
  // the children as well
3269
3271
  for (const path of paths) {
3270
3272
  const schemaType = this.$__schema.path(path);
3271
- if (!schemaType || !schemaType.$isMongooseArray) {
3273
+ if (!schemaType?.$isMongooseArray) {
3272
3274
  continue;
3273
3275
  }
3274
3276
  const val = this.$__getValue(path);
@@ -3409,12 +3411,12 @@ Document.prototype.invalidate = function(path, err, val, kind) {
3409
3411
  */
3410
3412
 
3411
3413
  Document.prototype.$markValid = function(path) {
3412
- if (!this.$__.validationError || !this.$__.validationError.errors[path]) {
3414
+ if (!this.$__.validationError?.errors[path]) {
3413
3415
  return;
3414
3416
  }
3415
3417
 
3416
3418
  delete this.$__.validationError.errors[path];
3417
- if (Object.keys(this.$__.validationError.errors).length === 0) {
3419
+ if (utils.hasOwnKeys(this.$__.validationError.errors) === false) {
3418
3420
  this.$__.validationError = null;
3419
3421
  }
3420
3422
  };
@@ -3434,7 +3436,7 @@ function _markValidSubpaths(doc, path) {
3434
3436
  delete doc.$__.validationError.errors[key];
3435
3437
  }
3436
3438
  }
3437
- if (Object.keys(doc.$__.validationError.errors).length === 0) {
3439
+ if (utils.hasOwnKeys(doc.$__.validationError.errors) === false) {
3438
3440
  doc.$__.validationError = null;
3439
3441
  }
3440
3442
  }
@@ -3509,7 +3511,7 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
3509
3511
  */
3510
3512
 
3511
3513
  Document.prototype.$isValid = function(path) {
3512
- if (this.$__.validationError == null || Object.keys(this.$__.validationError.errors).length === 0) {
3514
+ if (this.$__.validationError == null || utils.hasOwnKeys(this.$__.validationError.errors) === false) {
3513
3515
  return true;
3514
3516
  }
3515
3517
  if (path == null) {
@@ -3541,7 +3543,7 @@ Document.prototype.$__reset = function reset() {
3541
3543
 
3542
3544
  // Skip for subdocuments
3543
3545
  const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null;
3544
- if (subdocs && subdocs.length > 0) {
3546
+ if (subdocs?.length > 0) {
3545
3547
  for (const subdoc of subdocs) {
3546
3548
  subdoc.$__reset();
3547
3549
  }
@@ -3551,7 +3553,9 @@ Document.prototype.$__reset = function reset() {
3551
3553
  this.$__dirty().forEach(function(dirt) {
3552
3554
  const type = dirt.value;
3553
3555
 
3554
- if (type && type[arrayAtomicsSymbol]) {
3556
+ if (type && typeof type.clearAtomics === 'function') {
3557
+ type.clearAtomics();
3558
+ } else if (type && type[arrayAtomicsSymbol]) {
3555
3559
  type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol];
3556
3560
  type[arrayAtomicsSymbol] = {};
3557
3561
  }
@@ -3583,7 +3587,7 @@ Document.prototype.$__reset = function reset() {
3583
3587
  */
3584
3588
 
3585
3589
  Document.prototype.$__undoReset = function $__undoReset() {
3586
- if (this.$__.backup == null || this.$__.backup.activePaths == null) {
3590
+ if (this.$__.backup?.activePaths == null) {
3587
3591
  return;
3588
3592
  }
3589
3593
 
@@ -3596,7 +3600,7 @@ Document.prototype.$__undoReset = function $__undoReset() {
3596
3600
  for (const dirt of this.$__dirty()) {
3597
3601
  const type = dirt.value;
3598
3602
 
3599
- if (type && type[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) {
3603
+ if (type?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) {
3600
3604
  type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol];
3601
3605
  }
3602
3606
  }
@@ -3764,14 +3768,14 @@ Document.prototype.$getAllSubdocs = function(options) {
3764
3768
  }
3765
3769
  if (Array.isArray(val)) {
3766
3770
  for (const el of val) {
3767
- if (el != null && el.$__) {
3771
+ if (el?.$__) {
3768
3772
  newSubdocs.push(el);
3769
3773
  }
3770
3774
  }
3771
3775
  }
3772
3776
  if (val instanceof Map) {
3773
3777
  for (const el of val.values()) {
3774
- if (el != null && el.$__) {
3778
+ if (el?.$__) {
3775
3779
  newSubdocs.push(el);
3776
3780
  }
3777
3781
  }
@@ -3818,7 +3822,7 @@ Document.prototype.$__handleReject = function handleReject(err) {
3818
3822
  // emit on the Model if listening
3819
3823
  if (this.$listeners('error').length) {
3820
3824
  this.$emit('error', err);
3821
- } else if (this.constructor.listeners && this.constructor.listeners('error').length) {
3825
+ } else if (this.constructor.listeners?.('error').length) {
3822
3826
  this.constructor.emit('error', err);
3823
3827
  }
3824
3828
  };
@@ -3847,7 +3851,7 @@ Document.prototype.$toObject = function(options, json) {
3847
3851
  _minimize = options.minimize;
3848
3852
  } else if (this.$__schemaTypeOptions?.minimize != null) {
3849
3853
  _minimize = this.$__schemaTypeOptions.minimize;
3850
- } else if (defaultOptions != null && defaultOptions.minimize != null) {
3854
+ } else if (defaultOptions?.minimize != null) {
3851
3855
  _minimize = defaultOptions.minimize;
3852
3856
  } else {
3853
3857
  _minimize = this.$__schema.options.minimize;
@@ -4171,7 +4175,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
4171
4175
  let assignPath;
4172
4176
  let cur = self._doc;
4173
4177
  let v;
4174
- const aliases = typeof (toObjectOptions && toObjectOptions.aliases) === 'boolean'
4178
+ const aliases = typeof toObjectOptions?.aliases === 'boolean'
4175
4179
  ? toObjectOptions.aliases
4176
4180
  : true;
4177
4181
 
@@ -4179,7 +4183,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
4179
4183
  let virtualsToApply = null;
4180
4184
  if (Array.isArray(options.virtuals)) {
4181
4185
  virtualsToApply = new Set(options.virtuals);
4182
- } else if (options.virtuals && options.virtuals.pathsToSkip) {
4186
+ } else if (options.virtuals?.pathsToSkip) {
4183
4187
  virtualsToApply = new Set(paths);
4184
4188
  for (let i = 0; i < options.virtuals.pathsToSkip.length; i++) {
4185
4189
  if (virtualsToApply.has(options.virtuals.pathsToSkip[i])) {
@@ -4387,7 +4391,7 @@ function omitDeselectedFields(self, json) {
4387
4391
  selected = {};
4388
4392
  queryhelpers.applyPaths(selected, schema);
4389
4393
  }
4390
- if (selected == null || Object.keys(selected).length === 0) {
4394
+ if (selected == null || utils.hasOwnKeys(selected) === false) {
4391
4395
  return json;
4392
4396
  }
4393
4397
 
@@ -4469,6 +4473,20 @@ Document.prototype.parent = function() {
4469
4473
 
4470
4474
  Document.prototype.$parent = Document.prototype.parent;
4471
4475
 
4476
+ /**
4477
+ * Set the parent of this document.
4478
+ *
4479
+ * @param {Document} parent
4480
+ * @api private
4481
+ * @method $__setParent
4482
+ * @memberOf Document
4483
+ * @instance
4484
+ */
4485
+
4486
+ Document.prototype.$__setParent = function $__setParent(parent) {
4487
+ this.$__.parent = parent;
4488
+ };
4489
+
4472
4490
  /**
4473
4491
  * Helper for console.log
4474
4492
  *
@@ -4724,7 +4742,7 @@ Document.prototype.populated = function(path, val, options) {
4724
4742
  for (let i = 0; i < pieces.length - 1; ++i) {
4725
4743
  const subpath = pieces.slice(0, i + 1).join('.');
4726
4744
  const subdoc = this.$get(subpath);
4727
- if (subdoc != null && subdoc.$__ != null && this.$populated(subpath)) {
4745
+ if (subdoc?.$__ != null && this.$populated(subpath)) {
4728
4746
  const rest = pieces.slice(i + 1).join('.');
4729
4747
  subdoc.$populated(rest, val, options);
4730
4748
  // No need to continue because the above recursion should take care of
@@ -4815,7 +4833,7 @@ Document.prototype.depopulate = function(path) {
4815
4833
 
4816
4834
  let populatedIds;
4817
4835
  const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : [];
4818
- const populated = this.$__ && this.$__.populated || {};
4836
+ const populated = this.$__?.populated || {};
4819
4837
 
4820
4838
  if (arguments.length === 0) {
4821
4839
  // Depopulate all
@@ -4964,7 +4982,13 @@ Document.prototype.$__delta = function $__delta() {
4964
4982
  if (Array.isArray(optimisticConcurrency)) {
4965
4983
  const optCon = new Set(optimisticConcurrency);
4966
4984
  const modPaths = this.modifiedPaths();
4967
- if (modPaths.find(path => optCon.has(path))) {
4985
+ if (modPaths.some(path => optCon.has(path))) {
4986
+ this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
4987
+ }
4988
+ } else if (Array.isArray(optimisticConcurrency?.exclude)) {
4989
+ const excluded = new Set(optimisticConcurrency.exclude);
4990
+ const modPaths = this.modifiedPaths();
4991
+ if (modPaths.some(path => !excluded.has(path))) {
4968
4992
  this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
4969
4993
  }
4970
4994
  } else {
@@ -4984,7 +5008,7 @@ Document.prototype.$__delta = function $__delta() {
4984
5008
  where._id = this._doc._id;
4985
5009
  // If `_id` is an object, need to depopulate, but also need to be careful
4986
5010
  // because `_id` can technically be null (see gh-6406)
4987
- if ((where && where._id && where._id.$__ || null) != null) {
5011
+ if (where?._id?.$__ != null) {
4988
5012
  where._id = where._id.toObject({ transform: false, depopulate: true });
4989
5013
  }
4990
5014
  for (; d < len; ++d) {
@@ -5035,15 +5059,15 @@ Document.prototype.$__delta = function $__delta() {
5035
5059
  operand(this, where, delta, data, 1, '$unset');
5036
5060
  } else if (value === null) {
5037
5061
  operand(this, where, delta, data, null);
5038
- } else if (utils.isMongooseArray(value) && value.$path() && value[arrayAtomicsSymbol]) {
5039
- // arrays and other custom types (support plugins etc)
5062
+ } else if (typeof value.getAtomics === 'function') {
5063
+ // arrays and other custom container types
5040
5064
  handleAtomics(this, where, delta, data, value);
5041
5065
  } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) {
5042
5066
  // MongooseBuffer
5043
5067
  value = value.toObject();
5044
5068
  operand(this, where, delta, data, value);
5045
5069
  } else {
5046
- if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[data.path] != null) {
5070
+ if (this.$__.primitiveAtomics?.[data.path] != null) {
5047
5071
  const val = this.$__.primitiveAtomics[data.path];
5048
5072
  const op = firstKey(val);
5049
5073
  operand(this, where, delta, data, val[op], op);
@@ -5069,7 +5093,7 @@ Document.prototype.$__delta = function $__delta() {
5069
5093
  this.$__version(where, delta);
5070
5094
  }
5071
5095
 
5072
- if (Object.keys(delta).length === 0) {
5096
+ if (utils.hasOwnKeys(delta) === false) {
5073
5097
  return [where, null];
5074
5098
  }
5075
5099
 
@@ -5114,14 +5138,14 @@ function checkDivergentArray(doc, path, array) {
5114
5138
  // elements of the array and potentially would overwrite data.
5115
5139
  const check = pop.options.match ||
5116
5140
  pop.options.options && Object.hasOwn(pop.options.options, 'limit') || // 0 is not permitted
5117
- pop.options.options && pop.options.options.skip || // 0 is permitted
5141
+ pop.options.options?.skip || // 0 is permitted
5118
5142
  pop.options.select && // deselected _id?
5119
5143
  (pop.options.select._id === 0 ||
5120
5144
  /\s?-_id\s?/.test(pop.options.select));
5121
5145
 
5122
5146
  if (check) {
5123
5147
  const atomics = array[arrayAtomicsSymbol];
5124
- if (Object.keys(atomics).length === 0 || atomics.$set || atomics.$pop) {
5148
+ if (utils.hasOwnKeys(atomics) === false || atomics.$set || atomics.$pop) {
5125
5149
  return path;
5126
5150
  }
5127
5151
  }
@@ -5154,7 +5178,11 @@ function operand(self, where, delta, data, val, op) {
5154
5178
  // already marked for versioning?
5155
5179
  if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return;
5156
5180
 
5157
- if (self.$__schema.options.optimisticConcurrency) {
5181
+ if (
5182
+ self.$__schema.options.optimisticConcurrency === true ||
5183
+ Array.isArray(self.$__schema.options.optimisticConcurrency) ||
5184
+ Array.isArray(self.$__schema.options.optimisticConcurrency?.exclude)
5185
+ ) {
5158
5186
  return;
5159
5187
  }
5160
5188
 
@@ -5208,11 +5236,20 @@ function operand(self, where, delta, data, val, op) {
5208
5236
  */
5209
5237
 
5210
5238
  function handleAtomics(self, where, delta, data, value) {
5211
- if (delta.$set && delta.$set[data.path]) {
5239
+ if (delta.$set?.[data.path]) {
5212
5240
  // $set has precedence over other atomics
5213
5241
  return;
5214
5242
  }
5215
5243
 
5244
+ if (typeof value.getAtomics === 'function') {
5245
+ value.getAtomics().forEach(function(atomic) {
5246
+ const op = atomic[0];
5247
+ const val = atomic[1];
5248
+ operand(self, where, delta, data, val, op);
5249
+ });
5250
+ return;
5251
+ }
5252
+
5216
5253
  if (typeof value.$__getAtomics === 'function') {
5217
5254
  value.$__getAtomics().forEach(function(atomic) {
5218
5255
  const op = atomic[0];
@@ -5301,7 +5338,7 @@ Document.prototype.$clone = function() {
5301
5338
  const clonedDoc = new Model();
5302
5339
  clonedDoc.$isNew = this.$isNew;
5303
5340
  if (this._doc) {
5304
- clonedDoc._doc = clone(this._doc, { retainDocuments: true });
5341
+ clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc });
5305
5342
  }
5306
5343
  if (this.$__) {
5307
5344
  const Cache = this.$__.constructor;
@@ -5312,7 +5349,10 @@ Document.prototype.$clone = function() {
5312
5349
  }
5313
5350
  clonedCache[key] = clone(this.$__[key]);
5314
5351
  }
5315
- Object.assign(clonedCache.activePaths, clone({ ...this.$__.activePaths }));
5352
+ Object.assign(
5353
+ clonedCache.activePaths,
5354
+ clone({ ...this.$__.activePaths })
5355
+ );
5316
5356
  clonedDoc.$__ = clonedCache;
5317
5357
  }
5318
5358
  return clonedDoc;