From 195b46ccbbe56ac014ad92daafdf0e3dc9bda012 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 12 Jan 2024 10:07:42 -0500 Subject: [PATCH 01/89] fix(document): allow calling `push()` with different `$position` arguments Fix #14244 Re: #4322 --- lib/types/array/methods/index.js | 18 ++++++++---------- test/document.test.js | 13 +++++++------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/types/array/methods/index.js b/lib/types/array/methods/index.js index 13b3f493c98..45c88a9b2ad 100644 --- a/lib/types/array/methods/index.js +++ b/lib/types/array/methods/index.js @@ -2,7 +2,6 @@ const Document = require('../../../document'); const ArraySubdocument = require('../../ArraySubdocument'); -const MongooseError = require('../../../error/mongooseError'); const cleanModifiedSubpaths = require('../../../helpers/document/cleanModifiedSubpaths'); const internalToObjectOptions = require('../../../options').internalToObjectOptions; const mpath = require('mpath'); @@ -684,22 +683,21 @@ const methods = { if ((atomics.$push && atomics.$push.$each && atomics.$push.$each.length || 0) !== 0 && atomics.$push.$position != atomic.$position) { - throw new MongooseError('Cannot call `Array#push()` multiple times ' + - 'with different `$position`'); - } + if (atomic.$position != null) { + [].splice.apply(arr, [atomic.$position, 0].concat(values)); + ret = arr.length; + } else { + ret = [].push.apply(arr, values); + } - if (atomic.$position != null) { + this._registerAtomic('$set', this); + } else if (atomic.$position != null) { [].splice.apply(arr, [atomic.$position, 0].concat(values)); ret = this.length; } else { ret = [].push.apply(arr, values); } } else { - if ((atomics.$push && atomics.$push.$each && atomics.$push.$each.length || 0) !== 0 && - atomics.$push.$position != null) { - throw new MongooseError('Cannot call `Array#push()` multiple times ' + - 'with different `$position`'); - } atomic = values; ret = [].push.apply(arr, values); } diff --git a/test/document.test.js b/test/document.test.js index 627a43cf1d7..cd2246a1e9b 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -8232,12 +8232,13 @@ describe('document', function() { $each: [0], $position: 0 }); - assert.throws(() => { - doc.nums.push({ $each: [5] }); - }, /Cannot call.*multiple times/); - assert.throws(() => { - doc.nums.push(5); - }, /Cannot call.*multiple times/); + assert.deepStrictEqual(doc.nums.$__getAtomics(), [['$push', { $each: [0], $position: 0 }]]); + + doc.nums.push({ $each: [5] }); + assert.deepStrictEqual(doc.nums.$__getAtomics(), [['$set', [0, 1, 2, 3, 4, 5]]]); + + doc.nums.push({ $each: [0.5], $position: 1 }); + assert.deepStrictEqual(doc.nums.$__getAtomics(), [['$set', [0, 0.5, 1, 2, 3, 4, 5]]]); }); it('setting a path to a single nested document should update the single nested doc parent (gh-8400)', function() { From 6bc42cee9803cc056ecf8a83e697a7f617d60e17 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 12 Jan 2024 10:30:32 -0500 Subject: [PATCH 02/89] test: add missing issue to test title --- test/document.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/document.test.js b/test/document.test.js index cd2246a1e9b..beefe366d1f 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -8208,7 +8208,7 @@ describe('document', function() { assert.deepEqual(Object.keys(err.errors), ['age']); }); - it('array push with $position (gh-4322)', async function() { + it('array push with $position (gh-14244) (gh-4322)', async function() { const schema = Schema({ nums: [Number] }); From d3e22af841abb65ce5366c5fc0fa605d5e8ccb53 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 12 Jan 2024 17:01:39 -0500 Subject: [PATCH 03/89] fix(document): handle embedded recursive discriminators on nested path defined using Schema.prototype.discriminator Fix #14245 --- .../applyEmbeddedDiscriminators.js | 9 ++-- test/document.test.js | 48 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/helpers/discriminator/applyEmbeddedDiscriminators.js b/lib/helpers/discriminator/applyEmbeddedDiscriminators.js index 840a4dc628e..9a04ecb072f 100644 --- a/lib/helpers/discriminator/applyEmbeddedDiscriminators.js +++ b/lib/helpers/discriminator/applyEmbeddedDiscriminators.js @@ -19,11 +19,10 @@ function applyEmbeddedDiscriminators(schema, seen = new WeakSet()) { if (schemaType._appliedDiscriminators) { continue; } - for (const disc of schemaType.schema._applyDiscriminators.keys()) { - schemaType.discriminator( - disc, - schemaType.schema._applyDiscriminators.get(disc) - ); + for (const discriminatorKey of schemaType.schema._applyDiscriminators.keys()) { + const discriminatorSchema = schemaType.schema._applyDiscriminators.get(discriminatorKey); + applyEmbeddedDiscriminators(discriminatorSchema, seen); + schemaType.discriminator(discriminatorKey, discriminatorSchema); } schemaType._appliedDiscriminators = true; } diff --git a/test/document.test.js b/test/document.test.js index 7c416967f1b..484294ce0d1 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -12787,6 +12787,54 @@ describe('document', function() { await doc2.save(); }); + it('handles embedded recursive discriminators on nested path defined using Schema.prototype.discriminator (gh-14245)', async function() { + const baseSchema = new Schema({ + type: { type: Number, required: true } + }, { discriminatorKey: 'type' }); + + class Base { + whoAmI() { return 'I am Base'; } + } + + baseSchema.loadClass(Base); + + class NumberTyped extends Base { + whoAmI() { return 'I am NumberTyped'; } + } + + class StringTyped extends Base { + whoAmI() { return 'I am StringTyped'; } + } + + const selfRefSchema = new Schema({ + self: { type: [baseSchema], required: true } + }); + + class SelfReferenceTyped extends Base { + whoAmI() { return 'I am SelfReferenceTyped'; } + } + + selfRefSchema.loadClass(SelfReferenceTyped); + baseSchema.discriminator(5, selfRefSchema); + + const numberTypedSchema = new Schema({}).loadClass(NumberTyped); + const stringTypedSchema = new Schema({}).loadClass(StringTyped); + baseSchema.discriminator(1, numberTypedSchema); + baseSchema.discriminator(3, stringTypedSchema); + const containerSchema = new Schema({ items: [baseSchema] }); + const containerModel = db.model('Test', containerSchema); + + const instance = await containerModel.create({ + items: [{ type: 5, self: [{ type: 1 }, { type: 3 }] }] + }); + + assert.equal(instance.items[0].whoAmI(), 'I am SelfReferenceTyped'); + assert.deepStrictEqual(instance.items[0].self.map(item => item.whoAmI()), [ + 'I am NumberTyped', + 'I am StringTyped' + ]); + }); + it('can use `collection` as schema name (gh-13956)', async function() { const schema = new mongoose.Schema({ name: String, collection: String }); const Test = db.model('Test', schema); From 64fa4705c807e1ed285f0c5355e37854a5485f1b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 17 Jan 2024 06:59:00 -0500 Subject: [PATCH 04/89] docs(model+query+findoneandupdate): add more details about `overwriteDiscriminatorKey` option to docs Fix #14246 --- docs/tutorials/findoneandupdate.md | 38 +++++++++++++++++++++++++++++- lib/model.js | 4 ++++ lib/query.js | 5 +++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/findoneandupdate.md b/docs/tutorials/findoneandupdate.md index c589b15b952..5ca0314fff6 100644 --- a/docs/tutorials/findoneandupdate.md +++ b/docs/tutorials/findoneandupdate.md @@ -7,10 +7,18 @@ However, there are some cases where you need to use [`findOneAndUpdate()`](https * [Atomic Updates](#atomic-updates) * [Upsert](#upsert) * [The `rawResult` Option](#raw-result) +* [Updating Discriminator Keys](#updating-discriminator-keys) ## Getting Started -As the name implies, `findOneAndUpdate()` finds the first document that matches a given `filter`, applies an `update`, and returns the document. By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. +As the name implies, `findOneAndUpdate()` finds the first document that matches a given `filter`, applies an `update`, and returns the document. +The `findOneAndUpdate()` function has the following signature: + +```javascript +function findOneAndUpdate(filter, update, options) {} +``` + +By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. ```acquit [require:Tutorial.*findOneAndUpdate.*basic case] @@ -78,3 +86,31 @@ Here's what the `res` object from the above example looks like: age: 29 }, ok: 1 } ``` + +## Updating Discriminator Keys + +Mongoose prevents updating the [discriminator key](https://mongoosejs.com/docs/discriminators.html#discriminator-keys) using `findOneAndUpdate()` by default. +For example, suppose you have the following discriminator models. + +```javascript +const eventSchema = new mongoose.Schema({ time: Date }); +const Event = db.model('Event', eventSchema); + +const ClickedLinkEvent = Event.discriminator( + 'ClickedLink', + new mongoose.Schema({ url: String }) +); + +const SignedUpEvent = Event.discriminator( + 'SignedUp', + new mongoose.Schema({ username: String }) +); +``` + +Mongoose will remove `__t` (the default discriminator key) from the `update` parameter, if `__t` is set. +This is to prevent unintentional updates to the discriminator key; for example, if you're passing untrusted user input to the `update` parameter. +However, you can tell Mongoose to allow updating the discriminator key by setting the `overwriteDiscriminatorKey` option to `true` as shown below. + +```acquit +[require:use overwriteDiscriminatorKey to change discriminator key] +``` diff --git a/lib/model.js b/lib/model.js index e97407429aa..aee450da45d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2428,6 +2428,7 @@ Model.$where = function $where() { * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @return {Query} * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ @@ -2524,6 +2525,7 @@ Model.findOneAndUpdate = function(conditions, update, options) { * @param {Boolean} [options.new=false] if true, return the modified document rather than the original * @param {Object|String} [options.select] sets the document fields to return. * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @return {Query} * @see Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate() * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/ @@ -3888,6 +3890,7 @@ Model.hydrate = function(obj, projection, options) { * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @return {Query} * @see Query docs https://mongoosejs.com/docs/queries.html * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output @@ -3927,6 +3930,7 @@ Model.updateMany = function updateMany(conditions, doc, options) { * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @return {Query} * @see Query docs https://mongoosejs.com/docs/queries.html * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output diff --git a/lib/query.js b/lib/query.js index a6063cc5fc4..5db5c0e7ef7 100644 --- a/lib/query.js +++ b/lib/query.js @@ -3213,6 +3213,7 @@ function prepareDiscriminatorCriteria(query) { * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html * @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/ * @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html @@ -4003,6 +4004,7 @@ Query.prototype._replaceOne = async function _replaceOne() { * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @param {Function} [callback] params are (error, writeOpResult) * @return {Query} this * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update() @@ -4071,7 +4073,8 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) { * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. - @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @param {Function} [callback] params are (error, writeOpResult) * @return {Query} this * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update() From ee0d28f640d99b1fb19ec1ca54a6e016f9880d2f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 17 Jan 2024 13:38:34 -0500 Subject: [PATCH 05/89] Update docs/tutorials/findoneandupdate.md Co-authored-by: hasezoey --- docs/tutorials/findoneandupdate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/findoneandupdate.md b/docs/tutorials/findoneandupdate.md index 5ca0314fff6..3f34bf3cc6d 100644 --- a/docs/tutorials/findoneandupdate.md +++ b/docs/tutorials/findoneandupdate.md @@ -89,7 +89,7 @@ Here's what the `res` object from the above example looks like: ## Updating Discriminator Keys -Mongoose prevents updating the [discriminator key](https://mongoosejs.com/docs/discriminators.html#discriminator-keys) using `findOneAndUpdate()` by default. +Mongoose prevents updating the [discriminator key](../discriminators.html#discriminator-keys) using `findOneAndUpdate()` by default. For example, suppose you have the following discriminator models. ```javascript From cc80894fccb43cc16d5d3606888437073c479d0b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 17 Jan 2024 13:38:42 -0500 Subject: [PATCH 06/89] Update lib/model.js Co-authored-by: hasezoey --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index aee450da45d..967fed3494d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3890,7 +3890,7 @@ Model.hydrate = function(obj, projection, options) { * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern) * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object. - * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key + * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key * @return {Query} * @see Query docs https://mongoosejs.com/docs/queries.html * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output From 73bea51b3f306d1f4553bbb03864b8098aa02ea3 Mon Sep 17 00:00:00 2001 From: Rohan Kothapalli Date: Thu, 18 Jan 2024 17:19:25 +0530 Subject: [PATCH 07/89] null check --- lib/types/ArraySubdocument.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/ArraySubdocument.js b/lib/types/ArraySubdocument.js index 55889caa839..af0bdd0d4a1 100644 --- a/lib/types/ArraySubdocument.js +++ b/lib/types/ArraySubdocument.js @@ -137,7 +137,7 @@ ArraySubdocument.prototype.$__fullPath = function(path, skipIndex) { */ ArraySubdocument.prototype.$__pathRelativeToParent = function(path, skipIndex) { - if (this.__index == null) { + if (this.__index == null || (!this.__parentArray || !this.__parentArray.$path)) { return null; } if (skipIndex) { From 900e9fa7e5e0fc1a9e368bd8c1327b4fdf2a9207 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 21 Jan 2024 07:13:00 -0500 Subject: [PATCH 08/89] fix(collection): correctly handle buffer timeouts with `find()` Fix #14184 --- lib/drivers/node-mongodb-native/collection.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/drivers/node-mongodb-native/collection.js b/lib/drivers/node-mongodb-native/collection.js index 8cb3cbf8586..8f874542574 100644 --- a/lib/drivers/node-mongodb-native/collection.js +++ b/lib/drivers/node-mongodb-native/collection.js @@ -138,10 +138,23 @@ function iter(i) { let _args = args; let promise = null; let timeout = null; - if (syncCollectionMethods[i]) { - this.addQueue(() => { - lastArg.call(this, null, this[i].apply(this, _args.slice(0, _args.length - 1))); - }, []); + if (syncCollectionMethods[i] && typeof lastArg === 'function') { + this.addQueue(i, _args); + callback = lastArg; + } else if (syncCollectionMethods[i]) { + promise = new this.Promise((resolve, reject) => { + callback = function collectionOperationCallback(err, res) { + if (timeout != null) { + clearTimeout(timeout); + } + if (err != null) { + return reject(err); + } + resolve(res); + }; + _args = args.concat([callback]); + this.addQueue(i, _args); + }); } else if (typeof lastArg === 'function') { callback = function collectionOperationCallback() { if (timeout != null) { From 6b67f9bf65001b36a99a1fb86b55a10706c3b2ee Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 21 Jan 2024 13:09:08 -0500 Subject: [PATCH 09/89] test: add test case for #14184 --- test/collection.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/collection.test.js b/test/collection.test.js index 8b4fa71ea4c..627050413e3 100644 --- a/test/collection.test.js +++ b/test/collection.test.js @@ -68,6 +68,19 @@ describe('collections:', function() { }); }); + it('returns a promise if buffering and callback with find() (gh-14184)', function(done) { + db = mongoose.createConnection(); + const collection = db.collection('gh14184'); + collection.opts.bufferTimeoutMS = 100; + + collection.find({ foo: 'bar' }, {}, (err, docs) => { + assert.ok(err); + assert.ok(err.message.includes('buffering timed out after 100ms')); + assert.equal(docs, undefined); + done(); + }); + }); + it('methods should that throw (unimplemented)', function() { const collection = new Collection('test', mongoose.connection); let thrown = false; From 775aadf16be64ffc19f86aa05797095ad3d9eca3 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 26 Dec 2023 15:53:14 -0500 Subject: [PATCH 10/89] types(model): correct return type for findByIdAndDelete() Fix #14190 --- test/types/queries.test.ts | 8 ++++++++ types/models.d.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index efca12618a5..2b8ecff5504 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -518,3 +518,11 @@ function gh13630() { const x: UpdateQueryKnownOnly = { $set: { name: 'John' } }; expectAssignable>(x); } + +function gh14190() { + const userSchema = new Schema({ name: String, age: Number }); + const UserModel = model('User', userSchema); + + const doc = await UserModel.findByIdAndDelete('0'.repeat(24)); + expectType | null>(doc); +} diff --git a/types/models.d.ts b/types/models.d.ts index 2c7f54b17f8..7cb082bc372 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -550,8 +550,8 @@ declare module 'mongoose' { 'findOneAndDelete' >; findByIdAndDelete( - id?: mongodb.ObjectId | any, - options?: QueryOptions & { includeResultMetadata: true } + id: mongodb.ObjectId | any, + options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'findOneAndDelete'>; findByIdAndDelete( id?: mongodb.ObjectId | any, From 7f9406ff44c5d8536599060c3ae38009f866ab60 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 26 Dec 2023 16:14:07 -0500 Subject: [PATCH 11/89] types(query): improve findByIdAndDelete return type for query re: #14190 --- test/types/queries.test.ts | 18 +++++++++++++++++- types/query.d.ts | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index 2b8ecff5504..bf20145e1b7 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -17,7 +17,7 @@ import { ProjectionFields, QueryOptions } from 'mongoose'; -import { ObjectId } from 'mongodb'; +import { ModifyResult, ObjectId } from 'mongodb'; import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd'; import { autoTypedModel } from './models.test'; import { AutoTypedSchemaType } from './schema.test'; @@ -525,4 +525,20 @@ function gh14190() { const doc = await UserModel.findByIdAndDelete('0'.repeat(24)); expectType | null>(doc); + + const res = await UserModel.findByIdAndDelete( + '0'.repeat(24), + { includeResultMetadata: true } + ); + expectAssignable< + ModifyResult> + >(res); + + const res2 = await UserModel.find().findByIdAndDelete( + '0'.repeat(24), + { includeResultMetadata: true } + ); + expectAssignable< + ModifyResult> + >(res2); } diff --git a/types/query.d.ts b/types/query.d.ts index 2234e66f656..39ff69de9df 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -435,6 +435,10 @@ declare module 'mongoose' { ): QueryWithHelpers; /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ + findByIdAndDelete( + id: mongodb.ObjectId | any, + options: QueryOptions & { includeResultMetadata: true } + ): QueryWithHelpers, DocType, THelpers, RawDocType, 'findOneAndDelete'>; findByIdAndDelete( id?: mongodb.ObjectId | any, options?: QueryOptions | null From a6e4d18ab7bbb015d9f87fd3990b8e5bf4922825 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 21 Jan 2024 14:10:00 -0500 Subject: [PATCH 12/89] style: fix lint --- test/types/queries.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index bf20145e1b7..ed56bb9d7a0 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -532,7 +532,7 @@ function gh14190() { ); expectAssignable< ModifyResult> - >(res); + >(res); const res2 = await UserModel.find().findByIdAndDelete( '0'.repeat(24), @@ -540,5 +540,5 @@ function gh14190() { ); expectAssignable< ModifyResult> - >(res2); + >(res2); } From 09181ef655e1d0360c3b4a60f1ef15c39c56cb15 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 22 Jan 2024 11:03:26 -0500 Subject: [PATCH 13/89] chore: release 6.12.6 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e18dc705af4..1a112597b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +6.12.6 / 2024-01-22 +=================== + * fix(collection): correctly handle buffer timeouts with find() #14277 + * fix(document): allow calling push() with different $position arguments #14254 + 6.12.5 / 2024-01-03 =================== * perf(schema): remove unnecessary lookahead in numeric subpath check diff --git a/package.json b/package.json index f4d2b41c199..9cb365a38b0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "6.12.5", + "version": "6.12.6", "author": "Guillermo Rauch ", "keywords": [ "mongodb", From 9d2121ee3cac8928a2a6cda80f3f4e361e71e673 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 1 Feb 2024 21:12:59 -0500 Subject: [PATCH 14/89] types: add overwriteMiddlewareResult and skipMiddlewareFunction to types Fix #14289 --- test/types/base.test.ts | 9 +++++++++ types/index.d.ts | 6 ++++++ types/middlewares.d.ts | 8 ++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/test/types/base.test.ts b/test/types/base.test.ts index 1dfaaa2ef70..fba2acf37b0 100644 --- a/test/types/base.test.ts +++ b/test/types/base.test.ts @@ -8,6 +8,15 @@ Object.values(mongoose.models).forEach(model => { mongoose.pluralize(null); +mongoose.overwriteMiddlewareResult('foo'); +const schema = new mongoose.Schema({ name: String }); +schema.pre('save', function() { + return mongoose.skipMiddlewareFunction('foobar'); +}); +schema.post('save', function() { + return mongoose.overwriteMiddlewareResult('foobar'); +}); + function gh10746() { type A = string extends Function ? never : string; diff --git a/types/index.d.ts b/types/index.d.ts index 987d35c8117..3c94c7f94cf 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -677,5 +677,11 @@ declare module 'mongoose' { /* for ts-mongoose */ export class mquery { } + export class OverwriteMiddlewareResult {} + export function overwriteMiddlewareResult(val: any): OverwriteMiddlewareResult; + + export class SkipWrappedFunction {} + export function skipMiddlewareFunction(val: any): SkipWrappedFunction; + export default mongoose; } diff --git a/types/middlewares.d.ts b/types/middlewares.d.ts index 9302b9b7d48..af928ff6a76 100644 --- a/types/middlewares.d.ts +++ b/types/middlewares.d.ts @@ -37,13 +37,13 @@ declare module 'mongoose' { this: ThisType, next: CallbackWithoutResultAndOptionalError, opts?: Record - ) => void | Promise; + ) => void | Promise | SkipWrappedFunction; type PreSaveMiddlewareFunction = ( this: ThisType, next: CallbackWithoutResultAndOptionalError, opts: SaveOptions - ) => void | Promise; - type PostMiddlewareFunction = (this: ThisType, res: ResType, next: CallbackWithoutResultAndOptionalError) => void | Promise; + ) => void | Promise | SkipWrappedFunction; + type PostMiddlewareFunction = (this: ThisType, res: ResType, next: CallbackWithoutResultAndOptionalError) => void | Promise | OverwriteMiddlewareResult; type ErrorHandlingMiddlewareFunction = (this: ThisType, err: NativeError, res: ResType, next: CallbackWithoutResultAndOptionalError) => void; - type ErrorHandlingMiddlewareWithOption = (this: ThisType, err: NativeError, res: ResType | null, next: CallbackWithoutResultAndOptionalError) => void | Promise; + type ErrorHandlingMiddlewareWithOption = (this: ThisType, err: NativeError, res: ResType | null, next: CallbackWithoutResultAndOptionalError) => void | Promise | OverwriteMiddlewareResult; } From 34feac04113fa9b7263d167b0d89cc0c51a010aa Mon Sep 17 00:00:00 2001 From: Brown Date: Tue, 13 Feb 2024 16:42:01 +0900 Subject: [PATCH 15/89] introduce resumeTokenChanged into 6.x --- lib/cursor/ChangeStream.js | 2 +- test/model.test.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/cursor/ChangeStream.js b/lib/cursor/ChangeStream.js index 24c2f55665a..6cee9f8b371 100644 --- a/lib/cursor/ChangeStream.js +++ b/lib/cursor/ChangeStream.js @@ -75,7 +75,7 @@ class ChangeStream extends EventEmitter { this.closed = true; }); - ['close', 'change', 'end', 'error'].forEach(ev => { + ['close', 'change', 'end', 'error', 'resumeTokenChanged'].forEach(ev => { this.driverChangeStream.on(ev, data => { // Sometimes Node driver still polls after close, so // avoid any uncaught exceptions due to closed change streams diff --git a/test/model.test.js b/test/model.test.js index 700dfb87502..00573da4440 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -5406,6 +5406,20 @@ describe('Model', function() { assert.equal(changeData.operationType, 'insert'); assert.equal(changeData.fullDocument.name, 'Child'); }); + + it('bubbles up resumeTokenChanged events (gh-14349)', async function() { + const MyModel = db.model('Test', new Schema({ name: String })); + + const resumeTokenChangedEvent = new Promise(resolve => { + changeStream = MyModel.watch(); + listener = data => resolve(data); + changeStream.once('resumeTokenChanged', listener); + }); + + await MyModel.create({ name: 'test' }); + const { _data } = await resumeTokenChangedEvent; + assert.ok(_data); + }); }); describe('sessions (gh-6362)', function() { From 6950c96b97a7da102444b73b86418d98e527e4e6 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 20 Feb 2024 13:16:24 -0500 Subject: [PATCH 16/89] docs(connections): add note about using `asPromise()` with `createConnection()` for error handling Fix #14266 --- docs/connections.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/connections.md b/docs/connections.md index c6a1fdf8cc3..5b65b096293 100644 --- a/docs/connections.md +++ b/docs/connections.md @@ -402,16 +402,24 @@ The `mongoose.createConnection()` function takes the same arguments as const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options); ``` -This [connection](api/connection.html#connection_Connection) object is then used to -create and retrieve [models](api/model.html#model_Model). Models are -**always** scoped to a single connection. +This [connection](api/connection.html#connection_Connection) object is then used to create and retrieve [models](api/model.html#model_Model). +Models are **always** scoped to a single connection. ```javascript const UserModel = conn.model('User', userSchema); ``` -If you use multiple connections, you should make sure you export schemas, -**not** models. Exporting a model from a file is called the *export model pattern*. +The `createConnection()` function returns a connection instance, not a promise. +If you want to use `await` to make sure Mongoose successfully connects to MongoDB, use the [`asPromise()` function](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.asPromise()): + +```javascript +// `asPromise()` returns a promise that resolves to the connection +// once the connection succeeds, or rejects if connection failed. +const conn = await mongoose.createConnection(connectionString).asPromise(); +``` + +If you use multiple connections, you should make sure you export schemas, **not** models. +Exporting a model from a file is called the *export model pattern*. The export model pattern is limited because you can only use one connection. ```javascript From 81e36d10a3f440081f69d0ccc91b3d2f3d1da995 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 22 Feb 2024 14:55:23 -0500 Subject: [PATCH 17/89] Update docs/connections.md Co-authored-by: hasezoey --- docs/connections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/connections.md b/docs/connections.md index 5b65b096293..2c58cfd2142 100644 --- a/docs/connections.md +++ b/docs/connections.md @@ -410,7 +410,7 @@ const UserModel = conn.model('User', userSchema); ``` The `createConnection()` function returns a connection instance, not a promise. -If you want to use `await` to make sure Mongoose successfully connects to MongoDB, use the [`asPromise()` function](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.asPromise()): +If you want to use `await` to make sure Mongoose successfully connects to MongoDB, use the [`asPromise()` function](api/connection.html#Connection.prototype.asPromise()): ```javascript // `asPromise()` returns a promise that resolves to the connection From 4beb8bc4408f1f64fb69e8f01e497ee869c1f397 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sat, 24 Feb 2024 17:02:32 -0500 Subject: [PATCH 18/89] feat(query): add `options` parameter to `Query.prototype.sort()` Fix #14365 --- lib/query.js | 14 +++++++++++--- test/query.test.js | 25 ++++++++++++++++++++++++- types/query.d.ts | 5 ++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/query.js b/lib/query.js index 8731f003204..7c8cb0f2d8c 100644 --- a/lib/query.js +++ b/lib/query.js @@ -2861,19 +2861,27 @@ Query.prototype.distinct = function(field, conditions) { * Cannot be used with `distinct()` * * @param {Object|String|Array>} arg + * @param {Object} [options] + * @param {Boolean} [options.override=false] If true, replace existing sort options with `arg` * @return {Query} this * @see cursor.sort https://www.mongodb.com/docs/manual/reference/method/cursor.sort/ * @api public */ -Query.prototype.sort = function(arg) { - if (arguments.length > 1) { - throw new Error('sort() only takes 1 Argument'); +Query.prototype.sort = function(arg, options) { + if (arguments.length > 2) { + throw new Error('sort() takes at most 2 arguments'); + } + if (options != null && typeof options !== 'object') { + throw new Error('sort() options argument must be an object or nullish'); } if (this.options.sort == null) { this.options.sort = {}; } + if (options && options.override) { + this.options.sort = {}; + } const sort = this.options.sort; if (typeof arg === 'string') { const properties = arg.indexOf(' ') === -1 ? [arg] : arg.split(' '); diff --git a/test/query.test.js b/test/query.test.js index 9d1cd201515..94f6911903a 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -795,7 +795,7 @@ describe('Query', function() { e = err; } assert.ok(e, 'uh oh. no error was thrown'); - assert.equal(e.message, 'sort() only takes 1 Argument'); + assert.equal(e.message, 'sort() takes at most 2 arguments'); }); }); @@ -4191,4 +4191,27 @@ describe('Query', function() { assert.strictEqual(doc.account.owner, undefined); assert.strictEqual(doc.account.taxIds, undefined); }); + + it('allows overriding sort (gh-14365)', function() { + const testSchema = new mongoose.Schema({ + name: String + }); + + const Test = db.model('Test', testSchema); + + const q = Test.find().select('name').sort({ name: -1, _id: -1 }); + assert.deepStrictEqual(q.getOptions().sort, { name: -1, _id: -1 }); + + q.sort({ name: 1 }, { override: true }); + assert.deepStrictEqual(q.getOptions().sort, { name: 1 }); + + q.sort(null, { override: true }); + assert.deepStrictEqual(q.getOptions().sort, {}); + + q.sort({ _id: 1 }, { override: true }); + assert.deepStrictEqual(q.getOptions().sort, { _id: 1 }); + + q.sort({}, { override: true }); + assert.deepStrictEqual(q.getOptions().sort, {}); + }); }); diff --git a/types/query.d.ts b/types/query.d.ts index 0bdda904350..db9275a12bd 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -715,7 +715,10 @@ declare module 'mongoose' { slice(val: number | Array): this; /** Sets the sort order. If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`. */ - sort(arg?: string | { [key: string]: SortOrder | { $meta: any } } | [string, SortOrder][] | undefined | null): this; + sort( + arg?: string | { [key: string]: SortOrder | { $meta: any } } | [string, SortOrder][] | undefined | null, + options?: { override?: boolean } + ): this; /** Sets the tailable option (for use with capped collections). */ tailable(bool?: boolean, opts?: { From 5dfb62f5d2aa6833a20a72ca3a530cae95a7888c Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Wed, 3 May 2023 12:24:35 -0400 Subject: [PATCH 19/89] test: fix issues with cherry-picking #13376 to 6.x --- lib/helpers/processConnectionOptions.js | 7 ++++--- test/connection.test.js | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/helpers/processConnectionOptions.js b/lib/helpers/processConnectionOptions.js index a9d862b1030..1dbb767ebee 100644 --- a/lib/helpers/processConnectionOptions.js +++ b/lib/helpers/processConnectionOptions.js @@ -9,11 +9,12 @@ function processConnectionOptions(uri, options) { ? opts.readPreference : getUriReadPreference(uri); + const clonedOpts = clone(opts); const resolvedOpts = (readPreference && readPreference !== 'primary' && readPreference !== 'primaryPreferred') - ? resolveOptsConflicts(readPreference, opts) - : opts; + ? resolveOptsConflicts(readPreference, clonedOpts) + : clonedOpts; - return clone(resolvedOpts); + return resolvedOpts; } function resolveOptsConflicts(pref, opts) { diff --git a/test/connection.test.js b/test/connection.test.js index dcf4cf621c7..49f715a8f4a 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -1537,4 +1537,13 @@ describe('connections:', function() { }); assert.deepEqual(m.connections.length, 0); }); + + describe('processConnectionOptions', function() { + it('should not throw an error when attempting to mutate unmutable options object gh-13335', async function() { + const m = new mongoose.Mongoose(); + const opts = Object.preventExtensions({}); + const conn = await m.connect('mongodb://localhost:27017/db?retryWrites=true&w=majority&readPreference=secondaryPreferred', opts); + assert.ok(conn); + }); + }); }); From 45f5c4c6091a47bb0a2761ba0be7a7c18d11a084 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 25 Feb 2024 17:16:13 -0500 Subject: [PATCH 20/89] perf(model): make `insertMany()` `lean` option skip hydrating Mongoose docs Fix #14372 --- lib/model.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/model.js b/lib/model.js index c5e54c46f67..d6a3026c7fd 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3430,6 +3430,13 @@ Model.$__insertMany = function(arr, options, callback) { const results = ordered ? null : new Array(arr.length); const toExecute = arr.map((doc, index) => callback => { + // If option `lean` is set to true bypass validation and hydration + if (lean) { + // we have to execute callback at the nextTick to be compatible + // with parallelLimit, as `results` variable has TDZ issue if we + // execute the callback synchronously + return immediate(() => callback(null, doc)); + } if (!(doc instanceof _this)) { try { doc = new _this(doc); @@ -3440,13 +3447,6 @@ Model.$__insertMany = function(arr, options, callback) { if (options.session != null) { doc.$session(options.session); } - // If option `lean` is set to true bypass validation - if (lean) { - // we have to execute callback at the nextTick to be compatible - // with parallelLimit, as `results` variable has TDZ issue if we - // execute the callback synchronously - return immediate(() => callback(null, doc)); - } doc.$validate({ __noPromise: true }, function(error) { if (error) { // Option `ordered` signals that insert should be continued after reaching @@ -3510,7 +3510,7 @@ Model.$__insertMany = function(arr, options, callback) { callback(null, []); return; } - const docObjects = docAttributes.map(function(doc) { + const docObjects = lean ? docAttributes : docAttributes.map(function(doc) { if (doc.$__schema.options.versionKey) { doc[doc.$__schema.options.versionKey] = 0; } @@ -3572,6 +3572,9 @@ Model.$__insertMany = function(arr, options, callback) { return !isErrored; }). map(function setIsNewForInsertedDoc(doc) { + if (lean) { + return doc; + } doc.$__reset(); _setIsNew(doc, false); return doc; @@ -3588,9 +3591,11 @@ Model.$__insertMany = function(arr, options, callback) { return; } - for (const attribute of docAttributes) { - attribute.$__reset(); - _setIsNew(attribute, false); + if (!lean) { + for (const attribute of docAttributes) { + attribute.$__reset(); + _setIsNew(attribute, false); + } } if (rawResult) { From ac9ea0157b42a5c5848705d4827a42bb71a7763c Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 25 Feb 2024 17:20:17 -0500 Subject: [PATCH 21/89] test: quick connection string fix --- test/connection.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/connection.test.js b/test/connection.test.js index 49f715a8f4a..36290280b47 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -1542,7 +1542,7 @@ describe('connections:', function() { it('should not throw an error when attempting to mutate unmutable options object gh-13335', async function() { const m = new mongoose.Mongoose(); const opts = Object.preventExtensions({}); - const conn = await m.connect('mongodb://localhost:27017/db?retryWrites=true&w=majority&readPreference=secondaryPreferred', opts); + const conn = await m.connect('mongodb://127.0.0.1:27017/db?retryWrites=true&w=majority&readPreference=secondaryPreferred', opts); assert.ok(conn); }); }); From 71d4abf45145adab53ddb1150a866fc0a2a31ca0 Mon Sep 17 00:00:00 2001 From: Sean Derrow Date: Mon, 26 Feb 2024 01:15:27 -0500 Subject: [PATCH 22/89] [fix] MongooseQueryOptions typing --- types/models.d.ts | 10 +++++----- types/query.d.ts | 37 +++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/types/models.d.ts b/types/models.d.ts index a77aa25525e..2a1b73aa05a 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -228,7 +228,7 @@ declare module 'mongoose' { /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ countDocuments( filter?: FilterQuery, - options?: (mongodb.CountOptions & Omit, 'lean' | 'timestamps'>) | null + options?: (mongodb.CountOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< number, THydratedDocumentType, @@ -266,7 +266,7 @@ declare module 'mongoose' { */ deleteMany( filter?: FilterQuery, - options?: (mongodb.DeleteOptions & Omit, 'lean' | 'timestamps'>) | null + options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< mongodb.DeleteResult, THydratedDocumentType, @@ -291,7 +291,7 @@ declare module 'mongoose' { */ deleteOne( filter?: FilterQuery, - options?: (mongodb.DeleteOptions & Omit, 'lean' | 'timestamps'>) | null + options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< mongodb.DeleteResult, THydratedDocumentType, @@ -743,14 +743,14 @@ declare module 'mongoose' { updateMany( filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: (mongodb.UpdateOptions & Omit, 'lean'>) | null + options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions) | null ): QueryWithHelpers; /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ updateOne( filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: (mongodb.UpdateOptions & Omit, 'lean'>) | null + options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions) | null ): QueryWithHelpers; /** Creates a Query, applies the passed conditions, and returns the Query. */ diff --git a/types/query.d.ts b/types/query.d.ts index 0bdda904350..1bfb4950647 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -17,25 +17,30 @@ declare module 'mongoose' { */ type FilterQuery = _FilterQuery; - type MongooseQueryOptions = Pick< - QueryOptions, - 'context' | - 'lean' | - 'multipleCastError' | - 'overwriteDiscriminatorKey' | - 'populate' | - 'runValidators' | - 'sanitizeProjection' | - 'sanitizeFilter' | - 'setDefaultsOnInsert' | - 'strict' | - 'strictQuery' | - 'timestamps' | - 'translateAliases' - > & { + type MongooseBaseQueryOptionKeys = + | "context" + | "multipleCastError" + | "overwriteDiscriminatorKey" + | "populate" + | "runValidators" + | "sanitizeProjection" + | "sanitizeFilter" + | "setDefaultsOnInsert" + | "strict" + | "strictQuery" + | "translateAliases"; + + type MongooseQueryOptions< + DocType = unknown, + Keys extends keyof QueryOptions = MongooseBaseQueryOptionKeys | "timestamps" | "lean" + > = Pick, Keys> & { [other: string]: any; }; + type MongooseBaseQueryOptions = MongooseQueryOptions; + + type MongooseUpdateQueryOptions = MongooseQueryOptions; + type ProjectionFields = { [Key in keyof DocType]?: any } & Record; type QueryWithHelpers = Query & THelpers; From ec781d560f8e0c556fb3e1d6305a3cc0407dce95 Mon Sep 17 00:00:00 2001 From: Sean Derrow Date: Mon, 26 Feb 2024 01:34:02 -0500 Subject: [PATCH 23/89] Lint --- types/query.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/types/query.d.ts b/types/query.d.ts index 1bfb4950647..f450182ffdb 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -39,7 +39,10 @@ declare module 'mongoose' { type MongooseBaseQueryOptions = MongooseQueryOptions; - type MongooseUpdateQueryOptions = MongooseQueryOptions; + type MongooseUpdateQueryOptions = MongooseQueryOptions< + DocType, + MongooseBaseQueryOptionKeys | "timestamps" + >; type ProjectionFields = { [Key in keyof DocType]?: any } & Record; From 4df404b1f4d143b79fc3eb40751b7f7525b1e27b Mon Sep 17 00:00:00 2001 From: Sean Derrow Date: Mon, 26 Feb 2024 02:02:16 -0500 Subject: [PATCH 24/89] More lint --- types/query.d.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/types/query.d.ts b/types/query.d.ts index f450182ffdb..6d53f4f473e 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -18,21 +18,21 @@ declare module 'mongoose' { type FilterQuery = _FilterQuery; type MongooseBaseQueryOptionKeys = - | "context" - | "multipleCastError" - | "overwriteDiscriminatorKey" - | "populate" - | "runValidators" - | "sanitizeProjection" - | "sanitizeFilter" - | "setDefaultsOnInsert" - | "strict" - | "strictQuery" - | "translateAliases"; + | 'context' + | 'multipleCastError' + | 'overwriteDiscriminatorKey' + | 'populate' + | 'runValidators' + | 'sanitizeProjection' + | 'sanitizeFilter' + | 'setDefaultsOnInsert' + | 'strict' + | 'strictQuery' + | 'translateAliases'; type MongooseQueryOptions< DocType = unknown, - Keys extends keyof QueryOptions = MongooseBaseQueryOptionKeys | "timestamps" | "lean" + Keys extends keyof QueryOptions = MongooseBaseQueryOptionKeys | 'timestamps' | 'lean' > = Pick, Keys> & { [other: string]: any; }; @@ -41,7 +41,7 @@ declare module 'mongoose' { type MongooseUpdateQueryOptions = MongooseQueryOptions< DocType, - MongooseBaseQueryOptionKeys | "timestamps" + MongooseBaseQueryOptionKeys | 'timestamps' >; type ProjectionFields = { [Key in keyof DocType]?: any } & Record; From cf4f851489a4f741f77b1469f6eef2f143102f79 Mon Sep 17 00:00:00 2001 From: FaizBShah Date: Mon, 26 Feb 2024 19:12:31 +0530 Subject: [PATCH 25/89] fix: missing typescript details on options params of updateMany, updateOne, etc. --- types/models.d.ts | 10 +++++----- types/utility.d.ts | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/types/models.d.ts b/types/models.d.ts index a77aa25525e..b232620e1f9 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -228,7 +228,7 @@ declare module 'mongoose' { /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ countDocuments( filter?: FilterQuery, - options?: (mongodb.CountOptions & Omit, 'lean' | 'timestamps'>) | null + options?: (mongodb.CountOptions & ExcludeKeys, 'lean' | 'timestamps'>) | null ): QueryWithHelpers< number, THydratedDocumentType, @@ -266,7 +266,7 @@ declare module 'mongoose' { */ deleteMany( filter?: FilterQuery, - options?: (mongodb.DeleteOptions & Omit, 'lean' | 'timestamps'>) | null + options?: (mongodb.DeleteOptions & ExcludeKeys, 'lean' | 'timestamps'>) | null ): QueryWithHelpers< mongodb.DeleteResult, THydratedDocumentType, @@ -291,7 +291,7 @@ declare module 'mongoose' { */ deleteOne( filter?: FilterQuery, - options?: (mongodb.DeleteOptions & Omit, 'lean' | 'timestamps'>) | null + options?: (mongodb.DeleteOptions & ExcludeKeys, 'lean' | 'timestamps'>) | null ): QueryWithHelpers< mongodb.DeleteResult, THydratedDocumentType, @@ -743,14 +743,14 @@ declare module 'mongoose' { updateMany( filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: (mongodb.UpdateOptions & Omit, 'lean'>) | null + options?: (mongodb.UpdateOptions & ExcludeKeys, 'lean'>) | null ): QueryWithHelpers; /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ updateOne( filter?: FilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: (mongodb.UpdateOptions & Omit, 'lean'>) | null + options?: (mongodb.UpdateOptions & ExcludeKeys, 'lean'>) | null ): QueryWithHelpers; /** Creates a Query, applies the passed conditions, and returns the Query. */ diff --git a/types/utility.d.ts b/types/utility.d.ts index 60005c2e8b7..016f2c48b07 100644 --- a/types/utility.d.ts +++ b/types/utility.d.ts @@ -2,6 +2,17 @@ declare module 'mongoose' { type IfAny = 0 extends (1 & IFTYPE) ? THENTYPE : ELSETYPE; type IfUnknown = unknown extends IFTYPE ? THENTYPE : IFTYPE; + /** + * @summary Removes keys from a type + * @description It helps to exclude keys from a type + * @param {T} T A generic type to be checked. + * @param {K} K Keys from T that are to be excluded from the generic type + * @returns T with the keys in K excluded + */ + type ExcludeKeys = { + [P in keyof T as P extends K ? never : P]: T[P]; + }; + type Unpacked = T extends (infer U)[] ? U : T extends ReadonlyArray ? U : T; From 635c79510160fcf4aab3fd5212bb80f2d649f9d8 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Feb 2024 10:59:35 -0500 Subject: [PATCH 26/89] perf(document+schema): small optimizations to make `init()` faster Re: #14113 --- lib/document.js | 25 +++++++++++++------------ lib/schema.js | 3 +++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/document.js b/lib/document.js index fe636a4c351..757f5101c32 100644 --- a/lib/document.js +++ b/lib/document.js @@ -741,7 +741,7 @@ function init(self, obj, doc, opts, prefix) { if (i === '__proto__' || i === 'constructor') { return; } - path = prefix + i; + path = prefix ? prefix + i : i; schemaType = docSchema.path(path); // Should still work if not a model-level discriminator, but should not be @@ -751,7 +751,8 @@ function init(self, obj, doc, opts, prefix) { return; } - if (!schemaType && utils.isPOJO(obj[i])) { + const value = obj[i]; + if (!schemaType && utils.isPOJO(value)) { // assume nested object if (!doc[i]) { doc[i] = {}; @@ -759,30 +760,30 @@ function init(self, obj, doc, opts, prefix) { self[i] = doc[i]; } } - init(self, obj[i], doc[i], opts, path + '.'); + init(self, value, doc[i], opts, path + '.'); } else if (!schemaType) { - doc[i] = obj[i]; + doc[i] = value; if (!strict && !prefix) { - self[i] = obj[i]; + self[i] = value; } } else { // Retain order when overwriting defaults - if (doc.hasOwnProperty(i) && obj[i] !== void 0) { + if (doc.hasOwnProperty(i) && value !== void 0) { delete doc[i]; } - if (obj[i] === null) { + if (value === null) { doc[i] = schemaType._castNullish(null); - } else if (obj[i] !== undefined) { - const wasPopulated = obj[i].$__ == null ? null : obj[i].$__.wasPopulated; + } else if (value !== undefined) { + const wasPopulated = value.$__ == null ? null : value.$__.wasPopulated; if (schemaType && !wasPopulated) { try { if (opts && opts.setters) { // Call applySetters with `init = false` because otherwise setters are a noop const overrideInit = false; - doc[i] = schemaType.applySetters(obj[i], self, overrideInit); + doc[i] = schemaType.applySetters(value, self, overrideInit); } else { - doc[i] = schemaType.cast(obj[i], self, true); + doc[i] = schemaType.cast(value, self, true); } } catch (e) { self.invalidate(e.path, new ValidatorError({ @@ -794,7 +795,7 @@ function init(self, obj, doc, opts, prefix) { })); } } else { - doc[i] = obj[i]; + doc[i] = value; } } // mark as hydrated diff --git a/lib/schema.js b/lib/schema.js index 88e427d417d..dc0ebbd5003 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -955,6 +955,9 @@ reserved.collection = 1; Schema.prototype.path = function(path, obj) { if (obj === undefined) { + if (this.paths[path] != null) { + return this.paths[path]; + } // Convert to '.$' to check subpaths re: gh-6405 const cleanPath = _pathToPositionalSyntax(path); let schematype = _getPath(this, path, cleanPath); From 00314a0c52f44e0417942a526d02840cc4166313 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Feb 2024 11:49:58 -0500 Subject: [PATCH 27/89] types(querycursor): correct cursor async iterator type with `populate()` support Fix #14374 --- test/types/querycursor.test.ts | 27 +++++++++++++++++++++++++++ types/query.d.ts | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/test/types/querycursor.test.ts b/test/types/querycursor.test.ts index dc87e0a669b..f88d46ff57b 100644 --- a/test/types/querycursor.test.ts +++ b/test/types/querycursor.test.ts @@ -20,3 +20,30 @@ Test.find().cursor(). expectType(i); }). then(() => console.log('Done!')); + +async function gh14374() { + // `Parent` represents the object as it is stored in MongoDB + interface Parent { + child?: Types.ObjectId + name?: string + } + const ParentModel = model( + 'Parent', + new Schema({ + child: { type: Schema.Types.ObjectId, ref: 'Child' }, + name: String + }) + ) + + interface Child { + name: string + } + const childSchema: Schema = new Schema({ name: String }); + + // This does not + const cursor = ParentModel.find({}).populate<{ child: Child }>('child').cursor() + for await (const doc of cursor) { + const _t: string = doc.child.name; + } + +} \ No newline at end of file diff --git a/types/query.d.ts b/types/query.d.ts index 0bdda904350..168324d7d4e 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -208,7 +208,7 @@ declare module 'mongoose' { * A QueryCursor exposes a Streams3 interface, as well as a `.next()` function. * This is equivalent to calling `.cursor()` with no arguments. */ - [Symbol.asyncIterator](): AsyncIterableIterator; + [Symbol.asyncIterator](): AsyncIterableIterator>; /** Executes the query */ exec(): Promise; @@ -286,7 +286,7 @@ declare module 'mongoose' { * Returns a wrapper around a [mongodb driver cursor](https://mongodb.github.io/node-mongodb-native/4.9/classes/FindCursor.html). * A QueryCursor exposes a Streams3 interface, as well as a `.next()` function. */ - cursor(options?: QueryOptions): Cursor>; + cursor(options?: QueryOptions): Cursor, QueryOptions>; /** * Declare and/or execute this query as a `deleteMany()` operation. Works like From e0b556f447d93fdcff9a50cc9f0cb21a33da0e14 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Feb 2024 11:59:52 -0500 Subject: [PATCH 28/89] style: fix lint --- test/types/querycursor.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/types/querycursor.test.ts b/test/types/querycursor.test.ts index f88d46ff57b..fb0dc301b02 100644 --- a/test/types/querycursor.test.ts +++ b/test/types/querycursor.test.ts @@ -30,10 +30,10 @@ async function gh14374() { const ParentModel = model( 'Parent', new Schema({ - child: { type: Schema.Types.ObjectId, ref: 'Child' }, - name: String + child: { type: Schema.Types.ObjectId, ref: 'Child' }, + name: String }) - ) + ); interface Child { name: string @@ -41,9 +41,9 @@ async function gh14374() { const childSchema: Schema = new Schema({ name: String }); // This does not - const cursor = ParentModel.find({}).populate<{ child: Child }>('child').cursor() + const cursor = ParentModel.find({}).populate<{ child: Child }>('child').cursor(); for await (const doc of cursor) { - const _t: string = doc.child.name; + const _t: string = doc.child.name; } -} \ No newline at end of file +} From 834c29f0cb73b8e4e4cfaf5a273831d0f135c15f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Feb 2024 15:11:58 -0500 Subject: [PATCH 29/89] chore: release 7.6.9 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05ac8299cc9..4639ed8d7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +7.6.9 / 2024-02-26 +================== + * fix(document): handle embedded recursive discriminators on nested path defined using Schema.prototype.discriminator #14256 #14245 + * types(model): correct return type for findByIdAndDelete() #14233 #14190 + * docs(connections): add note about using asPromise() with createConnection() for error handling #14364 #14266 + * docs(model+query+findoneandupdate): add more details about overwriteDiscriminatorKey option to docs #14264 #14246 + 7.6.8 / 2024-01-08 ================== * perf(schema): remove unnecessary lookahead in numeric subpath check diff --git a/package.json b/package.json index 3f6a5beb0a8..d36925cae48 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "7.6.8", + "version": "7.6.9", "author": "Guillermo Rauch ", "keywords": [ "mongodb", From a7f108599b3e356da40a641aa0b0444473ae4798 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 26 Feb 2024 16:01:33 -0500 Subject: [PATCH 30/89] Update models.d.ts --- types/models.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/models.d.ts b/types/models.d.ts index 5ac31d24d2a..2a1b73aa05a 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -291,7 +291,7 @@ declare module 'mongoose' { */ deleteOne( filter?: FilterQuery, - options?: (mongodb.DeleteOptions & ExcludeKeys, 'lean' | 'timestamps'>) | null + options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< mongodb.DeleteResult, THydratedDocumentType, From a9f661436d6f62bc6fc5ff282179efd7e4daefea Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Feb 2024 16:19:24 -0500 Subject: [PATCH 31/89] test: try fixing tests --- test/connection.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/connection.test.js b/test/connection.test.js index 36290280b47..111432160a6 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -1542,7 +1542,7 @@ describe('connections:', function() { it('should not throw an error when attempting to mutate unmutable options object gh-13335', async function() { const m = new mongoose.Mongoose(); const opts = Object.preventExtensions({}); - const conn = await m.connect('mongodb://127.0.0.1:27017/db?retryWrites=true&w=majority&readPreference=secondaryPreferred', opts); + const conn = await m.connect('mongodb://127.0.0.1:27017/db?retryWrites=true&w=majority&readPreference=primaryPreferred', opts); assert.ok(conn); }); }); From 867f7b75cb9b31731ab31d477d7ca67201303d32 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Feb 2024 16:24:41 -0500 Subject: [PATCH 32/89] test: fix #13335 tests --- test/connection.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/connection.test.js b/test/connection.test.js index 111432160a6..3d65b001870 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -1542,7 +1542,12 @@ describe('connections:', function() { it('should not throw an error when attempting to mutate unmutable options object gh-13335', async function() { const m = new mongoose.Mongoose(); const opts = Object.preventExtensions({}); - const conn = await m.connect('mongodb://127.0.0.1:27017/db?retryWrites=true&w=majority&readPreference=primaryPreferred', opts); + + const uri = start.uri.lastIndexOf('?') === -1 ? + start.uri + '?retryWrites=true&w=majority&readPreference=primaryPreferred' : + start.uri.slice(0, start.uri.lastIndexOf('?')) + '?retryWrites=true&w=majority&readPreference=primaryPreferred'; + + const conn = await m.connect(uri, opts); assert.ok(conn); }); }); From 5096630bc236b1406d0f427c02aa1498f5850796 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Feb 2024 16:27:30 -0500 Subject: [PATCH 33/89] style: fix lint --- test/connection.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/connection.test.js b/test/connection.test.js index 3d65b001870..446f4b0ccff 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -1546,7 +1546,7 @@ describe('connections:', function() { const uri = start.uri.lastIndexOf('?') === -1 ? start.uri + '?retryWrites=true&w=majority&readPreference=primaryPreferred' : start.uri.slice(0, start.uri.lastIndexOf('?')) + '?retryWrites=true&w=majority&readPreference=primaryPreferred'; - + const conn = await m.connect(uri, opts); assert.ok(conn); }); From 2fbeb14c573fd44f925a566b0518c2b48e402b18 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Feb 2024 16:28:11 -0500 Subject: [PATCH 34/89] Update test/types/querycursor.test.ts Co-authored-by: hasezoey --- test/types/querycursor.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/types/querycursor.test.ts b/test/types/querycursor.test.ts index fb0dc301b02..d6cec2b43b5 100644 --- a/test/types/querycursor.test.ts +++ b/test/types/querycursor.test.ts @@ -40,7 +40,6 @@ async function gh14374() { } const childSchema: Schema = new Schema({ name: String }); - // This does not const cursor = ParentModel.find({}).populate<{ child: Child }>('child').cursor(); for await (const doc of cursor) { const _t: string = doc.child.name; From d0e970f4eb7f4fc45bbeaa42d866ea8e6c258eba Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 27 Feb 2024 16:37:18 -0500 Subject: [PATCH 35/89] test: add more rigorous type check for #14374 --- test/types/querycursor.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/types/querycursor.test.ts b/test/types/querycursor.test.ts index fb0dc301b02..dd9cee3c962 100644 --- a/test/types/querycursor.test.ts +++ b/test/types/querycursor.test.ts @@ -43,7 +43,7 @@ async function gh14374() { // This does not const cursor = ParentModel.find({}).populate<{ child: Child }>('child').cursor(); for await (const doc of cursor) { - const _t: string = doc.child.name; + expectType(doc.child); } } From e6f6d4475cc3ec76aa7da990e5ba4f3a8a4fc298 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 28 Feb 2024 14:49:22 -0500 Subject: [PATCH 36/89] fix(connection): avoid unhandled error on createConnection() if on('error') handler registered Fix #14377 --- lib/connection.js | 24 ++++++++++++++++++++++++ test/connection.test.js | 15 +++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/connection.js b/lib/connection.js index 542bdf3c572..05ff52461b0 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -829,6 +829,30 @@ Connection.prototype.openUri = async function openUri(uri, options) { return this; }; +/*! + * Treat `on('error')` handlers as handling the initialConnection promise + * to avoid uncaught exceptions when using `on('error')`. See gh-14377. + */ + +Connection.prototype.on = function on(event, callback) { + if (event === 'error' && this.$initialConnection) { + this.$initialConnection.catch(() => {}); + } + return EventEmitter.prototype.on.call(this, event, callback); +}; + +/*! + * Treat `once('error')` handlers as handling the initialConnection promise + * to avoid uncaught exceptions when using `on('error')`. See gh-14377. + */ + +Connection.prototype.once = function on(event, callback) { + if (event === 'error' && this.$initialConnection) { + this.$initialConnection.catch(() => {}); + } + return EventEmitter.prototype.once.call(this, event, callback); +}; + /*! * ignore */ diff --git a/test/connection.test.js b/test/connection.test.js index 9ea81e356d0..3d1170838cf 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -916,6 +916,21 @@ describe('connections:', function() { assert.equal(err.name, 'MongooseServerSelectionError'); }); + it('avoids unhandled error on createConnection() if error handler registered (gh-14377)', async function() { + const opts = { + serverSelectionTimeoutMS: 100 + }; + const uri = 'mongodb://baddomain:27017/test'; + + const conn = mongoose.createConnection(uri, opts); + await new Promise(resolve => { + conn.on('error', err => { + assert.equal(err.name, 'MongoServerSelectionError'); + resolve(); + }); + }); + }); + it('`watch()` on a whole collection (gh-8425)', async function() { this.timeout(10000); if (!process.env.REPLICA_SET) { From 222ad3b2f89f3957f9b87c057f974ea41d7c4da1 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 28 Feb 2024 15:43:02 -0500 Subject: [PATCH 37/89] chore: pin tmp@0.2.1 re: raszi/node-tmp#293 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9cb365a38b0..fa048ad6cb2 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "q": "1.5.1", "sinon": "15.0.1", "stream-browserify": "3.0.0", + "tmp": "0.2.1", "tsd": "0.25.0", "typescript": "4.9.5", "uuid": "9.0.0", From cf4c4f422360b48c9c345e913173a0b957e37b25 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 28 Feb 2024 16:24:29 -0500 Subject: [PATCH 38/89] fix(schema): avoid applying default write concern to operations that are in a transaction Fix #11382 --- lib/helpers/schema/applyWriteConcern.js | 6 ++++++ test/docs/transactions.test.js | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/helpers/schema/applyWriteConcern.js b/lib/helpers/schema/applyWriteConcern.js index fcce08f8410..27098110872 100644 --- a/lib/helpers/schema/applyWriteConcern.js +++ b/lib/helpers/schema/applyWriteConcern.js @@ -6,6 +6,12 @@ module.exports = function applyWriteConcern(schema, options) { if (options.writeConcern != null) { return; } + // Don't apply default write concern to operations in transactions, + // because setting write concern on an operation in a transaction is an error + // See: https://www.mongodb.com/docs/manual/reference/write-concern/ + if (options && options.session && options.session.transaction) { + return; + } const writeConcern = get(schema, 'options.writeConcern', {}); if (Object.keys(writeConcern).length != 0) { options.writeConcern = {}; diff --git a/test/docs/transactions.test.js b/test/docs/transactions.test.js index 5d0277a5aa4..8b883e3388c 100644 --- a/test/docs/transactions.test.js +++ b/test/docs/transactions.test.js @@ -477,4 +477,20 @@ describe('transactions', function() { assert.equal(i, 3); }); + + it('doesnt apply schema write concern to transaction operations (gh-11382)', async function() { + db.deleteModel(/Test/); + const Test = db.model('Test', Schema({ status: String }, { writeConcern: { w: 'majority' } })); + + await Test.createCollection(); + await Test.deleteMany({}); + + const session = await db.startSession(); + + await session.withTransaction(async function() { + await Test.findOneAndUpdate({}, { name: 'test' }, { session }); + }); + + await session.endSession(); + }); }); From 4ee3d277013642af9d4e4026eff709ff6f961d17 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 29 Feb 2024 15:01:22 -0500 Subject: [PATCH 39/89] fix(document): make `$clone` avoid converting subdocs into POJOs Re: #14353 --- lib/document.js | 2 +- lib/helpers/clone.js | 11 +++++++++++ test/document.test.js | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/document.js b/lib/document.js index 4127102f0c9..78b6f58eff2 100644 --- a/lib/document.js +++ b/lib/document.js @@ -4735,7 +4735,7 @@ Document.prototype.$clone = function() { const clonedDoc = new Model(); clonedDoc.$isNew = this.$isNew; if (this._doc) { - clonedDoc._doc = clone(this._doc); + clonedDoc._doc = clone(this._doc, { retainDocuments: true }); } if (this.$__) { const Cache = this.$__.constructor; diff --git a/lib/helpers/clone.js b/lib/helpers/clone.js index b83858acdf7..b4469110717 100644 --- a/lib/helpers/clone.js +++ b/lib/helpers/clone.js @@ -41,6 +41,17 @@ function clone(obj, options, isArrayChild) { if (options && options._skipSingleNestedGetters && obj.$isSingleNested) { options = Object.assign({}, options, { getters: false }); } + if (options != null && options.retainDocuments != null && obj.$__ != null) { + const clonedDoc = obj.$clone(); + if (obj.__index != null) { + clonedDoc.__index = obj.__index; + } + if (obj.__parentArray != null) { + clonedDoc.__parentArray = obj.__parentArray; + } + clonedDoc.$__parent = obj.$__parent; + return clonedDoc; + } const isSingleNested = obj.$isSingleNested; if (isPOJO(obj) && obj.$__ != null && obj._doc != null) { diff --git a/test/document.test.js b/test/document.test.js index 9999d18564b..f6f1d0797b3 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -12070,6 +12070,27 @@ describe('document', function() { assert.strictEqual(clonedDoc.$session(), session); }); + it('$clone() with single nested and doc array (gh-14353) (gh-11849)', async function() { + const schema = new mongoose.Schema({ + subdocArray: [{ + name: String + }], + subdoc: new mongoose.Schema({ name: String }) + }); + const Test = db.model('Test', schema); + + const item = await Test.create({ subdocArray: [{ name: 'test 1' }], subdoc: { name: 'test 2' } }); + + const doc = await Test.findById(item._id); + const clonedDoc = doc.$clone(); + + assert.ok(clonedDoc.subdocArray[0].$__); + assert.ok(clonedDoc.subdoc.$__); + + assert.deepEqual(doc.subdocArray[0], clonedDoc.subdocArray[0]); + assert.deepEqual(doc.subdoc, clonedDoc.subdoc); + }); + it('can create document with document array and top-level key named `schema` (gh-12480)', async function() { const AuthorSchema = new Schema({ fullName: { type: 'String', required: true } From c98175bdaa22d8475715dc22d5cc2505bd0edbcc Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 29 Feb 2024 15:16:24 -0500 Subject: [PATCH 40/89] test: fix lean test for bson 6.4 --- test/docs/lean.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/docs/lean.test.js b/test/docs/lean.test.js index 4512c7ced7f..49e050f1367 100644 --- a/test/docs/lean.test.js +++ b/test/docs/lean.test.js @@ -42,7 +42,7 @@ describe('Lean Tutorial', function() { const leanDoc = await MyModel.findOne().lean(); v8Serialize(normalDoc).length; // approximately 180 - v8Serialize(leanDoc).length; // 32, about 5x smaller! + v8Serialize(leanDoc).length; // approximately 55, about 3x smaller! // In case you were wondering, the JSON form of a Mongoose doc is the same // as the POJO. This additional memory only affects how much memory your @@ -50,7 +50,7 @@ describe('Lean Tutorial', function() { JSON.stringify(normalDoc).length === JSON.stringify(leanDoc).length; // true // acquit:ignore:start assert.ok(v8Serialize(normalDoc).length >= 150 && v8Serialize(normalDoc).length <= 200, v8Serialize(normalDoc).length); - assert.equal(v8Serialize(leanDoc).length, 32); + assert.equal(v8Serialize(leanDoc).length, 55); assert.equal(JSON.stringify(normalDoc).length, JSON.stringify(leanDoc).length); // acquit:ignore:end }); From bce8bde857db7e6b9919d5513391c0db1d153a07 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 29 Feb 2024 15:19:22 -0500 Subject: [PATCH 41/89] test: make tests succeed on bson@6.3 or bson@6.4 --- test/docs/lean.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/docs/lean.test.js b/test/docs/lean.test.js index 49e050f1367..e571987864b 100644 --- a/test/docs/lean.test.js +++ b/test/docs/lean.test.js @@ -50,7 +50,7 @@ describe('Lean Tutorial', function() { JSON.stringify(normalDoc).length === JSON.stringify(leanDoc).length; // true // acquit:ignore:start assert.ok(v8Serialize(normalDoc).length >= 150 && v8Serialize(normalDoc).length <= 200, v8Serialize(normalDoc).length); - assert.equal(v8Serialize(leanDoc).length, 55); + assert.ok(v8Serialize(leanDoc).length === 55 || v8Serialize(leanDoc).length === 32, v8Serialize(leanDoc).length); assert.equal(JSON.stringify(normalDoc).length, JSON.stringify(leanDoc).length); // acquit:ignore:end }); From 8656ede206048bcf439caee57477a6726fd1a240 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 1 Mar 2024 11:18:43 -0500 Subject: [PATCH 42/89] fix: address code review comments on #14395 --- lib/helpers/clone.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/helpers/clone.js b/lib/helpers/clone.js index b4469110717..a7b5f2f2fe5 100644 --- a/lib/helpers/clone.js +++ b/lib/helpers/clone.js @@ -36,21 +36,23 @@ function clone(obj, options, isArrayChild) { } if (isMongooseObject(obj)) { - // Single nested subdocs should apply getters later in `applyGetters()` - // when calling `toObject()`. See gh-7442, gh-8295 - if (options && options._skipSingleNestedGetters && obj.$isSingleNested) { - options = Object.assign({}, options, { getters: false }); - } - if (options != null && options.retainDocuments != null && obj.$__ != null) { - const clonedDoc = obj.$clone(); - if (obj.__index != null) { - clonedDoc.__index = obj.__index; + if (options) { + // Single nested subdocs should apply getters later in `applyGetters()` + // when calling `toObject()`. See gh-7442, gh-8295 + if (options._skipSingleNestedGetters && obj.$isSingleNested) { + options = Object.assign({}, options, { getters: false }); } - if (obj.__parentArray != null) { - clonedDoc.__parentArray = obj.__parentArray; + if (options.retainDocuments && obj.$__ != null) { + const clonedDoc = obj.$clone(); + if (obj.__index != null) { + clonedDoc.__index = obj.__index; + } + if (obj.__parentArray != null) { + clonedDoc.__parentArray = obj.__parentArray; + } + clonedDoc.$__parent = obj.$__parent; + return clonedDoc; } - clonedDoc.$__parent = obj.$__parent; - return clonedDoc; } const isSingleNested = obj.$isSingleNested; From 29f57c12caee006f2434a0ef9b21aace9b30fdcf Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 1 Mar 2024 13:34:32 -0500 Subject: [PATCH 43/89] chore: release 6.12.7 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a112597b79..e1f7bb87b8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +6.12.7 / 2024-03-01 +=================== + * perf(model): make insertMany() lean option skip hydrating Mongoose docs #14376 #14372 + * perf(document+schema): small optimizations to make init() faster #14383 #14113 + * fix(connection): don't modify passed options object to `openUri()` #14370 #13376 #13335 + * fix(ChangeStream): bubble up resumeTokenChanged changeStream event #14355 #14349 [3150](https://github.com/3150) + 6.12.6 / 2024-01-22 =================== * fix(collection): correctly handle buffer timeouts with find() #14277 diff --git a/package.json b/package.json index fa048ad6cb2..9f3a3889566 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "6.12.6", + "version": "6.12.7", "author": "Guillermo Rauch ", "keywords": [ "mongodb", From 0d8189a663acdbdf9cab077da8989a713f657ee5 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 1 Mar 2024 15:59:18 -0500 Subject: [PATCH 44/89] types: use Kareem exported OverwriteMiddlewareResult and SkipWrappedFunction --- package.json | 2 +- types/index.d.ts | 7 +++---- types/middlewares.d.ts | 9 +++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index b20802b5676..7df083cf3f5 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "license": "MIT", "dependencies": { "bson": "^6.2.0", - "kareem": "2.5.1", + "kareem": "git@github.com:mongoosejs/kareem.git#vkarpov15/typescript", "mongodb": "6.3.0", "mpath": "0.9.0", "mquery": "5.0.0", diff --git a/types/index.d.ts b/types/index.d.ts index 3c94c7f94cf..6adb78c96a9 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -27,6 +27,7 @@ declare class NativeDate extends global.Date { } declare module 'mongoose' { + import Kareem = require('kareem'); import events = require('events'); import mongodb = require('mongodb'); import mongoose = require('mongoose'); @@ -677,11 +678,9 @@ declare module 'mongoose' { /* for ts-mongoose */ export class mquery { } - export class OverwriteMiddlewareResult {} - export function overwriteMiddlewareResult(val: any): OverwriteMiddlewareResult; + export function overwriteMiddlewareResult(val: any): Kareem.OverwriteMiddlewareResult; - export class SkipWrappedFunction {} - export function skipMiddlewareFunction(val: any): SkipWrappedFunction; + export function skipMiddlewareFunction(val: any): Kareem.SkipWrappedFunction; export default mongoose; } diff --git a/types/middlewares.d.ts b/types/middlewares.d.ts index af928ff6a76..8d380ed69d1 100644 --- a/types/middlewares.d.ts +++ b/types/middlewares.d.ts @@ -1,4 +1,5 @@ declare module 'mongoose' { + import Kareem = require('kareem'); type MongooseQueryAndDocumentMiddleware = 'updateOne' | 'deleteOne'; @@ -37,13 +38,13 @@ declare module 'mongoose' { this: ThisType, next: CallbackWithoutResultAndOptionalError, opts?: Record - ) => void | Promise | SkipWrappedFunction; + ) => void | Promise | Kareem.SkipWrappedFunction; type PreSaveMiddlewareFunction = ( this: ThisType, next: CallbackWithoutResultAndOptionalError, opts: SaveOptions - ) => void | Promise | SkipWrappedFunction; - type PostMiddlewareFunction = (this: ThisType, res: ResType, next: CallbackWithoutResultAndOptionalError) => void | Promise | OverwriteMiddlewareResult; + ) => void | Promise | Kareem.SkipWrappedFunction; + type PostMiddlewareFunction = (this: ThisType, res: ResType, next: CallbackWithoutResultAndOptionalError) => void | Promise | Kareem.OverwriteMiddlewareResult; type ErrorHandlingMiddlewareFunction = (this: ThisType, err: NativeError, res: ResType, next: CallbackWithoutResultAndOptionalError) => void; - type ErrorHandlingMiddlewareWithOption = (this: ThisType, err: NativeError, res: ResType | null, next: CallbackWithoutResultAndOptionalError) => void | Promise | OverwriteMiddlewareResult; + type ErrorHandlingMiddlewareWithOption = (this: ThisType, err: NativeError, res: ResType | null, next: CallbackWithoutResultAndOptionalError) => void | Promise | Kareem.OverwriteMiddlewareResult; } From 79bd29875c104c496217f772bbb62901a59dd2a3 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 1 Mar 2024 16:32:44 -0500 Subject: [PATCH 45/89] chore: try alternative approach to install kareem branch --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a26251a2f2c..d06724e5020 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "license": "MIT", "dependencies": { "bson": "^6.2.0", - "kareem": "git@github.com:mongoosejs/kareem.git#vkarpov15/typescript", + "kareem": "https://api.github.com/repos/mongoosejs/kareem/tarball/vkarpov15/typescript", "mongodb": "6.3.0", "mpath": "0.9.0", "mquery": "5.0.0", From 7989218b7b15258c896b769ecb23026c94fff0cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:13:48 +0000 Subject: [PATCH 46/89] chore(deps-dev): bump dotenv from 16.4.1 to 16.4.5 Bumps [dotenv](https://github.com/motdotla/dotenv) from 16.4.1 to 16.4.5. - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/motdotla/dotenv/compare/v16.4.1...v16.4.5) --- updated-dependencies: - dependency-name: dotenv dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19c9cc27aa6..4610c0acdd9 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "buffer": "^5.6.0", "cheerio": "1.0.0-rc.12", "crypto-browserify": "3.12.0", - "dotenv": "16.4.1", + "dotenv": "16.4.5", "dox": "1.0.0", "eslint": "8.56.0", "eslint-plugin-markdown": "^3.0.1", From d7594fbce5582a0a76457deb5431048b80a91bd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:14:26 +0000 Subject: [PATCH 47/89] chore(deps-dev): bump @babel/preset-env from 7.23.9 to 7.24.0 Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.9 to 7.24.0. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.24.0/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19c9cc27aa6..93c905e573c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@babel/core": "7.23.9", - "@babel/preset-env": "7.23.9", + "@babel/preset-env": "7.24.0", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", "acquit": "1.3.0", From 2dfe7f153cdda0d0b06c96c6611c55678b7bd368 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:14:43 +0000 Subject: [PATCH 48/89] chore(deps-dev): bump eslint from 8.56.0 to 8.57.0 Bumps [eslint](https://github.com/eslint/eslint) from 8.56.0 to 8.57.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.56.0...v8.57.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19c9cc27aa6..56366d81119 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "crypto-browserify": "3.12.0", "dotenv": "16.4.1", "dox": "1.0.0", - "eslint": "8.56.0", + "eslint": "8.57.0", "eslint-plugin-markdown": "^3.0.1", "eslint-plugin-mocha-no-only": "1.1.1", "express": "^4.18.1", From 9606003f823667e5d8c71d974e7f143c45c2eb0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:15:24 +0000 Subject: [PATCH 49/89] chore(deps-dev): bump tsd from 0.30.4 to 0.30.7 Bumps [tsd](https://github.com/tsdjs/tsd) from 0.30.4 to 0.30.7. - [Release notes](https://github.com/tsdjs/tsd/releases) - [Commits](https://github.com/tsdjs/tsd/compare/v0.30.4...v0.30.7) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19c9cc27aa6..0921542583f 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "q": "1.5.1", "sinon": "17.0.1", "stream-browserify": "3.0.0", - "tsd": "0.30.4", + "tsd": "0.30.7", "typescript": "5.3.3", "uuid": "9.0.1", "webpack": "5.90.1" From f007581420fbcd0d7a115e1ba6a539d001c49420 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 1 Mar 2024 18:54:23 -0500 Subject: [PATCH 50/89] perf: clean up a couple of unnecessarty pre() definitions --- test/types/middleware.test.ts | 2 +- types/index.d.ts | 29 ----------------------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/test/types/middleware.test.ts b/test/types/middleware.test.ts index 31e210eb26d..e127c3b683b 100644 --- a/test/types/middleware.test.ts +++ b/test/types/middleware.test.ts @@ -72,7 +72,7 @@ schema.pre>('insertMany', function() { return Promise.resolve(); }); -schema.pre>('insertMany', { document: false, query: false }, function() { +schema.pre>('insertMany', function() { console.log(this.name); }); diff --git a/types/index.d.ts b/types/index.d.ts index 6dbd0bc7de3..5e9693f9f83 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -420,16 +420,6 @@ declare module 'mongoose' { options?: InsertManyOptions & { lean?: boolean } ) => void | Promise ): this; - pre( - method: 'insertMany' | RegExp, - options: SchemaPreOptions, - fn: ( - this: T, - next: (err?: CallbackError) => void, - docs: any | Array, - options?: InsertManyOptions & { lean?: boolean } - ) => void | Promise - ): this; /* method bulkWrite */ pre( method: 'bulkWrite' | RegExp, @@ -440,16 +430,6 @@ declare module 'mongoose' { options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions ) => void | Promise ): this; - pre( - method: 'bulkWrite' | RegExp, - options: SchemaPreOptions, - fn: ( - this: T, - next: (err?: CallbackError) => void, - ops: Array & MongooseBulkWritePerWriteOptions>, - options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions - ) => void | Promise - ): this; /* method createCollection */ pre( method: 'createCollection' | RegExp, @@ -459,15 +439,6 @@ declare module 'mongoose' { options?: mongodb.CreateCollectionOptions & Pick ) => void | Promise ): this; - pre( - method: 'createCollection' | RegExp, - options: SchemaPreOptions, - fn: ( - this: T, - next: (err?: CallbackError) => void, - options?: mongodb.CreateCollectionOptions & Pick - ) => void | Promise - ): this; /** Object of currently defined query helpers on this schema. */ query: TQueryHelpers; From 77e91f1cafa054e6a4f9d66ce12b6cfd6e799c1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:57:45 +0000 Subject: [PATCH 51/89] chore(deps): bump actions/setup-node from 4.0.1 to 4.0.2 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8...60edb5dd545a775178f52524783378180af0d1f8) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark.yml | 2 +- .github/workflows/documentation.yml | 4 ++-- .github/workflows/test.yml | 8 ++++---- .github/workflows/tidelift-alignment.yml | 2 +- .github/workflows/tsd.yml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 762b9438818..eb32422b89b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -26,7 +26,7 @@ jobs: with: fetch-depth: 0 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 16 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3f35875a1f1..75f6b49d93a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 16 @@ -52,7 +52,7 @@ jobs: - run: git fetch --depth=1 --tags # download all tags for documentation - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 16 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c3fa33aa5f..9d8858be7e8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 @@ -61,7 +61,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: ${{ matrix.node }} @@ -96,7 +96,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 16 - name: Load MongoDB binary cache @@ -124,7 +124,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 16 - run: npm install diff --git a/.github/workflows/tidelift-alignment.yml b/.github/workflows/tidelift-alignment.yml index 54362980425..e2498c40e08 100644 --- a/.github/workflows/tidelift-alignment.yml +++ b/.github/workflows/tidelift-alignment.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 16 - name: Alignment diff --git a/.github/workflows/tsd.yml b/.github/workflows/tsd.yml index ee33cc847fe..1eb30cf6862 100644 --- a/.github/workflows/tsd.yml +++ b/.github/workflows/tsd.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup node - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 14 From 42bca717afa9a861cd3d36569b07753838790895 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:21:04 +0000 Subject: [PATCH 52/89] chore(deps-dev): bump @babel/core from 7.23.9 to 7.24.0 Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.9 to 7.24.0. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.24.0/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a46772bfa99..50ec8f8905e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "sift": "16.0.1" }, "devDependencies": { - "@babel/core": "7.23.9", + "@babel/core": "7.24.0", "@babel/preset-env": "7.24.0", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", From ead215f973f4f69f099bc00d436181d53646bfda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:30:08 +0000 Subject: [PATCH 53/89] chore(deps-dev): bump mocha from 10.2.0 to 10.3.0 Bumps [mocha](https://github.com/mochajs/mocha) from 10.2.0 to 10.3.0. - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.3.0) --- updated-dependencies: - dependency-name: mocha dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a46772bfa99..038cfcdb619 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "markdownlint-cli2": "^0.12.1", "marked": "4.3.0", "mkdirp": "^3.0.1", - "mocha": "10.2.0", + "mocha": "10.3.0", "moment": "2.x", "mongodb-memory-server": "8.15.1", "ncp": "^2.0.0", From 9c335a8bde9d5cab2515d0cb583b6a12e4e4254f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 4 Mar 2024 12:30:52 -0500 Subject: [PATCH 54/89] perf: remove a couple of unnecessary pre<> signatures to improve TypeScript benchmark perf --- scripts/tsc-diagnostics-check.js | 2 +- types/index.d.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/tsc-diagnostics-check.js b/scripts/tsc-diagnostics-check.js index b00bcbb2438..2f74bf39b92 100644 --- a/scripts/tsc-diagnostics-check.js +++ b/scripts/tsc-diagnostics-check.js @@ -3,7 +3,7 @@ const fs = require('fs'); const stdin = fs.readFileSync(0).toString('utf8'); -const maxInstantiations = isNaN(process.argv[2]) ? 120000 : parseInt(process.argv[2], 10); +const maxInstantiations = isNaN(process.argv[2]) ? 125000 : parseInt(process.argv[2], 10); console.log(stdin); diff --git a/types/index.d.ts b/types/index.d.ts index 5e9693f9f83..92a05249e12 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -391,7 +391,6 @@ declare module 'mongoose' { ): this; // this = Document pre(method: 'save', fn: PreSaveMiddlewareFunction): this; - pre(method: 'save', options: SchemaPreOptions, fn: PreSaveMiddlewareFunction): this; pre(method: MongooseDistinctDocumentMiddleware|MongooseDistinctDocumentMiddleware[], fn: PreMiddlewareFunction): this; pre(method: MongooseDistinctDocumentMiddleware|MongooseDistinctDocumentMiddleware[], options: SchemaPreOptions, fn: PreMiddlewareFunction): this; pre( @@ -409,7 +408,6 @@ declare module 'mongoose' { pre>(method: MongooseQueryOrDocumentMiddleware | MongooseQueryOrDocumentMiddleware[] | RegExp, fn: PreMiddlewareFunction): this; // method aggregate pre>(method: 'aggregate' | RegExp, fn: PreMiddlewareFunction): this; - pre>(method: 'aggregate' | RegExp, options: SchemaPreOptions, fn: PreMiddlewareFunction): this; /* method insertMany */ pre( method: 'insertMany' | RegExp, From 35aae256eb7389a483d1317b324efa35f2c174e5 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 4 Mar 2024 15:34:09 -0500 Subject: [PATCH 55/89] feat: upgrade kareem to 2.6.0 for TypeScript types --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d06724e5020..39be888974a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "license": "MIT", "dependencies": { "bson": "^6.2.0", - "kareem": "https://api.github.com/repos/mongoosejs/kareem/tarball/vkarpov15/typescript", + "kareem": "2.6.0", "mongodb": "6.3.0", "mpath": "0.9.0", "mquery": "5.0.0", From 6854607fff56b742de278aadab406a6f838a6d03 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 4 Mar 2024 16:43:52 -0500 Subject: [PATCH 56/89] test: bump TypeScript instantiations cap to fix tests until we release #14328 --- scripts/tsc-diagnostics-check.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tsc-diagnostics-check.js b/scripts/tsc-diagnostics-check.js index b00bcbb2438..2810fe07998 100644 --- a/scripts/tsc-diagnostics-check.js +++ b/scripts/tsc-diagnostics-check.js @@ -3,7 +3,7 @@ const fs = require('fs'); const stdin = fs.readFileSync(0).toString('utf8'); -const maxInstantiations = isNaN(process.argv[2]) ? 120000 : parseInt(process.argv[2], 10); +const maxInstantiations = isNaN(process.argv[2]) ? 130000 : parseInt(process.argv[2], 10); console.log(stdin); From 17ce8e2b2b7b8f95ca72308ab2472b954c223767 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 21:56:16 +0000 Subject: [PATCH 57/89] chore(deps-dev): bump webpack from 5.90.1 to 5.90.3 Bumps [webpack](https://github.com/webpack/webpack) from 5.90.1 to 5.90.3. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.90.1...v5.90.3) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa80e14d6d4..b2f5adb4700 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "tsd": "0.30.7", "typescript": "5.3.3", "uuid": "9.0.1", - "webpack": "5.90.1" + "webpack": "5.90.3" }, "directories": { "lib": "./lib/mongoose" From fb1e00b185b90848ece2dc25c168a0c13159c032 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 4 Mar 2024 17:22:50 -0500 Subject: [PATCH 58/89] chore: release 8.2.1 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 254ef6d2699..6a4011591af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +8.2.1 / 2024-03-04 +================== + * fix(document): make $clone avoid converting subdocs into POJOs #14395 #14353 + * fix(connection): avoid unhandled error on createConnection() if on('error') handler registered #14390 #14377 + * fix(schema): avoid applying default write concern to operations that are in a transaction #14391 #11382 + * types(querycursor): correct cursor async iterator type with populate() support #14384 #14374 + * types: missing typescript details on options params of updateMany, updateOne, etc. #14382 #14379 #14378 [FaizBShah](https://github.com/FaizBShah) [sderrow](https://github.com/sderrow) + * types: allow Record as valid query select argument #14371 [sderrow](https://github.com/sderrow) + 7.6.9 / 2024-02-26 ================== * fix(document): handle embedded recursive discriminators on nested path defined using Schema.prototype.discriminator #14256 #14245 diff --git a/package.json b/package.json index 6778f95b514..55adb04c2af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "8.2.0", + "version": "8.2.1", "author": "Guillermo Rauch ", "keywords": [ "mongodb", From b46652fc84e6a5a4150f1bf51eeaa3371f7ae12d Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 5 Mar 2024 18:06:06 -0500 Subject: [PATCH 59/89] docs(model): add extra note about `lean` option for `insertMany()` skipping casting Re: #14376 --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index d6a3026c7fd..8c69d441635 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3372,7 +3372,7 @@ Model.startSession = function() { * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany) * @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`. * @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`. - * @param {Boolean} [options.lean=false] if `true`, skips hydrating and validating the documents. This option is useful if you need the extra performance, but Mongoose won't validate the documents before inserting. + * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast or validate any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](#Model.castObject()). * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory. * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set. * @param {Function} [callback] callback From 918ae174d44b68f2ec7a6ca34346f5cb9b983169 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 7 Mar 2024 10:44:41 -0500 Subject: [PATCH 60/89] style: fix lint --- lib/types/array/methods/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/types/array/methods/index.js b/lib/types/array/methods/index.js index 8276c15d6d1..f192e8c21ea 100644 --- a/lib/types/array/methods/index.js +++ b/lib/types/array/methods/index.js @@ -2,6 +2,7 @@ const Document = require('../../../document'); const ArraySubdocument = require('../../ArraySubdocument'); +const MongooseError = require('../../../error/mongooseError'); const cleanModifiedSubpaths = require('../../../helpers/document/cleanModifiedSubpaths'); const clone = require('../../../helpers/clone'); const internalToObjectOptions = require('../../../options').internalToObjectOptions; From c41ee9a93acbba1f2a9ad35de0d15259933f6afe Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 7 Mar 2024 10:55:26 -0500 Subject: [PATCH 61/89] docs(mongoose): add `options.overwriteModel` details to `mongoose.model()` docs Fix #14387 --- lib/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index 90547f1e95b..641e19fe876 100644 --- a/lib/index.js +++ b/lib/index.js @@ -507,12 +507,14 @@ Mongoose.prototype.pluralize = function(fn) { * * // or * - * const collectionName = 'actor' - * const M = mongoose.model('Actor', schema, collectionName) + * const collectionName = 'actor'; + * const M = mongoose.model('Actor', schema, collectionName); * * @param {String|Function} name model name or class extending Model * @param {Schema} [schema] the schema to use. * @param {String} [collection] name (optional, inferred from model name) + * @param {Object} [options] + * @param {Boolean} [options.overwriteModels=false] If true, overwrite existing models with the same name to avoid `OverwriteModelError` * @return {Model} The model associated with `name`. Mongoose will create the model if it doesn't already exist. * @api public */ From 911bc3324673fe4023cd1c06bb767f0229c15b57 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 7 Mar 2024 12:57:46 -0500 Subject: [PATCH 62/89] types(model): make `bulkWrite()` types more flexible to account for casting Re: #14400 --- test/types/models.test.ts | 16 +++++++ types/models.d.ts | 89 +++++++++++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 8 deletions(-) diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 61298be836d..3e1bd32449f 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -897,6 +897,22 @@ function gh4727() { const company = { _id: new mongoose.Types.ObjectId(), name: 'Booster', users: [users[0]] }; return Company.hydrate(company, {}, { hydratedPopulatedDocs: true }); +} + +async function gh14440() { + const testSchema = new Schema({ + dateProperty: { type: Date } + }); + const TestModel = model('Test', testSchema); + const doc = new TestModel(); + await TestModel.bulkWrite([ + { + updateOne: { + filter: { _id: doc._id }, + update: { dateProperty: (new Date('2023-06-01')).toISOString() } + } + } + ]); } diff --git a/types/models.d.ts b/types/models.d.ts index 2a1b73aa05a..137eeac793e 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -156,6 +156,85 @@ declare module 'mongoose' { const Model: Model; + export type AnyBulkWriteOperation = { + insertOne: InsertOneModel; + } | { + replaceOne: ReplaceOneModel; + } | { + updateOne: UpdateOneModel; + } | { + updateMany: UpdateManyModel; + } | { + deleteOne: DeleteOneModel; + } | { + deleteMany: DeleteManyModel; + }; + + export type InsertOneModel = { + document: mongodb.OptionalId + }; + + export interface ReplaceOneModel { + /** The filter to limit the replaced document. */ + filter: FilterQuery; + /** The document with which to replace the matched document. */ + replacement: mongodb.WithoutId; + /** Specifies a collation. */ + collation?: mongodb.CollationOptions; + /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ + hint?: mongodb.Hint; + /** When true, creates a new document if no document matches the query. */ + upsert?: boolean; + } + + export interface UpdateOneModel { + /** The filter to limit the updated documents. */ + filter: FilterQuery; + /** A document or pipeline containing update operators. */ + update: UpdateQuery; + /** A set of filters specifying to which array elements an update should apply. */ + arrayFilters?: AnyObject[]; + /** Specifies a collation. */ + collation?: mongodb.CollationOptions; + /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ + hint?: mongodb.Hint; + /** When true, creates a new document if no document matches the query. */ + upsert?: boolean; + } + + export interface UpdateManyModel { + /** The filter to limit the updated documents. */ + filter: FilterQuery; + /** A document or pipeline containing update operators. */ + update: UpdateQuery; + /** A set of filters specifying to which array elements an update should apply. */ + arrayFilters?: AnyObject[]; + /** Specifies a collation. */ + collation?: mongodb.CollationOptions; + /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ + hint?: mongodb.Hint; + /** When true, creates a new document if no document matches the query. */ + upsert?: boolean; + } + + export interface DeleteOneModel { + /** The filter to limit the deleted documents. */ + filter: FilterQuery; + /** Specifies a collation. */ + collation?: mongodb.CollationOptions; + /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ + hint?: mongodb.Hint; + } + + export interface DeleteManyModel { + /** The filter to limit the deleted documents. */ + filter: FilterQuery; + /** Specifies a collation. */ + collation?: mongodb.CollationOptions; + /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ + hint?: mongodb.Hint; + } + /** * Models are fancy constructors compiled from `Schema` definitions. * An instance of a model is called a document. @@ -201,17 +280,11 @@ declare module 'mongoose' { * round trip to the MongoDB server. */ bulkWrite( - writes: Array< - mongodb.AnyBulkWriteOperation< - DocContents extends mongodb.Document ? DocContents : any - > & MongooseBulkWritePerWriteOptions>, + writes: Array>, options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions & { ordered: false } ): Promise; bulkWrite( - writes: Array< - mongodb.AnyBulkWriteOperation< - DocContents extends mongodb.Document ? DocContents : any - > & MongooseBulkWritePerWriteOptions>, + writes: Array>, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions ): Promise; From 628595fbfc45b0a10923be7371059b4d3b3d7a2b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 7 Mar 2024 14:24:59 -0500 Subject: [PATCH 63/89] docs(model+query): add notes clarifying that Mongoose does not cast update pipelines Fix #14400 --- lib/model.js | 14 ++++++++++++-- lib/query.js | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/model.js b/lib/model.js index 56d391510a0..491a0953ed3 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3375,6 +3375,7 @@ function _setIsNew(doc, val) { * trip to MongoDB. * * Mongoose will perform casting on all operations you provide. + * The only exception is if you pass [set the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines. * * This function does **not** trigger any middleware, neither `save()`, nor `update()`. * If you need to trigger @@ -3410,6 +3411,15 @@ function _setIsNew(doc, val) { * console.log(res.insertedCount, res.modifiedCount, res.deletedCount); * }); * + * // Mongoose does **not** cast update pipelines, so no casting for the `update` option below. + * // Mongoose does still cast `filter` + * await Character.bulkWrite([{ + * updateOne: { + * filter: { name: 'Annika Hansen' }, + * update: [{ $set: { name: 7 } }] // Array means update pipeline, so Mongoose skips casting + * } + * }]); + * * The [supported operations](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are: * * - `insertOne` @@ -3939,7 +3949,7 @@ Model.hydrate = function(obj, projection, options) { * - `updateMany()` * * @param {Object} filter - * @param {Object|Array} update + * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted. * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document @@ -3979,7 +3989,7 @@ Model.updateMany = function updateMany(conditions, doc, options) { * - `updateOne()` * * @param {Object} filter - * @param {Object|Array} update + * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted. * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions()) * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document diff --git a/lib/query.js b/lib/query.js index 39b5f604d8f..529dc670821 100644 --- a/lib/query.js +++ b/lib/query.js @@ -3880,7 +3880,7 @@ Query.prototype._replaceOne = async function _replaceOne() { * - `updateMany()` * * @param {Object} [filter] - * @param {Object|Array} [update] the update command + * @param {Object|Array} [update] the update command. If array, this update will be treated as an update pipeline and not casted. * @param {Object} [options] * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) @@ -3950,7 +3950,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) { * - `updateOne()` * * @param {Object} [filter] - * @param {Object|Array} [update] the update command + * @param {Object|Array} [update] the update command. If array, this update will be treated as an update pipeline and not casted. * @param {Object} [options] * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict) From 0d41df3f6807cab60952b46bc3ff7a5c686fbf60 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 7 Mar 2024 16:46:56 -0500 Subject: [PATCH 64/89] Update lib/model.js Co-authored-by: hasezoey --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 8c69d441635..8a9e4a3226f 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3372,7 +3372,7 @@ Model.startSession = function() { * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany) * @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`. * @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`. - * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast or validate any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](#Model.castObject()). + * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast or validate any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](#model_Model-castObject). * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory. * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set. * @param {Function} [callback] callback From ffd254e3a2254611583a4b1587d76c9673dbdeb3 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Fri, 8 Mar 2024 13:46:56 +0100 Subject: [PATCH 65/89] style(model): fix link for 7.x documentation and up re 0d41df3f6807cab60952b46bc3ff7a5c686fbf60 --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 7c3572d63f7..7562986aeed 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3058,7 +3058,7 @@ Model.startSession = function() { * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany) * @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`. * @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`. - * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast or validate any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](#model_Model-castObject). + * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast or validate any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](https://mongoosejs.com/docs/api/model.html#Model.castObject()). * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory. * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set. * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully. From a23f8deff9e1e6e15e1b6d491eeace24fbcba319 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Fri, 8 Mar 2024 13:57:30 +0100 Subject: [PATCH 66/89] docs(version-support): change "Mongoose 5" to indicate being EOL --- docs/version-support.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/version-support.md b/docs/version-support.md index 436759db80c..23f0a500fe1 100644 --- a/docs/version-support.md +++ b/docs/version-support.md @@ -22,10 +22,4 @@ Mongoose 6.x will no longer receive any updates, security or otherwise, after th ## Mongoose 5 -Mongoose 5.x (released January 17, 2018) is currently only receiving security fixes and requested bug fixes. -Please open a [bug report on GitHub](https://github.com/Automattic/mongoose/issues/new?assignees=&labels=&template=bug.yml) to request backporting a fix to Mongoose 5. -We will **not** backport any new features from Mongoose 6 or Mongoose 7 into Mongoose 5. -This includes support for newer versions of MongoDB: we do not intend to add support for MongoDB 5 or higher to Mongoose 5.x. - -Mongoose 5.x end of life (EOL) is March 1, 2024. -Mongoose 5.x will no longer receive any updates, security or otherwise, after that date. +Mongoose 5.x (released January 17, 2018) is since March 1, 2024 End-of-Life (EOL), it will no longer receive any updates, security or otherwise. From b8a11ce2e9c5097aae8be14bddf064f84e9b2072 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Fri, 8 Mar 2024 14:00:31 +0100 Subject: [PATCH 67/89] style(version-support): remove "request feature backport" section from "Mongoose 6" --- docs/version-support.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/version-support.md b/docs/version-support.md index 23f0a500fe1..0a794b787f0 100644 --- a/docs/version-support.md +++ b/docs/version-support.md @@ -13,10 +13,6 @@ We ship all new bug fixes and features to 7.x. Mongoose 6.x (released August 24, 2021) is currently only receiving security fixes and requested bug fixes as of August 24, 2023. Please open a [bug report on GitHub](https://github.com/Automattic/mongoose/issues/new?assignees=&labels=&template=bug.yml) to request backporting a fix to Mongoose 6. -We are **not** actively backporting any new features from Mongoose 7 into Mongoose 6. -Until August 24, 2023, we will backport requested features into Mongoose 6; please open a [feature request on GitHub](https://github.com/Automattic/mongoose/issues/new?assignees=&labels=enhancement%2Cnew+feature&template=feature.yml) to request backporting a feature into Mongoose 6. -After August 24, 2023, we will not backport any new features into Mongoose 6. - Mongoose 6.x end of life (EOL) is January 1, 2025. Mongoose 6.x will no longer receive any updates, security or otherwise, after that date. From ef12d16be2f8d72df2da48eec3f5f8685c2ac1e2 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 8 Mar 2024 17:14:28 -0500 Subject: [PATCH 68/89] Update types/models.d.ts Co-authored-by: hasezoey --- types/models.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/models.d.ts b/types/models.d.ts index 137eeac793e..4b2a93b9697 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -170,7 +170,7 @@ declare module 'mongoose' { deleteMany: DeleteManyModel; }; - export type InsertOneModel = { + export interface InsertOneModel { document: mongodb.OptionalId }; From 4b233e7c4b770c6ffcda9c46ee5930bd422a5b18 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 8 Mar 2024 17:16:12 -0500 Subject: [PATCH 69/89] docs(model): small fix for wording --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 491a0953ed3..5f612d3435b 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3375,7 +3375,7 @@ function _setIsNew(doc, val) { * trip to MongoDB. * * Mongoose will perform casting on all operations you provide. - * The only exception is if you pass [set the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines. + * The only exception [setting the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines. * * This function does **not** trigger any middleware, neither `save()`, nor `update()`. * If you need to trigger From 0bca1084542a5f8d5b0bbacd15651d7e8bee4e61 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 8 Mar 2024 17:23:24 -0500 Subject: [PATCH 70/89] Update version-support.md --- docs/version-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/version-support.md b/docs/version-support.md index 0a794b787f0..986b69e6115 100644 --- a/docs/version-support.md +++ b/docs/version-support.md @@ -18,4 +18,4 @@ Mongoose 6.x will no longer receive any updates, security or otherwise, after th ## Mongoose 5 -Mongoose 5.x (released January 17, 2018) is since March 1, 2024 End-of-Life (EOL), it will no longer receive any updates, security or otherwise. +Mongoose 5.x (released January 17, 2018) is End-of-Life (EOL) since March 1, 2024. Mongoose 5.x will no longer receive any updates, security or otherwise. From 425bb853b35efce6c56ca1a878d19a8d9fdf3b50 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 8 Mar 2024 17:36:16 -0500 Subject: [PATCH 71/89] style: fix lint --- types/models.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/models.d.ts b/types/models.d.ts index 4b2a93b9697..7291e5ca79f 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -172,7 +172,7 @@ declare module 'mongoose' { export interface InsertOneModel { document: mongodb.OptionalId - }; + } export interface ReplaceOneModel { /** The filter to limit the replaced document. */ From 0bfffa35ce8e49f03d8d88b9bcae851d7dd659f9 Mon Sep 17 00:00:00 2001 From: jonathan Date: Mon, 11 Mar 2024 00:27:25 +0200 Subject: [PATCH 72/89] Adding the VectorSearch type --- types/pipelinestage.d.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/types/pipelinestage.d.ts b/types/pipelinestage.d.ts index 1e6007cd24e..a48d3d12b0a 100644 --- a/types/pipelinestage.d.ts +++ b/types/pipelinestage.d.ts @@ -36,7 +36,8 @@ declare module 'mongoose' { | PipelineStage.SortByCount | PipelineStage.UnionWith | PipelineStage.Unset - | PipelineStage.Unwind; + | PipelineStage.Unwind + | PipelineStage.VectorSearch; export namespace PipelineStage { export interface AddFields { @@ -308,5 +309,17 @@ declare module 'mongoose' { /** [`$unwind` reference](https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/) */ $unwind: string | { path: string; includeArrayIndex?: string; preserveNullAndEmptyArrays?: boolean } } + export interface VectorSearch { + /** [`$vectorSearch` reference](https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/) */ + $vectorSearch: { + index: string, + path: string, + queryVector: number[], + numCandidates: number, + limit: number, + filter?: Expression, + } + } + } } From 6757c88d4d9ccf0488de41675ce30aed49f76f21 Mon Sep 17 00:00:00 2001 From: jonathan Date: Mon, 11 Mar 2024 00:27:44 +0200 Subject: [PATCH 73/89] Adding test to the VectorSearch aggregation stage --- test/types/PipelineStage.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/types/PipelineStage.test.ts b/test/types/PipelineStage.test.ts index 967cf28b60d..c5f54a0aca0 100644 --- a/test/types/PipelineStage.test.ts +++ b/test/types/PipelineStage.test.ts @@ -520,3 +520,20 @@ function gh12269() { } }; } +const vectorSearchStages: PipelineStage[] = [ + { + $vectorSearch: { + index: 'title_vector_index', + path: 'embedding', + queryVector: [0.522,0.123,0.487], + limit: 5, + numCandidates: 100 + } + }, + { + $project: { + title: 1, + score: { $meta: 'searchScore' } + } + } +]; From 78353960a5f5a69480320e4252d857097bc7b99e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 11 Mar 2024 10:03:15 -0400 Subject: [PATCH 74/89] Update lib/model.js Co-authored-by: hasezoey --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 5f612d3435b..16c7ceb6e07 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3375,7 +3375,7 @@ function _setIsNew(doc, val) { * trip to MongoDB. * * Mongoose will perform casting on all operations you provide. - * The only exception [setting the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines. + * The only exception is [setting the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines. * * This function does **not** trigger any middleware, neither `save()`, nor `update()`. * If you need to trigger From 343593a2c60729396c3a501df1d0a5cda3192a59 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 11 Mar 2024 15:15:22 -0400 Subject: [PATCH 75/89] types: consistently infer array of objects in schema as a DocumentArray Fix #14367 --- test/types/docArray.test.ts | 34 ++++++++++++++++++++++++++++++++++ test/types/schema.test.ts | 8 ++++---- types/inferschematype.d.ts | 2 +- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/test/types/docArray.test.ts b/test/types/docArray.test.ts index c5c9154cde7..cf41df0b82e 100644 --- a/test/types/docArray.test.ts +++ b/test/types/docArray.test.ts @@ -93,3 +93,37 @@ async function gh13424() { const doc = new TestModel(); expectType(doc.subDocArray[0]._id); } + +async function gh14367() { + const UserSchema = new Schema( + { + reminders: { + type: [ + { + type: { type: Schema.Types.String }, + date: { type: Schema.Types.Date }, + toggle: { type: Schema.Types.Boolean }, + notified: { type: Schema.Types.Boolean } + } + ], + default: [ + { type: 'vote', date: new Date(), toggle: false, notified: false }, + { type: 'daily', date: new Date(), toggle: false, notified: false }, + { type: 'drop', date: new Date(), toggle: false, notified: false }, + { type: 'claim', date: new Date(), toggle: false, notified: false }, + { type: 'work', date: new Date(), toggle: false, notified: false } + ] + }, + avatar: { + type: Schema.Types.String + } + }, + { timestamps: true } + ); + + type IUser = InferSchemaType; + expectType({} as IUser['reminders'][0]['type']); + expectType({} as IUser['reminders'][0]['date']); + expectType({} as IUser['reminders'][0]['toggle']); + expectType({} as IUser['avatar']); +} diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index ec5c7ceb3e8..e7631229036 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1059,10 +1059,10 @@ function gh12882() { }); type tArrType = InferSchemaType; expectType<{ - fooArray: { + fooArray: Types.DocumentArray<{ type: string; foo: number; - }[] + }> }>({} as tArrType); // Readonly array of strings const rArrString = new Schema({ @@ -1110,10 +1110,10 @@ function gh12882() { }); type rTArrType = InferSchemaType; expectType<{ - fooArray: { + fooArray: Types.DocumentArray<{ type: string; foo: number; - }[] + }> }>({} as rTArrType); } diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 8f878b312f4..bdd56790a3d 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -229,7 +229,7 @@ type ResolvePathType[] : // If the type key isn't callable, then this is an array of objects, in which case // we need to call ObtainDocumentType to correctly infer its type. - ObtainDocumentType[] : + Types.DocumentArray> : IsSchemaTypeFromBuiltinClass extends true ? ObtainDocumentPathType[] : IsItRecordAndNotAny extends true ? From 8d0043cc781e7f292dc34535f363c4a3976c9836 Mon Sep 17 00:00:00 2001 From: Atharv <65293806+Atharv-Bobde@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:56:02 +0530 Subject: [PATCH 76/89] Added Null check in case schema.options['type'][0] is undefined --- lib/document.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/document.js b/lib/document.js index 78b6f58eff2..61a88edfe72 100644 --- a/lib/document.js +++ b/lib/document.js @@ -1388,6 +1388,7 @@ Document.prototype.$set = function $set(path, val, type, options) { if (schema.options && Array.isArray(schema.options[typeKey]) && schema.options[typeKey].length && + schema.options[typeKey][0] && schema.options[typeKey][0].ref && _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) { popOpts = { [populateModelSymbol]: val[0].constructor }; From 1c2d602646014fb876b7af4d756ee6c3e857bd99 Mon Sep 17 00:00:00 2001 From: IICarst <67209089+IICarst@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:12:01 +0100 Subject: [PATCH 77/89] Added pre and post function types on Query class --- types/query.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/types/query.d.ts b/types/query.d.ts index d67e11200ff..0161bba2b2f 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -627,6 +627,12 @@ declare module 'mongoose' { QueryOp >; + /** Add pre middleware to this query instance. Doesn't affect other queries. */ + pre(fn: Function): this; + + /** Add post middleware to this query instance. Doesn't affect other queries. */ + post(fn: Function): this; + /** Get/set the current projection (AKA fields). Pass `null` to remove the current projection. */ projection(fields?: ProjectionFields | string): ProjectionFields; projection(fields: null): null; From 9afba5fd43f8c014dd9d4dc50ca1421a4043bd89 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 13 Mar 2024 09:38:14 -0400 Subject: [PATCH 78/89] chore: release 7.6.10 --- CHANGELOG.md | 5 +++++ package.json | 2 +- scripts/tsc-diagnostics-check.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca10a38fb79..e064efc1588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +7.6.10 / 2024-03-13 +=================== + * docs(model): add extra note about lean option for insertMany() skipping casting #14415 + * docs(mongoose): add options.overwriteModel details to mongoose.model() docs #14422 + 6.12.7 / 2024-03-01 =================== * perf(model): make insertMany() lean option skip hydrating Mongoose docs #14376 #14372 diff --git a/package.json b/package.json index d36925cae48..ee64f3c5f74 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "7.6.9", + "version": "7.6.10", "author": "Guillermo Rauch ", "keywords": [ "mongodb", diff --git a/scripts/tsc-diagnostics-check.js b/scripts/tsc-diagnostics-check.js index b00bcbb2438..2f74bf39b92 100644 --- a/scripts/tsc-diagnostics-check.js +++ b/scripts/tsc-diagnostics-check.js @@ -3,7 +3,7 @@ const fs = require('fs'); const stdin = fs.readFileSync(0).toString('utf8'); -const maxInstantiations = isNaN(process.argv[2]) ? 120000 : parseInt(process.argv[2], 10); +const maxInstantiations = isNaN(process.argv[2]) ? 125000 : parseInt(process.argv[2], 10); console.log(stdin); From cf62382207607e3191e3f58e0744c24217c1c0d0 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:52:22 -0400 Subject: [PATCH 79/89] write test --- lib/schemaType.js | 6 ++++++ test/schematype.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/schemaType.js b/lib/schemaType.js index e3caa4ff033..c4c922372c5 100644 --- a/lib/schemaType.js +++ b/lib/schemaType.js @@ -803,6 +803,12 @@ SchemaType.prototype.get = function(fn) { return this; }; +SchemaType.prototype.validateAll = function(approvalSeekers) { + for (let i = 0; i < approvalSeekers.length; i++) { + this.validate(approvalSeekers[i]); + } +} + /** * Adds validator(s) for this document path. * diff --git a/test/schematype.test.js b/test/schematype.test.js index 582a135c09c..f4186ed0381 100644 --- a/test/schematype.test.js +++ b/test/schematype.test.js @@ -281,4 +281,37 @@ describe('schematype', function() { }); }); }); + it('demonstrates the `validateAll()` function (gh-6910)', async function() { + const m = new mongoose.Mongoose(); + await m.connect(start.uri); + const validateSchema = new m.Schema({ name: String, password: String }); + validateSchema.path('name').validate({ + validator: function(v) { + return v.length > 5; + }, + message: 'name must be longer than 5 characters' + }) + validateSchema.path('password').validateAll([ + { + validator: function(v) { + return this.name == v; + }, + message: `password must not equal name` + }, + { + validator: function(v) { + return v.length > 5 + }, + message: `password must be at least six characters` + } + ]); + + const Test = m.model('Test', validateSchema); + await Test.deleteMany({}); + const check = new Test(); + check.name = 'Test'; + check.password = 'test'; + const test = check.validateSync(); + assert.ok(true); + }); }); From 82aee6673a73c7700475b6f55b5a0b613bad5940 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:40:11 -0400 Subject: [PATCH 80/89] feat: `validateAll` function on `SchemaType` --- lib/schemaType.js | 2 +- test/schematype.test.js | 24 ++++++++---------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/schemaType.js b/lib/schemaType.js index c4c922372c5..f21a8406dfd 100644 --- a/lib/schemaType.js +++ b/lib/schemaType.js @@ -807,7 +807,7 @@ SchemaType.prototype.validateAll = function(approvalSeekers) { for (let i = 0; i < approvalSeekers.length; i++) { this.validate(approvalSeekers[i]); } -} +}; /** * Adds validator(s) for this document path. diff --git a/test/schematype.test.js b/test/schematype.test.js index f4186ed0381..e0e2339dd98 100644 --- a/test/schematype.test.js +++ b/test/schematype.test.js @@ -282,36 +282,28 @@ describe('schematype', function() { }); }); it('demonstrates the `validateAll()` function (gh-6910)', async function() { - const m = new mongoose.Mongoose(); - await m.connect(start.uri); - const validateSchema = new m.Schema({ name: String, password: String }); + + const validateSchema = new Schema({ name: String, password: String }); validateSchema.path('name').validate({ validator: function(v) { return v.length > 5; }, message: 'name must be longer than 5 characters' - }) + }); validateSchema.path('password').validateAll([ - { + { validator: function(v) { return this.name == v; }, - message: `password must not equal name` + message: 'password must not equal name' }, { validator: function(v) { - return v.length > 5 + return v.length > 5; }, - message: `password must be at least six characters` + message: 'password must be at least six characters' } ]); - - const Test = m.model('Test', validateSchema); - await Test.deleteMany({}); - const check = new Test(); - check.name = 'Test'; - check.password = 'test'; - const test = check.validateSync(); - assert.ok(true); + assert.equal(validateSchema.path('password').validators.length, 2); }); }); From cab97cca5c0e61b2ae14e5a75c6caa165f35ae6f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Mar 2024 14:41:57 -0400 Subject: [PATCH 81/89] types+docs(schematype): add types and jsdoc for SchemaType.prototype.validateAll() --- lib/schemaType.js | 19 +++++++++++++++---- test/schematype.test.js | 13 +++++++++++-- types/schematypes.d.ts | 13 ++++++++++--- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/schemaType.js b/lib/schemaType.js index f21a8406dfd..74d713795d8 100644 --- a/lib/schemaType.js +++ b/lib/schemaType.js @@ -803,10 +803,19 @@ SchemaType.prototype.get = function(fn) { return this; }; -SchemaType.prototype.validateAll = function(approvalSeekers) { - for (let i = 0; i < approvalSeekers.length; i++) { - this.validate(approvalSeekers[i]); +/** + * Adds multiple validators for this document path. + * Calls `validate()` for every element in validators. + * + * @param {Array} validators + * @returns this + */ + +SchemaType.prototype.validateAll = function(validators) { + for (let i = 0; i < validators.length; i++) { + this.validate(validators[i]); } + return this; }; /** @@ -1291,6 +1300,9 @@ SchemaType.prototype.select = function select(val) { SchemaType.prototype.doValidate = function(value, fn, scope, options) { let err = false; const path = this.path; + if (typeof fn !== 'function') { + throw new TypeError(`Must pass callback function to doValidate(), got ${typeof fn}`); + } // Avoid non-object `validators` const validators = this.validators. @@ -1425,7 +1437,6 @@ SchemaType.prototype.doValidateSync = function(value, scope, options) { let i = 0; const len = validators.length; for (i = 0; i < len; ++i) { - const v = validators[i]; if (v === null || typeof v !== 'object') { diff --git a/test/schematype.test.js b/test/schematype.test.js index e0e2339dd98..b2b0ba5d890 100644 --- a/test/schematype.test.js +++ b/test/schematype.test.js @@ -282,7 +282,6 @@ describe('schematype', function() { }); }); it('demonstrates the `validateAll()` function (gh-6910)', async function() { - const validateSchema = new Schema({ name: String, password: String }); validateSchema.path('name').validate({ validator: function(v) { @@ -293,7 +292,7 @@ describe('schematype', function() { validateSchema.path('password').validateAll([ { validator: function(v) { - return this.name == v; + return this.name !== v; }, message: 'password must not equal name' }, @@ -305,5 +304,15 @@ describe('schematype', function() { } ]); assert.equal(validateSchema.path('password').validators.length, 2); + + const passwordPath = validateSchema.path('password'); + assert.throws( + () => { throw passwordPath.doValidateSync('john', { name: 'john' }); }, + /password must not equal name/ + ); + assert.throws( + () => { throw passwordPath.doValidateSync('short', { name: 'john' }); }, + /password must be at least six characters/ + ); }); }); diff --git a/types/schematypes.d.ts b/types/schematypes.d.ts index 088bc27c598..656fcdd30c0 100644 --- a/types/schematypes.d.ts +++ b/types/schematypes.d.ts @@ -192,10 +192,14 @@ declare module 'mongoose' { [other: string]: any; } - interface Validator { - message?: string; type?: string; validator?: Function + interface Validator { + message?: string; + type?: string; + validator?: ValidatorFunction; } + type ValidatorFunction = (this: DocType, value: any, validatorProperties?: Validator) => any; + class SchemaType { /** SchemaType constructor */ constructor(path: string, options?: AnyObject, instance?: string); @@ -281,7 +285,10 @@ declare module 'mongoose' { validators: Validator[]; /** Adds validator(s) for this document path. */ - validate(obj: RegExp | ((this: DocType, value: any, validatorProperties?: Validator) => any), errorMsg?: string, type?: string): this; + validate(obj: RegExp | ValidatorFunction | Validator, errorMsg?: string, type?: string): this; + + /** Adds multiple validators for this document path. */ + validateAll(validators: Array): this; /** Default options for this SchemaType */ defaultOptions?: Record; From 55774c6da9fb93944396fdfc3e018bbe6e560e36 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Mar 2024 14:42:18 -0400 Subject: [PATCH 82/89] Update test/schematype.test.js Co-authored-by: hasezoey --- test/schematype.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/schematype.test.js b/test/schematype.test.js index b2b0ba5d890..ad8367d0f61 100644 --- a/test/schematype.test.js +++ b/test/schematype.test.js @@ -281,7 +281,7 @@ describe('schematype', function() { }); }); }); - it('demonstrates the `validateAll()` function (gh-6910)', async function() { + it('demonstrates the `validateAll()` function (gh-6910)', function() { const validateSchema = new Schema({ name: String, password: String }); validateSchema.path('name').validate({ validator: function(v) { From 55b62b0d2f834f50157636dddfc9f2c98628e36b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Mar 2024 14:44:05 -0400 Subject: [PATCH 83/89] Update test/types/PipelineStage.test.ts Co-authored-by: hasezoey --- test/types/PipelineStage.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/types/PipelineStage.test.ts b/test/types/PipelineStage.test.ts index c5f54a0aca0..5b4c57ec55f 100644 --- a/test/types/PipelineStage.test.ts +++ b/test/types/PipelineStage.test.ts @@ -523,11 +523,11 @@ function gh12269() { const vectorSearchStages: PipelineStage[] = [ { $vectorSearch: { - index: 'title_vector_index', - path: 'embedding', - queryVector: [0.522,0.123,0.487], - limit: 5, - numCandidates: 100 + index: 'title_vector_index', + path: 'embedding', + queryVector: [0.522,0.123,0.487], + limit: 5, + numCandidates: 100 } }, { From 8aea2f7b86d397331ba3881d0eef0ddb66f2986d Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Mar 2024 14:44:11 -0400 Subject: [PATCH 84/89] Update test/types/PipelineStage.test.ts Co-authored-by: hasezoey --- test/types/PipelineStage.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/PipelineStage.test.ts b/test/types/PipelineStage.test.ts index 5b4c57ec55f..d8a8d26b8c1 100644 --- a/test/types/PipelineStage.test.ts +++ b/test/types/PipelineStage.test.ts @@ -532,8 +532,8 @@ const vectorSearchStages: PipelineStage[] = [ }, { $project: { - title: 1, - score: { $meta: 'searchScore' } + title: 1, + score: { $meta: 'searchScore' } } } ]; From 399531b0feac66aba6e0fb6defeed317089451af Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Mar 2024 14:54:57 -0400 Subject: [PATCH 85/89] types: propagate DocType down to validators for validate() and validateAll() --- types/schematypes.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/schematypes.d.ts b/types/schematypes.d.ts index 656fcdd30c0..9cb48343009 100644 --- a/types/schematypes.d.ts +++ b/types/schematypes.d.ts @@ -285,10 +285,10 @@ declare module 'mongoose' { validators: Validator[]; /** Adds validator(s) for this document path. */ - validate(obj: RegExp | ValidatorFunction | Validator, errorMsg?: string, type?: string): this; + validate(obj: RegExp | ValidatorFunction | Validator, errorMsg?: string, type?: string): this; /** Adds multiple validators for this document path. */ - validateAll(validators: Array): this; + validateAll(validators: Array | Validator>): this; /** Default options for this SchemaType */ defaultOptions?: Record; From 580bc8c1fc4a63997aa3c253afb52bc74d8b56b4 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Mar 2024 14:55:51 -0400 Subject: [PATCH 86/89] Update PipelineStage.test.ts --- test/types/PipelineStage.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/types/PipelineStage.test.ts b/test/types/PipelineStage.test.ts index d8a8d26b8c1..b53a685cf21 100644 --- a/test/types/PipelineStage.test.ts +++ b/test/types/PipelineStage.test.ts @@ -525,7 +525,7 @@ const vectorSearchStages: PipelineStage[] = [ $vectorSearch: { index: 'title_vector_index', path: 'embedding', - queryVector: [0.522,0.123,0.487], + queryVector: [0.522, 0.123, 0.487], limit: 5, numCandidates: 100 } From ca623ef9d179cdc89b683831f916d3aeb918a24a Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 14 Mar 2024 17:19:33 -0400 Subject: [PATCH 87/89] fix(model): improve update minimizing to only minimize top-level properties in the update Fix #14420 Re: #13782 --- lib/model.js | 22 +++++++++++++++------ test/document.test.js | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/lib/model.js b/lib/model.js index 16c7ceb6e07..b78c1252d24 100644 --- a/lib/model.js +++ b/lib/model.js @@ -351,12 +351,22 @@ Model.prototype.$__handleSave = function(options, callback) { const update = delta[1]; if (this.$__schema.options.minimize) { - minimize(update); - // minimize might leave us with an empty object, which would - // lead to MongoDB throwing a "Update document requires atomic operators" error - if (Object.keys(update).length === 0) { - handleEmptyUpdate.call(this); - return; + for (const updateOp of Object.values(update)) { + if (updateOp == null) { + continue; + } + for (const key of Object.keys(updateOp)) { + if (updateOp[key] == null || typeof updateOp[key] !== 'object') { + continue; + } + if (!utils.isPOJO(updateOp[key])) { + continue; + } + minimize(updateOp[key]); + if (Object.keys(updateOp[key]).length === 0) { + updateOp[key] = null; + } + } } } diff --git a/test/document.test.js b/test/document.test.js index f6f1d0797b3..a38d3bdb853 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -13041,6 +13041,51 @@ describe('document', function() { assert.ok(doc.docArr.toString().includes('child'), doc.docArr.toString()); assert.ok(doc.docArr.toString().includes('test child'), doc.docArr.toString()); }); + + it('minimizes when updating existing documents (gh-13782)', async function() { + const schema = new Schema({ + metadata: { + type: {}, + default: {}, + required: true, + _id: false + } + }, { minimize: true }); + const Model = db.model('Test', schema); + const m = new Model({ metadata: {} }); + await m.save(); + + const x = await Model.findById(m._id).exec(); + x.metadata = {}; + await x.save(); + + const { metadata } = await Model.findById(m._id).orFail(); + assert.strictEqual(metadata, null); + }); + + it('saves when setting subdocument to empty object (gh-14420) (gh-13782)', async function() { + const SubSchema = new mongoose.Schema({ + name: { type: String }, + age: Number + }, { _id: false }); + + const MainSchema = new mongoose.Schema({ + sub: { + type: SubSchema + } + }); + + const MainModel = db.model('Test', MainSchema); + + const doc = new MainModel({ sub: { name: 'Hello World', age: 42 } }); + await doc.save(); + + doc.sub = {}; + await doc.save(); + + const savedDoc = await MainModel.findById(doc.id).orFail(); + assert.strictEqual(savedDoc.sub, null); + }); }); describe('Check if instance function that is supplied in schema option is availabe', function() { From 8116374bf6bf377388347d0cc86be936adb4d543 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 15 Mar 2024 14:24:58 -0400 Subject: [PATCH 88/89] chore: release 8.2.2 --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1296704010f..a6cf1375736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +8.2.2 / 2024-03-15 +================== + * fix(model): improve update minimizing to only minimize top-level properties in the update #14437 #14420 #13782 + * fix: add Null check in case schema.options['type'][0] is undefined #14431 [Atharv-Bobde](https://github.com/Atharv-Bobde) + * types: consistently infer array of objects in schema as a DocumentArray #14430 #14367 + * types: add TypeScript interface for the new PipelineStage - Vector Search - solving issue #14428 #14429 [jkorach](https://github.com/jkorach) + * types: add pre and post function types on Query class #14433 #14432 [IICarst](https://github.com/IICarst) + * types(model): make bulkWrite() types more flexible to account for casting #14423 + * docs: update version support documentation for mongoose 5 & 6 #14427 [hasezoey](https://github.com/hasezoey) + 7.6.10 / 2024-03-13 =================== * docs(model): add extra note about lean option for insertMany() skipping casting #14415 diff --git a/package.json b/package.json index 55adb04c2af..23ee85bebe4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "8.2.1", + "version": "8.2.2", "author": "Guillermo Rauch ", "keywords": [ "mongodb", From a8d5c98ac68c430aaa3c937b3c21c33de070ad17 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:27:08 -0400 Subject: [PATCH 89/89] fix: array schema definitions with `of` keyword --- lib/schema.js | 12 ++++++++---- test/schema.test.js | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 600e27208ee..433d3cf036e 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -1268,6 +1268,7 @@ Schema.prototype.interpretAsType = function(path, obj, options) { return clone; } + // If this schema has an associated Mongoose object, use the Mongoose object's // copy of SchemaTypes re: gh-7158 gh-6933 const MongooseTypes = this.base != null ? this.base.Schema.Types : Schema.Types; @@ -1333,9 +1334,13 @@ Schema.prototype.interpretAsType = function(path, obj, options) { } return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast); } - - if (Array.isArray(cast)) { - return new MongooseTypes.Array(path, this.interpretAsType(path, cast, options), obj); + if (typeof cast !== 'undefined') { + if (Array.isArray(cast) || cast.type === Array || cast.type == 'Array') { + if (cast && cast.type == 'Array') { + cast.type = Array; + } + return new MongooseTypes.Array(path, this.interpretAsType(path, cast, options), obj); + } } // Handle both `new Schema({ arr: [{ subpath: String }] })` and `new Schema({ arr: [{ type: { subpath: string } }] })` @@ -1386,7 +1391,6 @@ Schema.prototype.interpretAsType = function(path, obj, options) { type = cast[options.typeKey] && (options.typeKey !== 'type' || !cast.type.type) ? cast[options.typeKey] : cast; - if (Array.isArray(type)) { return new MongooseTypes.Array(path, this.interpretAsType(path, type, options), obj); } diff --git a/test/schema.test.js b/test/schema.test.js index 0df19a65790..61f206d6f0f 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -3202,4 +3202,14 @@ describe('schema', function() { const doc = new baseModel({ type: 1, self: [{ type: 1 }] }); assert.equal(doc.self[0].type, 1); }); + it('should have the correct schema definition with array schemas (gh-14416)', function() { + const schema = new Schema({ + nums: [{ type: Array, of: Number }], + tags: [{ type: 'Array', of: String }], + subdocs: [{ type: Array, of: Schema({ name: String }) }] + }); + assert.equal(schema.path('nums.$').caster.instance, 'Number'); // actually Mixed + assert.equal(schema.path('tags.$').caster.instance, 'String'); // actually Mixed + assert.equal(schema.path('subdocs.$').casterConstructor.schema.path('name').instance, 'String'); // actually Mixed + }); });