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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/mongoose
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [mongoose](https://www.npmjs.com/package/mongoose) @ 9.8.0 |
17
- | Processed | 2026-07-21 |
16
+ | Original | [mongoose](https://www.npmjs.com/package/mongoose) @ 9.9.0 |
17
+ | Processed | 2026-07-30 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 0 |
20
20
 
package/changes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "bumped": {},
3
- "timestamp": "2026-07-21T16:17:39.127Z",
3
+ "timestamp": "2026-07-30T16:33:49.673Z",
4
4
  "totalUpdated": 0
5
5
  }
package/lib/connection.js CHANGED
@@ -779,16 +779,8 @@ function _resetSessionDocuments(session) {
779
779
  doc.set(doc.schema.options.versionKey, state.versionKey);
780
780
  }
781
781
 
782
- if (state.modifiedPaths.length > 0 && doc.$__.activePaths.states.modify == null) {
783
- doc.$__.activePaths.states.modify = {};
784
- }
785
782
  for (const path of state.modifiedPaths) {
786
- const currentState = doc.$__.activePaths.paths[path];
787
- if (currentState != null) {
788
- delete doc.$__.activePaths[currentState][path];
789
- }
790
- doc.$__.activePaths.paths[path] = 'modify';
791
- doc.$__.activePaths.states.modify[path] = true;
783
+ doc.$__.activePaths.modify(path);
792
784
  }
793
785
 
794
786
  for (const path of state.atomics.keys()) {
package/lib/document.js CHANGED
@@ -49,6 +49,8 @@ const isPromise = require('./helpers/isPromise');
49
49
  const deepEqual = utils.deepEqual;
50
50
  const isMongooseObject = utils.isMongooseObject;
51
51
 
52
+ const applyDefaultsBeforeSettersOptions = Object.freeze({ skipParentChangeTracking: true });
53
+
52
54
  const arrayAtomicsBackupSymbol = require('./helpers/symbols').arrayAtomicsBackupSymbol;
53
55
  const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
54
56
  const documentArrayParent = require('./helpers/symbols').documentArrayParent;
@@ -91,7 +93,7 @@ function Document(obj, fields, options) {
91
93
  if (typeof options === 'boolean') {
92
94
  throw new Error('The skipId parameter has been removed. Use { skipId: true } in the options parameter instead.');
93
95
  }
94
- options = Object.assign({}, options);
96
+ options = options == null ? {} : Object.assign({}, options);
95
97
  let skipId = options.skipId;
96
98
 
97
99
  this.$__ = new InternalCache();
@@ -143,7 +145,7 @@ function Document(obj, fields, options) {
143
145
  this.$__.strictMode = schema.options.strict;
144
146
  }
145
147
 
146
- const requiredPaths = schema.requiredPaths(true);
148
+ const requiredPaths = schema.requiredPaths();
147
149
  for (const path of requiredPaths) {
148
150
  this.$__.activePaths.require(path);
149
151
  }
@@ -162,15 +164,14 @@ function Document(obj, fields, options) {
162
164
  $__hasIncludedChildren(fields) :
163
165
  null;
164
166
 
167
+ let hasPostSetterDefaults = true;
165
168
  if (this._doc == null) {
166
169
  this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
167
170
 
168
171
  // By default, defaults get applied **before** setting initial values
169
172
  // Re: gh-6155
170
173
  if (defaults) {
171
- applyDefaults(this, fields, exclude, hasIncludedChildren, true, null, {
172
- skipParentChangeTracking: true
173
- });
174
+ hasPostSetterDefaults = applyDefaults(this, fields, exclude, hasIncludedChildren, true, null, applyDefaultsBeforeSettersOptions);
174
175
  }
175
176
  }
176
177
  if (obj) {
@@ -181,7 +182,7 @@ function Document(obj, fields, options) {
181
182
  this.$set(obj, undefined, true, options);
182
183
  }
183
184
 
184
- if (obj instanceof Document) {
185
+ if (obj.$__ != null && obj instanceof Document) {
185
186
  this.$isNew = obj.$isNew;
186
187
  }
187
188
  }
@@ -193,7 +194,7 @@ function Document(obj, fields, options) {
193
194
  if (options.skipDefaults) {
194
195
  this.$__.skipDefaults = options.skipDefaults;
195
196
  }
196
- } else if (defaults) {
197
+ } else if (defaults && hasPostSetterDefaults) {
197
198
  applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
198
199
  }
199
200
 
@@ -560,6 +561,14 @@ function $applyDefaultsToNested(val, path, doc) {
560
561
  */
561
562
 
562
563
  Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasIncludedChildren) {
564
+ // This function only creates intermediate objects for nested paths (e.g.
565
+ // `{ nested: {} }` for path `nested.name`), so if the schema has no nested
566
+ // paths then `_doc` starts out empty.
567
+ if (!utils.hasOwnKeys(this.$__schema.nested)) {
568
+ this._doc = {};
569
+ return;
570
+ }
571
+
563
572
  const doc = {};
564
573
 
565
574
  const paths = Object.keys(this.$__schema.paths);
@@ -1927,7 +1936,13 @@ Document.prototype.$inc = function $inc(path, val) {
1927
1936
  * @api private
1928
1937
  */
1929
1938
 
1930
- Document.prototype.$__setValue = function(path, val) {
1939
+ Document.prototype.$__setValue = function $__setValue(path, val) {
1940
+ if (Array.isArray(path) && path.length === 1) {
1941
+ // Fast path: if pre-split paths array of length 1, avoid all
1942
+ // setValue overhead and just set the value directly.
1943
+ this._doc[path[0]] = val;
1944
+ return this;
1945
+ }
1931
1946
  utils.setValue(path, val, this._doc);
1932
1947
  return this;
1933
1948
  };
@@ -2007,7 +2022,7 @@ Document.prototype.get = function(path, type, options) {
2007
2022
  return undefined;
2008
2023
  }
2009
2024
 
2010
- if (obj?._doc) {
2025
+ if (obj && obj._doc) {
2011
2026
  obj = obj._doc;
2012
2027
  }
2013
2028
 
@@ -2582,18 +2597,18 @@ Document.prototype.isSelected = function isSelected(path) {
2582
2597
  return inclusive;
2583
2598
  }
2584
2599
 
2585
- const pathDot = path + '.';
2600
+ const pathHasDot = path.indexOf('.') !== -1;
2586
2601
 
2587
2602
  for (const cur of paths) {
2588
2603
  if (cur === '_id') {
2589
2604
  continue;
2590
2605
  }
2591
2606
 
2592
- if (cur.startsWith(pathDot)) {
2593
- return inclusive || cur !== pathDot;
2607
+ if (cur.charAt(path.length) === '.' && cur.slice(0, path.length) === path) {
2608
+ return inclusive || cur !== path + '.';
2594
2609
  }
2595
2610
 
2596
- if (pathDot.startsWith(cur + '.')) {
2611
+ if (pathHasDot && path.charAt(cur.length) === '.' && path.slice(0, cur.length) === cur) {
2597
2612
  return inclusive;
2598
2613
  }
2599
2614
  }
@@ -2713,11 +2728,22 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
2713
2728
  this.$__.validating = true;
2714
2729
  }
2715
2730
 
2731
+ const hasValidateHooks = this.$__middleware.hasHooks('validate');
2716
2732
  try {
2717
2733
  try {
2718
- [options] = await this._execDocumentPreHooks('validate', options, [options]);
2734
+ if (hasValidateHooks) {
2735
+ [options] = await this._execDocumentPreHooks('validate', options, [options]);
2736
+ } else if (!_skipParallelValidateCheck) {
2737
+ // Even with no validate hooks, preserve the async boundary that the pre
2738
+ // hook `await` used to provide so that the parallel validate check still
2739
+ // observes `$__.validating` across a tick (gh-8468). insertMany's per-doc
2740
+ // validate passes `_skipParallelValidateCheck` and stays fully synchronous.
2741
+ await Promise.resolve();
2742
+ }
2719
2743
  } catch (error) {
2720
- await this._execDocumentPostHooks('validate', options, error);
2744
+ if (hasValidateHooks) {
2745
+ await this._execDocumentPostHooks('validate', options, error);
2746
+ }
2721
2747
  return;
2722
2748
  }
2723
2749
 
@@ -2789,7 +2815,11 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
2789
2815
  if (paths.length === 0) {
2790
2816
  const error = _completeValidate(this);
2791
2817
 
2792
- await this._execDocumentPostHooks('validate', options, error);
2818
+ if (hasValidateHooks) {
2819
+ await this._execDocumentPostHooks('validate', options, error);
2820
+ } else if (error != null) {
2821
+ throw error;
2822
+ }
2793
2823
  return;
2794
2824
  }
2795
2825
 
@@ -2884,7 +2914,11 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
2884
2914
 
2885
2915
  const error = _completeValidate(this);
2886
2916
 
2887
- await this._execDocumentPostHooks('validate', options, error);
2917
+ if (hasValidateHooks) {
2918
+ await this._execDocumentPostHooks('validate', options, error);
2919
+ } else if (error != null) {
2920
+ throw error;
2921
+ }
2888
2922
  } finally {
2889
2923
  // Assign rather than `delete` to avoid putting `$__` in dictionary mode
2890
2924
  this.$__.validateModifiedOnly = undefined;
@@ -2947,25 +2981,8 @@ function _completeValidate(doc) {
2947
2981
  return error;
2948
2982
  }
2949
2983
 
2950
- function _evaluateRequiredFunctions(doc) {
2951
- const requiredFields = Object.keys(doc.$__.activePaths.getStatePaths('require'));
2952
- let i = 0;
2953
- const len = requiredFields.length;
2954
- for (i = 0; i < len; ++i) {
2955
- const path = requiredFields[i];
2956
-
2957
- const p = doc.$__schema.path(path);
2958
-
2959
- if (typeof p?.originalRequiredValue === 'function') {
2960
- doc.$__.cachedRequired = doc.$__.cachedRequired || {};
2961
- try {
2962
- doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc, doc);
2963
- } catch (err) {
2964
- doc.invalidate(path, err);
2965
- }
2966
- }
2967
- }
2968
- }
2984
+ const STATES_TO_VALIDATE = Object.freeze(['init', 'default', 'modify']);
2985
+ const STAR_CHAR_CODE = '*'.charCodeAt(0);
2969
2986
 
2970
2987
  /*!
2971
2988
  * ignore
@@ -2973,65 +2990,103 @@ function _evaluateRequiredFunctions(doc) {
2973
2990
 
2974
2991
  function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate) {
2975
2992
  const doValidateOptions = {};
2993
+ const schema = doc.$__schema;
2994
+ const schemaPaths = schema.paths;
2995
+ const activeStates = doc.$__.activePaths.states;
2996
+
2997
+ // gh-16379: compute the modified-path set at most once and reuse it below, so
2998
+ // checking many required paths doesn't rebuild it per-path (O(required x modified)).
2999
+ let _modifiedPaths;
3000
+ const getModifiedPaths = () => (_modifiedPaths ??= doc.modifiedPaths());
2976
3001
 
2977
- _evaluateRequiredFunctions(doc);
2978
3002
  // only validate required fields when necessary
2979
- let paths = new Set(Object.keys(doc.$__.activePaths.getStatePaths('require')).filter(function(path) {
2980
- if (!doc.$__isSelected(path) && !doc.$isModified(path)) {
2981
- return false;
2982
- }
2983
- if (path.endsWith('.$*')) {
2984
- // Skip $* paths - they represent map schemas, not actual document paths
2985
- return false;
2986
- }
2987
- if (doc.$__.cachedRequired != null && path in doc.$__.cachedRequired) {
2988
- return doc.$__.cachedRequired[path];
3003
+ let paths = [];
3004
+ const requireStates = activeStates.require;
3005
+ if (requireStates != null) {
3006
+ let modifiedPaths = null;
3007
+ for (const path in requireStates) {
3008
+ // Evaluate function-valued `required` for every required path, before any
3009
+ // filtering, so `invalidate()` calls and `cachedRequired` entries stay
3010
+ // consistent no matter which paths end up validated.
3011
+ const type = Object.hasOwn(schemaPaths, path) ? schemaPaths[path] : schema.path(path);
3012
+ if (typeof type?.originalRequiredValue === 'function') {
3013
+ const cachedRequired = doc.$__.cachedRequired ?? (doc.$__.cachedRequired = {});
3014
+ try {
3015
+ cachedRequired[path] = type.originalRequiredValue.call(doc, doc);
3016
+ } catch (err) {
3017
+ doc.invalidate(path, err);
3018
+ }
3019
+ }
3020
+
3021
+ if (!doc.$__isSelected(path)) {
3022
+ if (modifiedPaths === null) {
3023
+ modifiedPaths = doc.modifiedPaths();
3024
+ }
3025
+ if (!doc.$isModified(path, null, modifiedPaths)) {
3026
+ continue;
3027
+ }
3028
+ }
3029
+ if (path.charCodeAt(path.length - 1) === STAR_CHAR_CODE && path.endsWith('.$*')) {
3030
+ // Skip $* paths - they represent map schemas, not actual document paths
3031
+ continue;
3032
+ }
3033
+ const cachedRequired = doc.$__.cachedRequired;
3034
+ if (cachedRequired != null && path in cachedRequired) {
3035
+ if (cachedRequired[path]) {
3036
+ paths.push(path);
3037
+ }
3038
+ } else {
3039
+ paths.push(path);
3040
+ }
2989
3041
  }
2990
- return true;
2991
- }));
3042
+ }
2992
3043
 
2993
- Object.keys(doc.$__.activePaths.getStatePaths('init')).forEach(addToPaths);
2994
- Object.keys(doc.$__.activePaths.getStatePaths('modify')).forEach(addToPaths);
2995
- Object.keys(doc.$__.activePaths.getStatePaths('default')).forEach(addToPaths);
2996
- function addToPaths(p) {
2997
- if (p.endsWith('.$*')) {
2998
- // Skip $* paths - they represent map schemas, not actual document paths
2999
- return;
3044
+ for (let i = 0; i < STATES_TO_VALIDATE.length; ++i) {
3045
+ const statePaths = activeStates[STATES_TO_VALIDATE[i]];
3046
+ if (statePaths == null) {
3047
+ continue;
3000
3048
  }
3049
+ for (const p in statePaths) {
3050
+ if (p.charCodeAt(p.length - 1) === STAR_CHAR_CODE && p.endsWith('.$*')) {
3051
+ // Skip $* paths - they represent map schemas, not actual document paths
3052
+ continue;
3053
+ }
3001
3054
 
3002
- const _pathType = doc.$__schema.path(p);
3003
-
3004
- // Optimization: if primitive path with no validators, or array of primitives
3005
- // with no validators, skip validating this path entirely.
3006
- // Note: paths with no _pathType (e.g. sub-paths under Mixed) must still be
3007
- // added, as they trigger validation of the parent Mixed path.
3008
- if (_pathType) {
3009
- if (!_pathType.schema &&
3010
- !_pathType.embeddedSchemaType &&
3011
- _pathType.validators.length === 0 &&
3012
- !_pathType.$parentSchemaDocArray &&
3013
- // gh-15957: skip this optimization for SchemaMap as maps can contain subdocuments
3014
- // that need validation even if the map itself has no validators
3015
- !_pathType.$isSchemaMap &&
3016
- !_pathType.$isSchemaUnion) {
3017
- return;
3018
- } else if (_pathType.$isMongooseArray &&
3019
- !_pathType.$isMongooseDocumentArray && // Skip document arrays...
3020
- !_pathType.embeddedSchemaType.$isMongooseArray && // and arrays of arrays
3021
- _pathType.validators.length === 0 && // and arrays with top-level validators
3022
- _pathType.embeddedSchemaType.validators.length === 0) {
3023
- return;
3055
+ const _pathType = Object.hasOwn(schemaPaths, p) ? schemaPaths[p] : schema.path(p);
3056
+
3057
+ // Optimization: if primitive path with no validators, or array of primitives
3058
+ // with no validators, skip validating this path entirely.
3059
+ // Note: paths with no _pathType (e.g. sub-paths under Mixed) must still be
3060
+ // added, as they trigger validation of the parent Mixed path.
3061
+ if (_pathType) {
3062
+ if (!_pathType.schema &&
3063
+ !_pathType.embeddedSchemaType &&
3064
+ _pathType.validators.length === 0 &&
3065
+ !_pathType.$parentSchemaDocArray &&
3066
+ // gh-15957: skip this optimization for SchemaMap as maps can contain subdocuments
3067
+ // that need validation even if the map itself has no validators
3068
+ !_pathType.$isSchemaMap &&
3069
+ !_pathType.$isSchemaUnion) {
3070
+ continue;
3071
+ } else if (_pathType.$isMongooseArray &&
3072
+ !_pathType.$isMongooseDocumentArray && // Skip document arrays...
3073
+ !_pathType.embeddedSchemaType.$isMongooseArray && // and arrays of arrays
3074
+ _pathType.validators.length === 0 && // and arrays with top-level validators
3075
+ _pathType.embeddedSchemaType.validators.length === 0) {
3076
+ continue;
3077
+ }
3024
3078
  }
3025
- }
3026
3079
 
3027
- paths.add(p);
3080
+ paths.push(p);
3081
+ }
3028
3082
  }
3029
3083
 
3030
- const onlyPrimitiveValues = doc.$__hasOnlyPrimitiveValues();
3031
- if (onlyPrimitiveValues && paths.size === 0) {
3032
- return [[], doValidateOptions];
3084
+ if (paths.length === 0 && doc.$__hasOnlyPrimitiveValues()) {
3085
+ return [paths, doValidateOptions];
3033
3086
  }
3034
3087
 
3088
+ paths = new Set(paths);
3089
+
3035
3090
  if (!isNestedValidate) {
3036
3091
  // If we're validating a subdocument, all this logic will run anyway on the top-level document, so skip for subdocuments.
3037
3092
  // But only run for top-level subdocuments, because we're looking for subdocuments that are not modified at top-level but
@@ -3056,7 +3111,19 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
3056
3111
  }
3057
3112
  }
3058
3113
  }
3059
- const modifiedPaths = doc.modifiedPaths();
3114
+ const modifiedPaths = getModifiedPaths();
3115
+
3116
+ // gh-6818: if a document array that has array-level validators has any
3117
+ // modified element, validate the array itself so those validators run.
3118
+ for (const modifiedPath of modifiedPaths) {
3119
+ const modifiedPathType = doc.$__schema.path(modifiedPath);
3120
+ if (modifiedPathType != null &&
3121
+ modifiedPathType.$isMongooseDocumentArray &&
3122
+ modifiedPathType.validators.length > 0) {
3123
+ paths.add(modifiedPath);
3124
+ }
3125
+ }
3126
+
3060
3127
  for (const subdoc of topLevelSubdocs) {
3061
3128
  if (subdoc.$basePath) {
3062
3129
  const fullPathToSubdoc = subdoc.$__pathRelativeToParent();
@@ -3096,7 +3163,7 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
3096
3163
  }
3097
3164
  }
3098
3165
 
3099
- if (!onlyPrimitiveValues) {
3166
+ if (!doc.$__hasOnlyPrimitiveValues()) {
3100
3167
  for (const path of paths) {
3101
3168
  const _pathType = doc.$__schema.path(path);
3102
3169
  if (_pathType && _pathType.$isMongooseDocumentArray) {
@@ -3135,8 +3202,8 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
3135
3202
  // Re: gh-8468
3136
3203
  const singleNestedPaths = doc.$__schema.singleNestedPaths;
3137
3204
  for (const path of Object.keys(flat)) {
3138
- if (!Object.hasOwn(singleNestedPaths, path)) {
3139
- addToPaths(path);
3205
+ if (!Object.hasOwn(singleNestedPaths, path) && !path.endsWith('.$*')) {
3206
+ paths.add(path);
3140
3207
  }
3141
3208
  }
3142
3209
  }
@@ -3628,23 +3695,23 @@ Document.prototype.$isValid = function(path) {
3628
3695
  * Resets the internal modified state of this document.
3629
3696
  *
3630
3697
  * @api private
3698
+ * @param {boolean} [skipBackup] If `true`, skips the backup of modified paths for performance
3699
+ * @param {boolean} [hasOnlyPrimitiveValues] Precalculate `$__hasOnlyPrimitiveValues()` for performance
3631
3700
  * @return {Document} this
3632
3701
  * @method $__reset
3633
3702
  * @memberOf Document
3634
3703
  * @instance
3635
3704
  */
3636
3705
 
3637
- Document.prototype.$__reset = function reset() {
3638
- let _this = this;
3639
-
3640
- const onlyPrimitiveValues = this.$__hasOnlyPrimitiveValues();
3706
+ Document.prototype.$__reset = function reset(skipBackup, hasOnlyPrimitiveValues) {
3707
+ hasOnlyPrimitiveValues = hasOnlyPrimitiveValues ?? this.$__hasOnlyPrimitiveValues();
3641
3708
 
3642
3709
  // Skip for subdocuments. Also skip if doc only has primitive values,
3643
3710
  // because primitives can't be subdocs.
3644
- const subdocs = !this.$isSubdocument && !onlyPrimitiveValues ? this.$getAllSubdocs({ useCache: true }) : null;
3711
+ const subdocs = !this.$isSubdocument && !hasOnlyPrimitiveValues ? this.$getAllSubdocs({ useCache: true }) : null;
3645
3712
  if (subdocs?.length > 0) {
3646
3713
  for (const subdoc of subdocs) {
3647
- subdoc.$__reset();
3714
+ subdoc.$__reset(skipBackup);
3648
3715
  }
3649
3716
  }
3650
3717
 
@@ -3653,27 +3720,32 @@ Document.prototype.$__reset = function reset() {
3653
3720
  // arrays, a Map, and does parent-path deduplication we don't need here.
3654
3721
  // Skip entirely if the doc only has primitive values, because only arrays
3655
3722
  // and maps have atomics.
3656
- if (!onlyPrimitiveValues) {
3723
+ if (!hasOnlyPrimitiveValues) {
3657
3724
  this.$__resetAtomics();
3658
3725
  }
3659
3726
 
3660
- this.$__.backup = {};
3661
- this.$__.backup.activePaths = {
3662
- modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')),
3663
- default: Object.assign({}, this.$__.activePaths.getStatePaths('default'))
3664
- };
3665
- this.$__.backup.validationError = this.$__.validationError;
3666
- this.$__.backup.errors = this.$errors;
3727
+ if (!skipBackup) {
3728
+ this.$__.backup = {};
3729
+ this.$__.backup.activePaths = {
3730
+ modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')),
3731
+ default: Object.assign({}, this.$__.activePaths.getStatePaths('default'))
3732
+ };
3733
+ this.$__.backup.validationError = this.$__.validationError;
3734
+ this.$__.backup.errors = this.$errors;
3735
+ }
3667
3736
 
3668
3737
  // Clear 'dirty' cache
3669
- this.$__.activePaths.clear('modify');
3670
- this.$__.activePaths.clear('default');
3671
- this.$__.validationError = undefined;
3672
- this.$errors = undefined;
3673
- _this = this;
3674
- this.$__schema.requiredPaths().forEach(function(path) {
3675
- _this.$__.activePaths.require(path);
3676
- });
3738
+ this.$__.activePaths.clearAllExcept('init');
3739
+ if (this.$__.validationError) {
3740
+ this.$__.validationError = undefined;
3741
+ }
3742
+ if (this.$errors) {
3743
+ this.$errors = undefined;
3744
+ }
3745
+
3746
+ for (const path of this.$__schema.requiredPaths()) {
3747
+ this.$__.activePaths.require(path);
3748
+ }
3677
3749
 
3678
3750
  return this;
3679
3751
  };
@@ -4003,9 +4075,6 @@ Document.prototype.$toObject = function(options, json) {
4003
4075
  if (depopulate && options._isNested && this.$__.wasPopulated) {
4004
4076
  return clone(this.$__.wasPopulated.value || this._doc._id, options);
4005
4077
  }
4006
- if (depopulate) {
4007
- options.depopulate = true;
4008
- }
4009
4078
 
4010
4079
  // merge default options with input options.
4011
4080
  if (defaultOptions != null) {
@@ -4017,7 +4086,6 @@ Document.prototype.$toObject = function(options, json) {
4017
4086
  }
4018
4087
  options._isNested = true;
4019
4088
  options.json = json;
4020
- options.minimize = _minimize;
4021
4089
 
4022
4090
  const parentOptions = options._parentOptions;
4023
4091
  // Parent options should only bubble down for subdocuments, not populated docs
@@ -4025,7 +4093,7 @@ Document.prototype.$toObject = function(options, json) {
4025
4093
 
4026
4094
  const schemaFieldsOnly = options._calledWithOptions.schemaFieldsOnly
4027
4095
  ?? options.schemaFieldsOnly
4028
- ?? defaultOptions.schemaFieldsOnly
4096
+ ?? defaultOptions?.schemaFieldsOnly
4029
4097
  ?? false;
4030
4098
 
4031
4099
  let ret;
@@ -4062,7 +4130,7 @@ Document.prototype.$toObject = function(options, json) {
4062
4130
 
4063
4131
  const getters = options._calledWithOptions.getters
4064
4132
  ?? options.getters
4065
- ?? defaultOptions.getters
4133
+ ?? defaultOptions?.getters
4066
4134
  ?? false;
4067
4135
 
4068
4136
  if (getters) {
@@ -4074,7 +4142,7 @@ Document.prototype.$toObject = function(options, json) {
4074
4142
  }
4075
4143
 
4076
4144
  const virtuals = options._calledWithOptions.virtuals
4077
- ?? defaultOptions.virtuals
4145
+ ?? defaultOptions?.virtuals
4078
4146
  ?? parentOptions?.virtuals
4079
4147
  ?? undefined;
4080
4148
 
@@ -4089,7 +4157,7 @@ Document.prototype.$toObject = function(options, json) {
4089
4157
  const transform = options._calledWithOptions.transform ?? true;
4090
4158
  let transformFunction = undefined;
4091
4159
  if (transform === true) {
4092
- transformFunction = defaultOptions.transform;
4160
+ transformFunction = defaultOptions?.transform;
4093
4161
  } else if (typeof transform === 'function') {
4094
4162
  transformFunction = transform;
4095
4163
  }
@@ -4402,6 +4470,32 @@ function applyGetters(self, json) {
4402
4470
  while (i--) {
4403
4471
  path = paths[i];
4404
4472
 
4473
+ const schemaType = schema.paths[path];
4474
+ // Skip if no getters, including `$__isSelected()` because that can be expensive
4475
+ if (!schemaType.getters?.length && !schemaType.embeddedSchemaType?.getters?.length) {
4476
+ continue;
4477
+ }
4478
+
4479
+ if (!self.$__isSelected(path)) {
4480
+ continue;
4481
+ }
4482
+
4483
+ if (path.indexOf('.') === -1) {
4484
+ json[path] = schemaType.applyGetters(
4485
+ json[path],
4486
+ self
4487
+ );
4488
+ if (Array.isArray(json[path]) && schemaType.embeddedSchemaType) {
4489
+ for (let i = 0; i < json[path].length; ++i) {
4490
+ json[path][i] = schemaType.embeddedSchemaType.applyGetters(
4491
+ json[path][i],
4492
+ self
4493
+ );
4494
+ }
4495
+ }
4496
+ continue;
4497
+ }
4498
+
4405
4499
  const parts = path.split('.');
4406
4500
 
4407
4501
  const plen = parts.length;
@@ -4410,10 +4504,6 @@ function applyGetters(self, json) {
4410
4504
  let part;
4411
4505
  cur = self._doc;
4412
4506
 
4413
- if (!self.$__isSelected(path)) {
4414
- continue;
4415
- }
4416
-
4417
4507
  for (let ii = 0; ii < plen; ++ii) {
4418
4508
  part = parts[ii];
4419
4509
  v = cur[part];
@@ -4423,13 +4513,13 @@ function applyGetters(self, json) {
4423
4513
  if (branch != null && typeof branch !== 'object') {
4424
4514
  break;
4425
4515
  } else if (ii === last) {
4426
- branch[part] = schema.paths[path].applyGetters(
4516
+ branch[part] = schemaType.applyGetters(
4427
4517
  branch[part],
4428
4518
  self
4429
4519
  );
4430
- if (Array.isArray(branch[part]) && schema.paths[path].embeddedSchemaType) {
4520
+ if (Array.isArray(branch[part]) && schemaType.embeddedSchemaType) {
4431
4521
  for (let i = 0; i < branch[part].length; ++i) {
4432
- branch[part][i] = schema.paths[path].embeddedSchemaType.applyGetters(
4522
+ branch[part][i] = schemaType.embeddedSchemaType.applyGetters(
4433
4523
  branch[part][i],
4434
4524
  self
4435
4525
  );
@@ -4461,10 +4551,10 @@ function applyGetters(self, json) {
4461
4551
 
4462
4552
  function applySchemaTypeTransforms(self, json) {
4463
4553
  const schema = self.$__schema;
4464
- const paths = Object.keys(schema.paths || {});
4554
+ const paths = schema.pathsWithTransforms();
4465
4555
  const cur = self._doc;
4466
4556
 
4467
- if (!cur) {
4557
+ if (!cur || !paths) {
4468
4558
  return json;
4469
4559
  }
4470
4560
 
@@ -5659,13 +5749,22 @@ Document.prototype.$clearModifiedPaths = function $clearModifiedPaths() {
5659
5749
  */
5660
5750
 
5661
5751
  Document.prototype.$__hasOnlyPrimitiveValues = function $__hasOnlyPrimitiveValues() {
5662
- return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => {
5663
- return v == null
5752
+ if (this.$__.populated) {
5753
+ return false;
5754
+ }
5755
+ const doc = this._doc;
5756
+ for (const key in doc) {
5757
+ const v = doc[key];
5758
+ if (v == null
5664
5759
  || typeof v !== 'object'
5665
5760
  || (utils.isNativeObject(v) && !Array.isArray(v))
5666
5761
  || isBsonType(v, 'ObjectId')
5667
- || isBsonType(v, 'Decimal128');
5668
- }));
5762
+ || isBsonType(v, 'Decimal128')) {
5763
+ continue;
5764
+ }
5765
+ return false;
5766
+ }
5767
+ return true;
5669
5768
  };
5670
5769
 
5671
5770
  /*!