diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ceef2d6060..fd76b02d43e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +8.3.4 / 2024-05-06 +================== + * perf(document): avoid cloning options using spread operator for perf reasons #14565 #14394 + * fix(query): apply translateAliases before casting to avoid strictMode error when using aliases #14562 #14521 + * fix(model): consistent top-level timestamps option for bulkWrite operations +#14546 #14536 + * docs(connections): improve description of connection creation patterns #14564 #14528 + 8.3.3 / 2024-04-29 ================== * perf(document): add fast path for applying non-nested virtuals to JSON #14543 diff --git a/lib/document.js b/lib/document.js index 8a9149ebbe9..54267d26b9e 100644 --- a/lib/document.js +++ b/lib/document.js @@ -3780,7 +3780,7 @@ Document.prototype.$__handleReject = function handleReject(err) { */ Document.prototype.$toObject = function(options, json) { - let defaultOptions = { + const defaultOptions = { transform: true, flattenDecimals: true }; @@ -3793,7 +3793,7 @@ Document.prototype.$toObject = function(options, json) { const schemaOptions = this.$__schema && this.$__schema.options || {}; // merge base default options with Schema's set default options if available. // `clone` is necessary here because `utils.options` directly modifies the second input. - defaultOptions = { ...defaultOptions, ...baseOptions, ...schemaOptions[path] }; + Object.assign(defaultOptions, baseOptions, schemaOptions[path]); // If options do not exist or is not an object, set it to empty object options = utils.isPOJO(options) ? { ...options } : {}; @@ -3830,21 +3830,18 @@ Document.prototype.$toObject = function(options, json) { // `clone()` will recursively call `$toObject()` on embedded docs, so we // need the original options the user passed in, plus `_isNested` and // `_parentOptions` for checking whether we need to depopulate. - const cloneOptions = Object.assign({}, options, { + const cloneOptions = { _isNested: true, json: json, minimize: _minimize, flattenMaps: flattenMaps, flattenObjectIds: flattenObjectIds, - _seen: (options && options._seen) || new Map() - }); - - if (utils.hasUserDefinedProperty(options, 'getters')) { - cloneOptions.getters = options.getters; - } - if (utils.hasUserDefinedProperty(options, 'virtuals')) { - cloneOptions.virtuals = options.virtuals; - } + _seen: (options && options._seen) || new Map(), + _calledWithOptions: options._calledWithOptions, + virtuals: options.virtuals, + getters: options.getters, + depopulate: options.depopulate + }; const depopulate = options.depopulate || (options._parentOptions && options._parentOptions.depopulate || false); @@ -3855,25 +3852,27 @@ Document.prototype.$toObject = function(options, json) { } // merge default options with input options. - options = { ...defaultOptions, ...options }; + for (const key of Object.keys(defaultOptions)) { + if (options[key] == null) { + options[key] = defaultOptions[key]; + } + } options._isNested = true; options.json = json; options.minimize = _minimize; cloneOptions._parentOptions = options; - cloneOptions._skipSingleNestedGetters = false; - - const gettersOptions = Object.assign({}, cloneOptions); - gettersOptions._skipSingleNestedGetters = true; + cloneOptions._skipSingleNestedGetters = false; // remember the root transform function // to save it from being overwritten by sub-transform functions const originalTransform = options.transform; let ret = clone(this._doc, cloneOptions) || {}; + cloneOptions._skipSingleNestedGetters = true; if (options.getters) { - applyGetters(this, ret, gettersOptions); + applyGetters(this, ret, cloneOptions); if (options.minimize) { ret = minimize(ret) || {}; @@ -3881,7 +3880,7 @@ Document.prototype.$toObject = function(options, json) { } if (options.virtuals || (options.getters && options.virtuals !== false)) { - applyVirtuals(this, ret, gettersOptions, options); + applyVirtuals(this, ret, cloneOptions, options); } if (options.versionKey === false && this.$__schema.options.versionKey) { diff --git a/package.json b/package.json index 22ee6ddfbc7..e543bd33e0c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "8.3.3", + "version": "8.3.4", "author": "Guillermo Rauch ", "keywords": [ "mongodb", diff --git a/test/document.test.js b/test/document.test.js index a770b3b8352..b8030900f9b 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -722,32 +722,27 @@ describe('document', function() { lastName: String, password: String }); - userSchema.virtual('fullName').get(function() { return this.firstName + ' ' + this.lastName; }); - userSchema.set('toObject', { virtuals: false }); const postSchema = new Schema({ owner: { type: Schema.Types.ObjectId, ref: 'User' }, content: String }); - postSchema.virtual('capContent').get(function() { return this.content.toUpperCase(); }); - postSchema.set('toObject', { virtuals: true }); + const User = db.model('User', userSchema); const Post = db.model('BlogPost', postSchema); const user = new User({ firstName: 'Joe', lastName: 'Smith', password: 'password' }); - const savedUser = await user.save(); const post = await Post.create({ owner: savedUser._id, content: 'lorem ipsum' }); - const newPost = await Post.findById(post._id).populate('owner').exec(); const obj = newPost.toObject(); diff --git a/test/model.populate.test.js b/test/model.populate.test.js index a987bd6d4cc..9ad6376f89a 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -2976,33 +2976,27 @@ describe('model: populate:', function() { return ret; } }); - const Team = db.model('Test', teamSchema); const userSchema = new Schema({ username: String }); - userSchema.set('toJSON', { transform: function(doc, ret) { return ret; } }); - const User = db.model('User', userSchema); const user = new User({ username: 'Test' }); - await user.save(); const team = new Team({ members: [{ user: user }] }); - await team.save(); - await team.populate('members.user'); + assert.equal(calls, 0); team.toJSON(); - assert.equal(calls, 1); });