mongoose 9.0.1 → 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 (93) 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 +126 -79
  8. package/lib/drivers/node-mongodb-native/collection.js +33 -126
  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 +42 -48
  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 +3 -2
  85. package/types/index.d.ts +26 -9
  86. package/types/inferrawdoctype.d.ts +9 -3
  87. package/types/inferschematype.d.ts +20 -27
  88. package/types/middlewares.d.ts +11 -0
  89. package/types/models.d.ts +15 -5
  90. package/types/query.d.ts +1 -1
  91. package/types/schemaoptions.d.ts +4 -2
  92. package/types/utility.d.ts +1 -1
  93. package/types/virtuals.d.ts +3 -3
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;
@@ -648,6 +648,10 @@ Document.prototype.init = function(doc, opts, fn) {
648
648
  opts = null;
649
649
  }
650
650
 
651
+ if (doc == null) {
652
+ throw new ObjectParameterError(doc, 'doc', 'init');
653
+ }
654
+
651
655
  this.$__init(doc, opts);
652
656
 
653
657
  if (fn) {
@@ -677,6 +681,9 @@ Document.prototype.$init = function() {
677
681
  */
678
682
 
679
683
  Document.prototype.$__init = function(doc, opts) {
684
+ if (doc == null) {
685
+ throw new ObjectParameterError(doc, 'doc', 'init');
686
+ }
680
687
  this.$isNew = false;
681
688
  opts = opts || {};
682
689
 
@@ -695,7 +702,7 @@ Document.prototype.$__init = function(doc, opts) {
695
702
  continue;
696
703
  }
697
704
  for (const child of item._childDocs) {
698
- if (child == null || child.$__ == null) {
705
+ if (child?.$__ == null) {
699
706
  continue;
700
707
  }
701
708
  child.$__.parent = this;
@@ -794,7 +801,7 @@ function init(self, obj, doc, opts, prefix) {
794
801
 
795
802
  if (schemaType && !wasPopulated && !opts.hydratedPopulatedDocs) {
796
803
  try {
797
- if (opts && opts.setters) {
804
+ if (opts?.setters) {
798
805
  // Call applySetters with `init = false` because otherwise setters are a noop
799
806
  const overrideInit = false;
800
807
  doc[i] = schemaType.applySetters(value, self, overrideInit, null, opts);
@@ -813,7 +820,7 @@ function init(self, obj, doc, opts, prefix) {
813
820
  } else if (schemaType && opts.hydratedPopulatedDocs) {
814
821
  doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
815
822
 
816
- if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
823
+ if (doc[i]?.$__?.wasPopulated) {
817
824
  self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
818
825
  } else if (Array.isArray(doc[i]) && doc[i].length && doc[i][0]?.$__?.wasPopulated) {
819
826
  self.$populated(path, doc[i].map(populatedDoc => populatedDoc?.$__?.wasPopulated?.value).filter(val => val != null), doc[i][0].$__.wasPopulated.options);
@@ -842,7 +849,7 @@ function init(self, obj, doc, opts, prefix) {
842
849
  * - same as in [Model.updateOne](https://mongoosejs.com/docs/api/model.html#Model.updateOne)
843
850
  *
844
851
  * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne
845
- * @param {Object} doc
852
+ * @param {Object} update
846
853
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
847
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).
848
855
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
@@ -853,33 +860,33 @@ function init(self, obj, doc, opts, prefix) {
853
860
  * @instance
854
861
  */
855
862
 
856
- Document.prototype.updateOne = function updateOne(doc, options) {
857
- 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();
858
865
  const self = this;
859
866
  query.pre(async function queryPreUpdateOne() {
860
- const res = await self._execDocumentPreHooks('updateOne', self);
867
+ const res = await self._execDocumentPreHooks('updateOne', self, update, options);
861
868
  // `self` is passed to pre hooks as argument for backwards compatibility, but that
862
869
  // isn't the actual arguments passed to the wrapped function.
863
- if (res?.length !== 1 || res[0] !== self) {
870
+ if (res[0] !== self || res[1] !== update || res[2] !== options) {
864
871
  throw new Error('Document updateOne pre hooks cannot overwrite arguments');
865
872
  }
873
+ query.updateOne({ _id: self._doc._id }, update, options);
866
874
  // Apply custom where conditions _after_ document updateOne middleware for
867
875
  // consistency with save() - sharding plugin needs to set $where
868
876
  if (self.$where != null) {
869
877
  this.where(self.$where);
870
878
  }
879
+ if (self.$session() != null) {
880
+ if (!('session' in query.options)) {
881
+ query.options.session = self.$session();
882
+ }
883
+ }
871
884
  return res;
872
885
  });
873
886
  query.post(function queryPostUpdateOne() {
874
887
  return self._execDocumentPostHooks('updateOne');
875
888
  });
876
889
 
877
- if (this.$session() != null) {
878
- if (!('session' in query.options)) {
879
- query.options.session = this.$session();
880
- }
881
- }
882
-
883
890
  return query;
884
891
  };
885
892
 
@@ -931,14 +938,14 @@ Document.prototype.replaceOne = function replaceOne() {
931
938
 
932
939
  Document.prototype.$session = function $session(session) {
933
940
  if (arguments.length === 0) {
934
- if (this.$__.session != null && this.$__.session.hasEnded) {
941
+ if (this.$__.session?.hasEnded) {
935
942
  this.$__.session = null;
936
943
  return null;
937
944
  }
938
945
  return this.$__.session;
939
946
  }
940
947
 
941
- if (session != null && session.hasEnded) {
948
+ if (session?.hasEnded) {
942
949
  throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' +
943
950
  'called `endSession()` on the session you are passing to `$session()`.');
944
951
  }
@@ -1015,17 +1022,21 @@ Document.prototype.$timestamps = function $timestamps(value) {
1015
1022
  */
1016
1023
 
1017
1024
  Document.prototype.overwrite = function overwrite(obj) {
1018
- 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
+ }
1019
1029
 
1030
+ const schemaOptions = this.$__schema.options;
1020
1031
  for (const key of keys) {
1021
1032
  if (key === '_id') {
1022
1033
  continue;
1023
1034
  }
1024
1035
  // Explicitly skip version key
1025
- if (this.$__schema.options.versionKey && key === this.$__schema.options.versionKey) {
1036
+ if (schemaOptions.versionKey && key === schemaOptions.versionKey) {
1026
1037
  continue;
1027
1038
  }
1028
- if (this.$__schema.options.discriminatorKey && key === this.$__schema.options.discriminatorKey) {
1039
+ if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) {
1029
1040
  continue;
1030
1041
  }
1031
1042
  this.$set(key, obj[key]);
@@ -1055,7 +1066,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1055
1066
  type = undefined;
1056
1067
  }
1057
1068
 
1058
- const merge = options && options.merge;
1069
+ const merge = options?.merge;
1059
1070
  const adhoc = type && type !== true;
1060
1071
  const constructing = type === true;
1061
1072
  let adhocs;
@@ -1101,7 +1112,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1101
1112
 
1102
1113
  // `_skipMinimizeTopLevel` is because we may have deleted the top-level
1103
1114
  // nested key to ensure key order.
1104
- const _skipMinimizeTopLevel = options && options._skipMinimizeTopLevel || false;
1115
+ const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false;
1105
1116
  if (len === 0 && _skipMinimizeTopLevel) {
1106
1117
  delete options._skipMinimizeTopLevel;
1107
1118
  if (val) {
@@ -1415,7 +1426,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1415
1426
 
1416
1427
  let didPopulate = false;
1417
1428
  if (refMatches && val instanceof Document && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) {
1418
- const unpopulatedValue = (schema && schema.$isSingleNested) ? schema.cast(val, this) : val._doc._id;
1429
+ const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id;
1419
1430
  this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor });
1420
1431
  val.$__.wasPopulated = { value: unpopulatedValue };
1421
1432
  didPopulate = true;
@@ -1447,7 +1458,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1447
1458
  if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) {
1448
1459
  setterContext = getDeepestSubdocumentForPath(this, parts, this.schema);
1449
1460
  }
1450
- if (options != null && options.overwriteImmutable) {
1461
+ if (options?.overwriteImmutable) {
1451
1462
  val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true });
1452
1463
  } else {
1453
1464
  val = schema.applySetters(val, setterContext, false, priorVal, { path });
@@ -1458,9 +1469,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1458
1469
  !Array.isArray(schema) &&
1459
1470
  schema.$isMongooseDocumentArray &&
1460
1471
  val.length !== 0 &&
1461
- val[0] != null &&
1462
- val[0].$__ != null &&
1463
- val[0].$__.populated != null) {
1472
+ val[0]?.$__?.populated != null) {
1464
1473
  const populatedPaths = Object.keys(val[0].$__.populated);
1465
1474
  for (const populatedPath of populatedPaths) {
1466
1475
  this.$populated(path + '.' + populatedPath,
@@ -1625,7 +1634,7 @@ Document.prototype.set = Document.prototype.$set;
1625
1634
  */
1626
1635
 
1627
1636
  Document.prototype.$__shouldModify = function(pathToMark, path, options, constructing, parts, schema, val, priorVal) {
1628
- if (options && options._skipMarkModified) {
1637
+ if (options?._skipMarkModified) {
1629
1638
  return false;
1630
1639
  }
1631
1640
  if (this.$isNew) {
@@ -1695,9 +1704,9 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
1695
1704
  schema, val, priorVal);
1696
1705
 
1697
1706
  if (shouldModify) {
1698
- if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[path]) {
1707
+ if (this.$__.primitiveAtomics?.[path]) {
1699
1708
  delete this.$__.primitiveAtomics[path];
1700
- if (Object.keys(this.$__.primitiveAtomics).length === 0) {
1709
+ if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) {
1701
1710
  delete this.$__.primitiveAtomics;
1702
1711
  }
1703
1712
  }
@@ -1939,7 +1948,7 @@ Document.prototype.get = function(path, type, options) {
1939
1948
  return obj;
1940
1949
  }
1941
1950
 
1942
- if (schema != null && schema.instance === 'Mixed') {
1951
+ if (schema?.instance === 'Mixed') {
1943
1952
  const virtual = this.$__schema.virtualpath(path);
1944
1953
  if (virtual != null) {
1945
1954
  schema = virtual;
@@ -1956,7 +1965,7 @@ Document.prototype.get = function(path, type, options) {
1956
1965
  }
1957
1966
 
1958
1967
  for (let i = 0, l = pieces.length; i < l; i++) {
1959
- if (obj && obj._doc) {
1968
+ if (obj?._doc) {
1960
1969
  obj = obj._doc;
1961
1970
  }
1962
1971
 
@@ -2284,7 +2293,7 @@ Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
2284
2293
 
2285
2294
  Document.prototype.isModified = function(paths, options, modifiedPaths) {
2286
2295
  if (paths) {
2287
- const ignoreAtomics = options && options.ignoreAtomics;
2296
+ const ignoreAtomics = options?.ignoreAtomics;
2288
2297
  const directModifiedPathsObj = this.$__.activePaths.states.modify;
2289
2298
  if (directModifiedPathsObj == null) {
2290
2299
  return false;
@@ -2309,7 +2318,7 @@ Document.prototype.isModified = function(paths, options, modifiedPaths) {
2309
2318
  if (ignoreAtomics) {
2310
2319
  directModifiedPaths = directModifiedPaths.filter(path => {
2311
2320
  const value = this.$__getValue(path);
2312
- if (value != null && value[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) {
2321
+ if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) {
2313
2322
  return false;
2314
2323
  }
2315
2324
  return true;
@@ -2431,7 +2440,7 @@ Document.prototype.isDirectModified = function(path) {
2431
2440
  for (let i = 0; i < pieces.length - 1; ++i) {
2432
2441
  const subpath = pieces.slice(0, i + 1).join('.');
2433
2442
  const subdoc = this.$get(subpath);
2434
- if (subdoc != null && subdoc.$__ != null && subdoc.isDirectModified(pieces.slice(i + 1).join('.'))) {
2443
+ if (subdoc?.$__ != null && subdoc.isDirectModified(pieces.slice(i + 1).join('.'))) {
2435
2444
  return true;
2436
2445
  }
2437
2446
  }
@@ -2649,7 +2658,7 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
2649
2658
  const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1;
2650
2659
  options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' ');
2651
2660
  }
2652
- const _skipParallelValidateCheck = options && options._skipParallelValidateCheck;
2661
+ const _skipParallelValidateCheck = options?._skipParallelValidateCheck;
2653
2662
 
2654
2663
  if (this.$isSubdocument != null) {
2655
2664
  // Skip parallel validate check for subdocuments
@@ -2690,7 +2699,7 @@ function _evaluateRequiredFunctions(doc) {
2690
2699
 
2691
2700
  const p = doc.$__schema.path(path);
2692
2701
 
2693
- if (p != null && typeof p.originalRequiredValue === 'function') {
2702
+ if (typeof p?.originalRequiredValue === 'function') {
2694
2703
  doc.$__.cachedRequired = doc.$__.cachedRequired || {};
2695
2704
  try {
2696
2705
  doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc, doc);
@@ -2750,7 +2759,7 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
2750
2759
  }
2751
2760
  } else if (schemaType.$isMongooseDocumentArray) {
2752
2761
  const arr = doc.$get(path);
2753
- if (arr && arr.length) {
2762
+ if (arr?.length) {
2754
2763
  for (const subdoc of arr) {
2755
2764
  if (subdoc) {
2756
2765
  topLevelSubdocs.push(subdoc);
@@ -2903,7 +2912,7 @@ function _addArrayPathsToValidate(doc, paths) {
2903
2912
  // it unless we have a case of #6364
2904
2913
  (!Array.isArray(_pathType) &&
2905
2914
  _pathType.$isMongooseDocumentArray &&
2906
- !(_pathType && _pathType.schemaOptions && _pathType.schemaOptions.required))) {
2915
+ !_pathType?.schemaOptions?.required)) {
2907
2916
  continue;
2908
2917
  }
2909
2918
 
@@ -2970,7 +2979,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
2970
2979
  (typeof options === 'object') &&
2971
2980
  ('validateModifiedOnly' in options);
2972
2981
 
2973
- const pathsToSkip = (options && options.pathsToSkip) || null;
2982
+ const pathsToSkip = options?.pathsToSkip || null;
2974
2983
 
2975
2984
  let shouldValidateModifiedOnly;
2976
2985
  if (hasValidateModifiedOnlyOption) {
@@ -2979,7 +2988,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
2979
2988
  shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly;
2980
2989
  }
2981
2990
 
2982
- const validateAllPaths = options && options.validateAllPaths;
2991
+ const validateAllPaths = options?.validateAllPaths;
2983
2992
  if (validateAllPaths) {
2984
2993
  if (pathsToSkip) {
2985
2994
  throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`');
@@ -3006,7 +3015,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3006
3015
  delete validationError.errors[errPath];
3007
3016
  }
3008
3017
  }
3009
- if (Object.keys(validationError.errors).length === 0) {
3018
+ if (utils.hasOwnKeys(validationError.errors) === false) {
3010
3019
  validationError = void 0;
3011
3020
  }
3012
3021
  }
@@ -3037,7 +3046,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3037
3046
  // the children as well
3038
3047
  for (const path of paths) {
3039
3048
  const schemaType = this.$__schema.path(path);
3040
- if (!schemaType || !schemaType.$isMongooseArray) {
3049
+ if (!schemaType?.$isMongooseArray) {
3041
3050
  continue;
3042
3051
  }
3043
3052
  const val = this.$__getValue(path);
@@ -3049,7 +3058,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3049
3058
  paths = [...paths];
3050
3059
  doValidateOptionsByPath = {};
3051
3060
  } else {
3052
- const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options && options._nestedValidate);
3061
+ const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate);
3053
3062
  paths = shouldValidateModifiedOnly ?
3054
3063
  pathDetails[0].filter((path) => this.$isModified(path)) :
3055
3064
  pathDetails[0];
@@ -3117,7 +3126,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
3117
3126
  let pop;
3118
3127
  if ((pop = _this.$populated(path))) {
3119
3128
  val = pop;
3120
- } else if (val != null && val.$__ != null && val.$__.wasPopulated) {
3129
+ } else if (val?.$__?.wasPopulated) {
3121
3130
  // Array paths, like `somearray.1`, do not show up as populated with `$populated()`,
3122
3131
  // so in that case pull out the document's id
3123
3132
  val = val._doc._id;
@@ -3233,9 +3242,9 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
3233
3242
  shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly;
3234
3243
  }
3235
3244
 
3236
- let pathsToSkip = options && options.pathsToSkip;
3245
+ let pathsToSkip = options?.pathsToSkip;
3237
3246
 
3238
- const validateAllPaths = options && options.validateAllPaths;
3247
+ const validateAllPaths = options?.validateAllPaths;
3239
3248
  if (validateAllPaths) {
3240
3249
  if (pathsToSkip) {
3241
3250
  throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`');
@@ -3261,7 +3270,7 @@ Document.prototype.validateSync = function(pathsToValidate, options) {
3261
3270
  // the children as well
3262
3271
  for (const path of paths) {
3263
3272
  const schemaType = this.$__schema.path(path);
3264
- if (!schemaType || !schemaType.$isMongooseArray) {
3273
+ if (!schemaType?.$isMongooseArray) {
3265
3274
  continue;
3266
3275
  }
3267
3276
  const val = this.$__getValue(path);
@@ -3402,12 +3411,12 @@ Document.prototype.invalidate = function(path, err, val, kind) {
3402
3411
  */
3403
3412
 
3404
3413
  Document.prototype.$markValid = function(path) {
3405
- if (!this.$__.validationError || !this.$__.validationError.errors[path]) {
3414
+ if (!this.$__.validationError?.errors[path]) {
3406
3415
  return;
3407
3416
  }
3408
3417
 
3409
3418
  delete this.$__.validationError.errors[path];
3410
- if (Object.keys(this.$__.validationError.errors).length === 0) {
3419
+ if (utils.hasOwnKeys(this.$__.validationError.errors) === false) {
3411
3420
  this.$__.validationError = null;
3412
3421
  }
3413
3422
  };
@@ -3427,7 +3436,7 @@ function _markValidSubpaths(doc, path) {
3427
3436
  delete doc.$__.validationError.errors[key];
3428
3437
  }
3429
3438
  }
3430
- if (Object.keys(doc.$__.validationError.errors).length === 0) {
3439
+ if (utils.hasOwnKeys(doc.$__.validationError.errors) === false) {
3431
3440
  doc.$__.validationError = null;
3432
3441
  }
3433
3442
  }
@@ -3502,7 +3511,7 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
3502
3511
  */
3503
3512
 
3504
3513
  Document.prototype.$isValid = function(path) {
3505
- if (this.$__.validationError == null || Object.keys(this.$__.validationError.errors).length === 0) {
3514
+ if (this.$__.validationError == null || utils.hasOwnKeys(this.$__.validationError.errors) === false) {
3506
3515
  return true;
3507
3516
  }
3508
3517
  if (path == null) {
@@ -3534,7 +3543,7 @@ Document.prototype.$__reset = function reset() {
3534
3543
 
3535
3544
  // Skip for subdocuments
3536
3545
  const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null;
3537
- if (subdocs && subdocs.length > 0) {
3546
+ if (subdocs?.length > 0) {
3538
3547
  for (const subdoc of subdocs) {
3539
3548
  subdoc.$__reset();
3540
3549
  }
@@ -3544,7 +3553,9 @@ Document.prototype.$__reset = function reset() {
3544
3553
  this.$__dirty().forEach(function(dirt) {
3545
3554
  const type = dirt.value;
3546
3555
 
3547
- if (type && type[arrayAtomicsSymbol]) {
3556
+ if (type && typeof type.clearAtomics === 'function') {
3557
+ type.clearAtomics();
3558
+ } else if (type && type[arrayAtomicsSymbol]) {
3548
3559
  type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol];
3549
3560
  type[arrayAtomicsSymbol] = {};
3550
3561
  }
@@ -3576,7 +3587,7 @@ Document.prototype.$__reset = function reset() {
3576
3587
  */
3577
3588
 
3578
3589
  Document.prototype.$__undoReset = function $__undoReset() {
3579
- if (this.$__.backup == null || this.$__.backup.activePaths == null) {
3590
+ if (this.$__.backup?.activePaths == null) {
3580
3591
  return;
3581
3592
  }
3582
3593
 
@@ -3589,7 +3600,7 @@ Document.prototype.$__undoReset = function $__undoReset() {
3589
3600
  for (const dirt of this.$__dirty()) {
3590
3601
  const type = dirt.value;
3591
3602
 
3592
- if (type && type[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) {
3603
+ if (type?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) {
3593
3604
  type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol];
3594
3605
  }
3595
3606
  }
@@ -3757,14 +3768,14 @@ Document.prototype.$getAllSubdocs = function(options) {
3757
3768
  }
3758
3769
  if (Array.isArray(val)) {
3759
3770
  for (const el of val) {
3760
- if (el != null && el.$__) {
3771
+ if (el?.$__) {
3761
3772
  newSubdocs.push(el);
3762
3773
  }
3763
3774
  }
3764
3775
  }
3765
3776
  if (val instanceof Map) {
3766
3777
  for (const el of val.values()) {
3767
- if (el != null && el.$__) {
3778
+ if (el?.$__) {
3768
3779
  newSubdocs.push(el);
3769
3780
  }
3770
3781
  }
@@ -3811,7 +3822,7 @@ Document.prototype.$__handleReject = function handleReject(err) {
3811
3822
  // emit on the Model if listening
3812
3823
  if (this.$listeners('error').length) {
3813
3824
  this.$emit('error', err);
3814
- } else if (this.constructor.listeners && this.constructor.listeners('error').length) {
3825
+ } else if (this.constructor.listeners?.('error').length) {
3815
3826
  this.constructor.emit('error', err);
3816
3827
  }
3817
3828
  };
@@ -3840,7 +3851,7 @@ Document.prototype.$toObject = function(options, json) {
3840
3851
  _minimize = options.minimize;
3841
3852
  } else if (this.$__schemaTypeOptions?.minimize != null) {
3842
3853
  _minimize = this.$__schemaTypeOptions.minimize;
3843
- } else if (defaultOptions != null && defaultOptions.minimize != null) {
3854
+ } else if (defaultOptions?.minimize != null) {
3844
3855
  _minimize = defaultOptions.minimize;
3845
3856
  } else {
3846
3857
  _minimize = this.$__schema.options.minimize;
@@ -4164,7 +4175,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
4164
4175
  let assignPath;
4165
4176
  let cur = self._doc;
4166
4177
  let v;
4167
- const aliases = typeof (toObjectOptions && toObjectOptions.aliases) === 'boolean'
4178
+ const aliases = typeof toObjectOptions?.aliases === 'boolean'
4168
4179
  ? toObjectOptions.aliases
4169
4180
  : true;
4170
4181
 
@@ -4172,7 +4183,7 @@ function applyVirtuals(self, json, options, toObjectOptions) {
4172
4183
  let virtualsToApply = null;
4173
4184
  if (Array.isArray(options.virtuals)) {
4174
4185
  virtualsToApply = new Set(options.virtuals);
4175
- } else if (options.virtuals && options.virtuals.pathsToSkip) {
4186
+ } else if (options.virtuals?.pathsToSkip) {
4176
4187
  virtualsToApply = new Set(paths);
4177
4188
  for (let i = 0; i < options.virtuals.pathsToSkip.length; i++) {
4178
4189
  if (virtualsToApply.has(options.virtuals.pathsToSkip[i])) {
@@ -4380,7 +4391,7 @@ function omitDeselectedFields(self, json) {
4380
4391
  selected = {};
4381
4392
  queryhelpers.applyPaths(selected, schema);
4382
4393
  }
4383
- if (selected == null || Object.keys(selected).length === 0) {
4394
+ if (selected == null || utils.hasOwnKeys(selected) === false) {
4384
4395
  return json;
4385
4396
  }
4386
4397
 
@@ -4462,6 +4473,20 @@ Document.prototype.parent = function() {
4462
4473
 
4463
4474
  Document.prototype.$parent = Document.prototype.parent;
4464
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
+
4465
4490
  /**
4466
4491
  * Helper for console.log
4467
4492
  *
@@ -4717,7 +4742,7 @@ Document.prototype.populated = function(path, val, options) {
4717
4742
  for (let i = 0; i < pieces.length - 1; ++i) {
4718
4743
  const subpath = pieces.slice(0, i + 1).join('.');
4719
4744
  const subdoc = this.$get(subpath);
4720
- if (subdoc != null && subdoc.$__ != null && this.$populated(subpath)) {
4745
+ if (subdoc?.$__ != null && this.$populated(subpath)) {
4721
4746
  const rest = pieces.slice(i + 1).join('.');
4722
4747
  subdoc.$populated(rest, val, options);
4723
4748
  // No need to continue because the above recursion should take care of
@@ -4808,7 +4833,7 @@ Document.prototype.depopulate = function(path) {
4808
4833
 
4809
4834
  let populatedIds;
4810
4835
  const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : [];
4811
- const populated = this.$__ && this.$__.populated || {};
4836
+ const populated = this.$__?.populated || {};
4812
4837
 
4813
4838
  if (arguments.length === 0) {
4814
4839
  // Depopulate all
@@ -4957,7 +4982,13 @@ Document.prototype.$__delta = function $__delta() {
4957
4982
  if (Array.isArray(optimisticConcurrency)) {
4958
4983
  const optCon = new Set(optimisticConcurrency);
4959
4984
  const modPaths = this.modifiedPaths();
4960
- 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))) {
4961
4992
  this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
4962
4993
  }
4963
4994
  } else {
@@ -4977,7 +5008,7 @@ Document.prototype.$__delta = function $__delta() {
4977
5008
  where._id = this._doc._id;
4978
5009
  // If `_id` is an object, need to depopulate, but also need to be careful
4979
5010
  // because `_id` can technically be null (see gh-6406)
4980
- if ((where && where._id && where._id.$__ || null) != null) {
5011
+ if (where?._id?.$__ != null) {
4981
5012
  where._id = where._id.toObject({ transform: false, depopulate: true });
4982
5013
  }
4983
5014
  for (; d < len; ++d) {
@@ -5028,15 +5059,15 @@ Document.prototype.$__delta = function $__delta() {
5028
5059
  operand(this, where, delta, data, 1, '$unset');
5029
5060
  } else if (value === null) {
5030
5061
  operand(this, where, delta, data, null);
5031
- } else if (utils.isMongooseArray(value) && value.$path() && value[arrayAtomicsSymbol]) {
5032
- // arrays and other custom types (support plugins etc)
5062
+ } else if (typeof value.getAtomics === 'function') {
5063
+ // arrays and other custom container types
5033
5064
  handleAtomics(this, where, delta, data, value);
5034
5065
  } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) {
5035
5066
  // MongooseBuffer
5036
5067
  value = value.toObject();
5037
5068
  operand(this, where, delta, data, value);
5038
5069
  } else {
5039
- if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[data.path] != null) {
5070
+ if (this.$__.primitiveAtomics?.[data.path] != null) {
5040
5071
  const val = this.$__.primitiveAtomics[data.path];
5041
5072
  const op = firstKey(val);
5042
5073
  operand(this, where, delta, data, val[op], op);
@@ -5062,7 +5093,7 @@ Document.prototype.$__delta = function $__delta() {
5062
5093
  this.$__version(where, delta);
5063
5094
  }
5064
5095
 
5065
- if (Object.keys(delta).length === 0) {
5096
+ if (utils.hasOwnKeys(delta) === false) {
5066
5097
  return [where, null];
5067
5098
  }
5068
5099
 
@@ -5107,14 +5138,14 @@ function checkDivergentArray(doc, path, array) {
5107
5138
  // elements of the array and potentially would overwrite data.
5108
5139
  const check = pop.options.match ||
5109
5140
  pop.options.options && Object.hasOwn(pop.options.options, 'limit') || // 0 is not permitted
5110
- pop.options.options && pop.options.options.skip || // 0 is permitted
5141
+ pop.options.options?.skip || // 0 is permitted
5111
5142
  pop.options.select && // deselected _id?
5112
5143
  (pop.options.select._id === 0 ||
5113
5144
  /\s?-_id\s?/.test(pop.options.select));
5114
5145
 
5115
5146
  if (check) {
5116
5147
  const atomics = array[arrayAtomicsSymbol];
5117
- if (Object.keys(atomics).length === 0 || atomics.$set || atomics.$pop) {
5148
+ if (utils.hasOwnKeys(atomics) === false || atomics.$set || atomics.$pop) {
5118
5149
  return path;
5119
5150
  }
5120
5151
  }
@@ -5147,7 +5178,11 @@ function operand(self, where, delta, data, val, op) {
5147
5178
  // already marked for versioning?
5148
5179
  if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return;
5149
5180
 
5150
- 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
+ ) {
5151
5186
  return;
5152
5187
  }
5153
5188
 
@@ -5174,7 +5209,7 @@ function operand(self, where, delta, data, val, op) {
5174
5209
  if (/\.\d+\.|\.\d+$/.test(data.path)) {
5175
5210
  self.$__.version = VERSION_ALL;
5176
5211
  } else {
5177
- self.$__.version = VERSION_INC;
5212
+ self.$__.version |= VERSION_INC;
5178
5213
  }
5179
5214
  } else if (/^\$p/.test(op)) {
5180
5215
  // potentially changing array positions
@@ -5185,7 +5220,7 @@ function operand(self, where, delta, data, val, op) {
5185
5220
  } else if (/\.\d+\.|\.\d+$/.test(data.path)) {
5186
5221
  // now handling $set, $unset
5187
5222
  // subpath of array
5188
- self.$__.version = VERSION_WHERE;
5223
+ self.$__.version |= VERSION_WHERE;
5189
5224
  }
5190
5225
  }
5191
5226
 
@@ -5201,11 +5236,20 @@ function operand(self, where, delta, data, val, op) {
5201
5236
  */
5202
5237
 
5203
5238
  function handleAtomics(self, where, delta, data, value) {
5204
- if (delta.$set && delta.$set[data.path]) {
5239
+ if (delta.$set?.[data.path]) {
5205
5240
  // $set has precedence over other atomics
5206
5241
  return;
5207
5242
  }
5208
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
+
5209
5253
  if (typeof value.$__getAtomics === 'function') {
5210
5254
  value.$__getAtomics().forEach(function(atomic) {
5211
5255
  const op = atomic[0];
@@ -5294,7 +5338,7 @@ Document.prototype.$clone = function() {
5294
5338
  const clonedDoc = new Model();
5295
5339
  clonedDoc.$isNew = this.$isNew;
5296
5340
  if (this._doc) {
5297
- clonedDoc._doc = clone(this._doc, { retainDocuments: true });
5341
+ clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc });
5298
5342
  }
5299
5343
  if (this.$__) {
5300
5344
  const Cache = this.$__.constructor;
@@ -5305,7 +5349,10 @@ Document.prototype.$clone = function() {
5305
5349
  }
5306
5350
  clonedCache[key] = clone(this.$__[key]);
5307
5351
  }
5308
- Object.assign(clonedCache.activePaths, clone({ ...this.$__.activePaths }));
5352
+ Object.assign(
5353
+ clonedCache.activePaths,
5354
+ clone({ ...this.$__.activePaths })
5355
+ );
5309
5356
  clonedDoc.$__ = clonedCache;
5310
5357
  }
5311
5358
  return clonedDoc;