From 0fbe1d439b1e42bd5e621dc0b63aa8312f0586c0 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Thu, 11 Jul 2024 13:02:18 +0300 Subject: [PATCH 1/7] Fix ChangeStream.close to return a Promise like the driver Client code that calls stream.close().then(...) fails with TypeError, since mongoose close() returns undefined. --- lib/cursor/changeStream.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cursor/changeStream.js b/lib/cursor/changeStream.js index 64fcbf22ab9..ec5cac5705f 100644 --- a/lib/cursor/changeStream.js +++ b/lib/cursor/changeStream.js @@ -141,8 +141,9 @@ class ChangeStream extends EventEmitter { close() { this.closed = true; if (this.driverChangeStream) { - this.driverChangeStream.close(); + return this.driverChangeStream.close(); } + return Promise.resolve(); } } From 3646952acc46d1d16e792f1a72c293a0150c4494 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 11 Jul 2024 13:25:21 -0400 Subject: [PATCH 2/7] fix(model): handle `transactionAsyncLocalStorage` option with `insertMany()` Fix #14738 --- lib/model.js | 5 +++++ test/docs/transactions.test.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/lib/model.js b/lib/model.js index 9cd7a14d6de..767c8e67121 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2838,6 +2838,11 @@ Model.$__insertMany = function(arr, options, callback) { const throwOnValidationError = typeof options.throwOnValidationError === 'boolean' ? options.throwOnValidationError : false; const lean = !!options.lean; + const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore(); + if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) { + options = { ...options, session: asyncLocalStorage.session }; + } + if (!Array.isArray(arr)) { arr = [arr]; } diff --git a/test/docs/transactions.test.js b/test/docs/transactions.test.js index b68996d86a0..ab41a7063bc 100644 --- a/test/docs/transactions.test.js +++ b/test/docs/transactions.test.js @@ -388,6 +388,8 @@ describe('transactions', function() { }(); assert.equal(doc.name, 'test_transactionAsyncLocalStorage'); + await Test.insertMany([{ name: 'bar' }]); + throw new Error('Oops!'); }), /Oops!/ @@ -397,6 +399,9 @@ describe('transactions', function() { exists = await Test.exists({ name: 'foo' }); assert.ok(!exists); + + exists = await Test.exists({ name: 'bar' }); + assert.ok(!exists); }); }); From 1f9fa4ab85a091df1760c7029a1eba7d1a965955 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 11 Jul 2024 14:13:13 -0400 Subject: [PATCH 3/7] fix(model): support `session: null` option for `save()` to opt out of automatic `session` option with `transactionAsyncLocalStorage` Fix #14736 --- lib/model.js | 4 ++-- test/docs/transactions.test.js | 13 ++++++++++++- test/document.test.js | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/model.js b/lib/model.js index 9cd7a14d6de..82dd0857bed 100644 --- a/lib/model.js +++ b/lib/model.js @@ -290,9 +290,9 @@ Model.prototype.$__handleSave = function(options, callback) { const session = this.$session(); const asyncLocalStorage = this[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore(); - if (!saveOptions.hasOwnProperty('session') && session != null) { + if (!options.hasOwnProperty('session') && session != null) { saveOptions.session = session; - } else if (asyncLocalStorage?.session != null) { + } else if (!options.hasOwnProperty('session') && asyncLocalStorage?.session != null) { saveOptions.session = asyncLocalStorage.session; } if (this.$isNew) { diff --git a/test/docs/transactions.test.js b/test/docs/transactions.test.js index b68996d86a0..b51a6ae7fd3 100644 --- a/test/docs/transactions.test.js +++ b/test/docs/transactions.test.js @@ -370,7 +370,7 @@ describe('transactions', function() { await Test.createCollection(); await Test.deleteMany({}); - const doc = new Test({ name: 'test_transactionAsyncLocalStorage' }); + let doc = new Test({ name: 'test_transactionAsyncLocalStorage' }); await assert.rejects( () => m.connection.transaction(async() => { await doc.save(); @@ -397,6 +397,17 @@ describe('transactions', function() { exists = await Test.exists({ name: 'foo' }); assert.ok(!exists); + + doc = new Test({ name: 'test_transactionAsyncLocalStorage' }); + await assert.rejects( + () => m.connection.transaction(async() => { + await doc.save({ session: null }); + throw new Error('Oops!'); + }), + /Oops!/ + ); + exists = await Test.exists({ _id: doc._id }); + assert.ok(exists); }); }); diff --git a/test/document.test.js b/test/document.test.js index 0e7eae43e52..c11f72753ee 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -13746,7 +13746,7 @@ describe('document', function() { }); }); -describe('Check if instance function that is supplied in schema option is availabe', function() { +describe('Check if instance function that is supplied in schema option is available', function() { it('should give an instance function back rather than undefined', function ModelJS() { const testSchema = new mongoose.Schema({}, { methods: { instanceFn() { return 'Returned from DocumentInstanceFn'; } } }); const TestModel = mongoose.model('TestModel', testSchema); From 7466843187c450480ea6cbd14da1c930251a06f9 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 12 Jul 2024 12:23:58 -0400 Subject: [PATCH 4/7] style: fix lint --- test/docs/transactions.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/docs/transactions.test.js b/test/docs/transactions.test.js index 0406798e0c6..2b19aef9ee3 100644 --- a/test/docs/transactions.test.js +++ b/test/docs/transactions.test.js @@ -399,7 +399,7 @@ describe('transactions', function() { exists = await Test.exists({ name: 'foo' }); assert.ok(!exists); - + exists = await Test.exists({ name: 'bar' }); assert.ok(!exists); From dc7966ca1932b81509ada86f6ac7f51a353e9c26 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 12 Jul 2024 12:35:16 -0400 Subject: [PATCH 5/7] fix tests --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 8c3b15ddaba..78c7c2cb4b5 100644 --- a/lib/model.js +++ b/lib/model.js @@ -290,7 +290,7 @@ Model.prototype.$__handleSave = function(options, callback) { const session = this.$session(); const asyncLocalStorage = this[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore(); - if (!options.hasOwnProperty('session') && session != null) { + if (!saveOptions.hasOwnProperty('session') && session != null) { saveOptions.session = session; } else if (!options.hasOwnProperty('session') && asyncLocalStorage?.session != null) { saveOptions.session = asyncLocalStorage.session; From 41b1ce30e698d937ef0abf768ff83258571175d2 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 12 Jul 2024 12:47:15 -0400 Subject: [PATCH 6/7] refactor: remove unnecessary condition and add clarifying comment --- lib/model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 78c7c2cb4b5..507ec49ff1d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -290,9 +290,10 @@ Model.prototype.$__handleSave = function(options, callback) { const session = this.$session(); const asyncLocalStorage = this[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore(); - if (!saveOptions.hasOwnProperty('session') && session != null) { + if (session != null) { saveOptions.session = session; } else if (!options.hasOwnProperty('session') && asyncLocalStorage?.session != null) { + // Only set session from asyncLocalStorage if `session` option wasn't originally passed in options saveOptions.session = asyncLocalStorage.session; } if (this.$isNew) { From 1972f7f04ce92bcef097a40d7c223122e56fb029 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 12 Jul 2024 15:21:57 -0400 Subject: [PATCH 7/7] chore: release 8.5.1 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7f2da7e9f2..241620b2b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +8.5.1 / 2024-07-12 +================== + * perf(model): performance improvements for insertMany() #14724 + * fix(model): avoid leaving subdoc defaults on top-level doc when setting subdocument to same value #14728 #14722 + * fix(model): handle transactionAsyncLocalStorage option with insertMany() #14743 + * types: make _id required on Document type #14735 #14660 + * types: fix ChangeStream.close to return a Promise like the driver #14740 [orgads](https://github.com/orgads) + 8.5.0 / 2024-07-08 ================== * perf: memoize toJSON / toObject default options #14672 diff --git a/package.json b/package.json index 8e2d22784d2..477448ac4f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "8.5.0", + "version": "8.5.1", "author": "Guillermo Rauch ", "keywords": [ "mongodb",