@@ -31,6 +31,7 @@ const getKeysInSchemaOrder = require('./helpers/schema/getKeysInSchemaOrder');
31
31
const getSubdocumentStrictValue = require ( './helpers/schema/getSubdocumentStrictValue' ) ;
32
32
const handleSpreadDoc = require ( './helpers/document/handleSpreadDoc' ) ;
33
33
const immediate = require ( './helpers/immediate' ) ;
34
+ const isBsonType = require ( './helpers/isBsonType' ) ;
34
35
const isDefiningProjection = require ( './helpers/projection/isDefiningProjection' ) ;
35
36
const isExclusive = require ( './helpers/projection/isExclusive' ) ;
36
37
const isPathExcluded = require ( './helpers/projection/isPathExcluded' ) ;
@@ -2611,17 +2612,6 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
2611
2612
let parallelValidate ;
2612
2613
this . $op = 'validate' ;
2613
2614
2614
- if ( this . $isSubdocument != null ) {
2615
- // Skip parallel validate check for subdocuments
2616
- } else if ( this . $__ . validating ) {
2617
- parallelValidate = new ParallelValidateError ( this , {
2618
- parentStack : options && options . parentStack ,
2619
- conflictStack : this . $__ . validating . stack
2620
- } ) ;
2621
- } else {
2622
- this . $__ . validating = new ParallelValidateError ( this , { parentStack : options && options . parentStack } ) ;
2623
- }
2624
-
2625
2615
if ( arguments . length === 1 ) {
2626
2616
if ( typeof arguments [ 0 ] === 'object' && ! Array . isArray ( arguments [ 0 ] ) ) {
2627
2617
options = arguments [ 0 ] ;
@@ -2632,6 +2622,18 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
2632
2622
const isOnePathOnly = options . pathsToSkip . indexOf ( ' ' ) === - 1 ;
2633
2623
options . pathsToSkip = isOnePathOnly ? [ options . pathsToSkip ] : options . pathsToSkip . split ( ' ' ) ;
2634
2624
}
2625
+ const _skipParallelValidateCheck = options && options . _skipParallelValidateCheck ;
2626
+
2627
+ if ( this . $isSubdocument != null ) {
2628
+ // Skip parallel validate check for subdocuments
2629
+ } else if ( this . $__ . validating && ! _skipParallelValidateCheck ) {
2630
+ parallelValidate = new ParallelValidateError ( this , {
2631
+ parentStack : options && options . parentStack ,
2632
+ conflictStack : this . $__ . validating . stack
2633
+ } ) ;
2634
+ } else if ( ! _skipParallelValidateCheck ) {
2635
+ this . $__ . validating = new ParallelValidateError ( this , { parentStack : options && options . parentStack } ) ;
2636
+ }
2635
2637
2636
2638
if ( parallelValidate != null ) {
2637
2639
throw parallelValidate ;
@@ -3480,31 +3482,33 @@ Document.prototype.$__reset = function reset() {
3480
3482
let _this = this ;
3481
3483
3482
3484
// Skip for subdocuments
3483
- const subdocs = this . $parent ( ) === this ? this . $getAllSubdocs ( ) : [ ] ;
3484
- const resetArrays = new Set ( ) ;
3485
- for ( const subdoc of subdocs ) {
3486
- const fullPathWithIndexes = subdoc . $__fullPathWithIndexes ( ) ;
3487
- subdoc . $__reset ( ) ;
3488
- if ( this . isModified ( fullPathWithIndexes ) || isParentInit ( fullPathWithIndexes ) ) {
3489
- if ( subdoc . $isDocumentArrayElement ) {
3490
- resetArrays . add ( subdoc . parentArray ( ) ) ;
3491
- } else {
3492
- const parent = subdoc . $parent ( ) ;
3493
- if ( parent === this ) {
3494
- this . $__ . activePaths . clearPath ( subdoc . $basePath ) ;
3495
- } else if ( parent != null && parent . $isSubdocument ) {
3496
- // If map path underneath subdocument, may end up with a case where
3497
- // map path is modified but parent still needs to be reset. See gh-10295
3498
- parent . $__reset ( ) ;
3485
+ const subdocs = ! this . $isSubdocument ? this . $getAllSubdocs ( ) : null ;
3486
+ if ( subdocs && subdocs . length > 0 ) {
3487
+ const resetArrays = new Set ( ) ;
3488
+ for ( const subdoc of subdocs ) {
3489
+ const fullPathWithIndexes = subdoc . $__fullPathWithIndexes ( ) ;
3490
+ subdoc . $__reset ( ) ;
3491
+ if ( this . isModified ( fullPathWithIndexes ) || isParentInit ( fullPathWithIndexes ) ) {
3492
+ if ( subdoc . $isDocumentArrayElement ) {
3493
+ resetArrays . add ( subdoc . parentArray ( ) ) ;
3494
+ } else {
3495
+ const parent = subdoc . $parent ( ) ;
3496
+ if ( parent === this ) {
3497
+ this . $__ . activePaths . clearPath ( subdoc . $basePath ) ;
3498
+ } else if ( parent != null && parent . $isSubdocument ) {
3499
+ // If map path underneath subdocument, may end up with a case where
3500
+ // map path is modified but parent still needs to be reset. See gh-10295
3501
+ parent . $__reset ( ) ;
3502
+ }
3499
3503
}
3500
3504
}
3501
3505
}
3502
- }
3503
3506
3504
- for ( const array of resetArrays ) {
3505
- this . $__ . activePaths . clearPath ( array . $path ( ) ) ;
3506
- array [ arrayAtomicsBackupSymbol ] = array [ arrayAtomicsSymbol ] ;
3507
- array [ arrayAtomicsSymbol ] = { } ;
3507
+ for ( const array of resetArrays ) {
3508
+ this . $__ . activePaths . clearPath ( array . $path ( ) ) ;
3509
+ array [ arrayAtomicsBackupSymbol ] = array [ arrayAtomicsSymbol ] ;
3510
+ array [ arrayAtomicsSymbol ] = { } ;
3511
+ }
3508
3512
}
3509
3513
3510
3514
function isParentInit ( path ) {
@@ -3809,6 +3813,8 @@ Document.prototype.$__handleReject = function handleReject(err) {
3809
3813
Document . prototype . $toObject = function ( options , json ) {
3810
3814
const defaultOptions = this . $__schema . _defaultToObjectOptions ( json ) ;
3811
3815
3816
+ const hasOnlyPrimitiveValues = this . $__hasOnlyPrimitiveValues ( ) ;
3817
+
3812
3818
// If options do not exist or is not an object, set it to empty object
3813
3819
options = utils . isPOJO ( options ) ? { ...options } : { } ;
3814
3820
options . _calledWithOptions = options . _calledWithOptions || { ...options } ;
@@ -3823,7 +3829,9 @@ Document.prototype.$toObject = function(options, json) {
3823
3829
}
3824
3830
3825
3831
options . minimize = _minimize ;
3826
- options . _seen = options . _seen || new Map ( ) ;
3832
+ if ( ! hasOnlyPrimitiveValues ) {
3833
+ options . _seen = options . _seen || new Map ( ) ;
3834
+ }
3827
3835
3828
3836
const depopulate = options . _calledWithOptions . depopulate
3829
3837
?? options . _parentOptions ?. depopulate
@@ -3854,7 +3862,14 @@ Document.prototype.$toObject = function(options, json) {
3854
3862
// to save it from being overwritten by sub-transform functions
3855
3863
// const originalTransform = options.transform;
3856
3864
3857
- let ret = clone ( this . _doc , options ) || { } ;
3865
+ let ret ;
3866
+ if ( hasOnlyPrimitiveValues && ! options . flattenObjectIds ) {
3867
+ // Fast path: if we don't have any nested objects or arrays, we only need a
3868
+ // shallow clone.
3869
+ ret = this . $__toObjectShallow ( ) ;
3870
+ } else {
3871
+ ret = clone ( this . _doc , options ) || { } ;
3872
+ }
3858
3873
3859
3874
options . _skipSingleNestedGetters = true ;
3860
3875
const getters = options . _calledWithOptions . getters
@@ -3912,6 +3927,26 @@ Document.prototype.$toObject = function(options, json) {
3912
3927
return ret ;
3913
3928
} ;
3914
3929
3930
+ /*!
3931
+ * Internal shallow clone alternative to `$toObject()`: much faster, no options processing
3932
+ */
3933
+
3934
+ Document . prototype . $__toObjectShallow = function $__toObjectShallow ( ) {
3935
+ const ret = { } ;
3936
+ if ( this . _doc != null ) {
3937
+ for ( const key of Object . keys ( this . _doc ) ) {
3938
+ const value = this . _doc [ key ] ;
3939
+ if ( value instanceof Date ) {
3940
+ ret [ key ] = new Date ( value ) ;
3941
+ } else if ( value !== undefined ) {
3942
+ ret [ key ] = value ;
3943
+ }
3944
+ }
3945
+ }
3946
+
3947
+ return ret ;
3948
+ } ;
3949
+
3915
3950
/**
3916
3951
* Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)).
3917
3952
*
@@ -5292,6 +5327,20 @@ Document.prototype.$clearModifiedPaths = function $clearModifiedPaths() {
5292
5327
return this ;
5293
5328
} ;
5294
5329
5330
+ /*!
5331
+ * Check if the given document only has primitive values
5332
+ */
5333
+
5334
+ Document . prototype . $__hasOnlyPrimitiveValues = function $__hasOnlyPrimitiveValues ( ) {
5335
+ return ! this . $__ . populated && ! this . $__ . wasPopulated && ( this . _doc == null || Object . values ( this . _doc ) . every ( v => {
5336
+ return v == null
5337
+ || typeof v !== 'object'
5338
+ || ( utils . isNativeObject ( v ) && ! Array . isArray ( v ) )
5339
+ || isBsonType ( v , 'ObjectId' )
5340
+ || isBsonType ( v , 'Decimal128' ) ;
5341
+ } ) ) ;
5342
+ } ;
5343
+
5295
5344
/*!
5296
5345
* Module exports.
5297
5346
*/
0 commit comments