From 5c9ba3f84e0a6aae47e9ce21e3a04a4e80f85de4 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 25 May 2023 11:14:31 -0400 Subject: [PATCH 01/23] feat(schema): consistently add `.get()` function to all SchemaType classes Re: #7156 --- lib/schema/SubdocumentPath.js | 12 ++++++++++++ lib/schema/array.js | 12 ++++++++++++ lib/schema/bigint.js | 17 +++++++++++++++++ lib/schema/boolean.js | 19 +++++++++++++++++++ lib/schema/buffer.js | 20 ++++++++++++++++++++ lib/schema/date.js | 20 ++++++++++++++++++++ lib/schema/decimal128.js | 17 +++++++++++++++++ lib/schema/documentarray.js | 12 ++++++++++++ 8 files changed, 129 insertions(+) diff --git a/lib/schema/SubdocumentPath.js b/lib/schema/SubdocumentPath.js index 6b2058cb3d4..cf15af3400e 100644 --- a/lib/schema/SubdocumentPath.js +++ b/lib/schema/SubdocumentPath.js @@ -347,6 +347,18 @@ SubdocumentPath.defaultOptions = {}; SubdocumentPath.set = SchemaType.set; +/** + * Attaches a getter for all SubdocumentPath instances + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SubdocumentPath.get = SchemaType.get; + /*! * ignore */ diff --git a/lib/schema/array.js b/lib/schema/array.js index 5a621ba6aac..9b292fbc3d7 100644 --- a/lib/schema/array.js +++ b/lib/schema/array.js @@ -170,6 +170,18 @@ SchemaArray.defaultOptions = {}; */ SchemaArray.set = SchemaType.set; +/** + * Attaches a getter for all Array instances + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaArray.get = SchemaType.get; + /*! * Inherits from SchemaType. */ diff --git a/lib/schema/bigint.js b/lib/schema/bigint.js index 24cff4ea9ed..3c05a53d13c 100644 --- a/lib/schema/bigint.js +++ b/lib/schema/bigint.js @@ -62,6 +62,23 @@ SchemaBigInt._cast = castBigInt; SchemaBigInt.set = SchemaType.set; +/** + * Attaches a getter for all BigInt instances + * + * #### Example: + * + * // Convert bigints to numbers + * mongoose.Schema.BigInt.get(v => v == null ? number : Number(v)); + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaBigInt.get = SchemaType.get; + /** * Get/set the function used to cast arbitrary values to booleans. * diff --git a/lib/schema/boolean.js b/lib/schema/boolean.js index b6c4f891c41..49e4b4e67b0 100644 --- a/lib/schema/boolean.js +++ b/lib/schema/boolean.js @@ -65,6 +65,25 @@ SchemaBoolean._cast = castBoolean; SchemaBoolean.set = SchemaType.set; +/** + * Attaches a getter for all Boolean instances + * + * #### Example: + * + * mongoose.Schema.Boolean.get(v => v === true ? 'yes' : 'no'); + * + * const Order = mongoose.model('Order', new Schema({ isPaid: Boolean })); + * new Order({ isPaid: false }).isPaid; // 'no' + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaBoolean.get = SchemaType.get; + /** * Get/set the function used to cast arbitrary values to booleans. * diff --git a/lib/schema/buffer.js b/lib/schema/buffer.js index bbab035bc9c..badb1bafabe 100644 --- a/lib/schema/buffer.js +++ b/lib/schema/buffer.js @@ -70,6 +70,26 @@ SchemaBuffer._checkRequired = v => !!(v && v.length); SchemaBuffer.set = SchemaType.set; +/** + * Attaches a getter for all Buffer instances + * + * #### Example: + * + * // Always convert to string when getting an ObjectId + * mongoose.Schema.Types.Buffer.get(v => v.toString('hex')); + * + * const Model = mongoose.model('Test', new Schema({ buf: Buffer } })); + * typeof (new Model({ buf: Buffer.fromString('hello') }).buf); // 'string' + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaBuffer.get = SchemaType.get; + /** * Override the function the required validator uses to check whether a string * passes the `required` check. diff --git a/lib/schema/date.js b/lib/schema/date.js index 746f39df8b4..5e3b226cab5 100644 --- a/lib/schema/date.js +++ b/lib/schema/date.js @@ -70,6 +70,26 @@ SchemaDate._cast = castDate; SchemaDate.set = SchemaType.set; +/** + * Attaches a getter for all ObjectId instances + * + * #### Example: + * + * // Always convert to string when getting an ObjectId + * mongoose.Date.get(v => v.toString()); + * + * const Model = mongoose.model('Test', new Schema({ date: { type: Date, default: () => new Date() } })); + * typeof (new Model({}).date); // 'string' + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaDate.get = SchemaType.get; + /** * Get/set the function used to cast arbitrary values to dates. * diff --git a/lib/schema/decimal128.js b/lib/schema/decimal128.js index 6ac87416fe6..c0c79d5834c 100644 --- a/lib/schema/decimal128.js +++ b/lib/schema/decimal128.js @@ -66,6 +66,23 @@ Decimal128._cast = castDecimal128; Decimal128.set = SchemaType.set; +/** + * Attaches a getter for all Decimal128 instances + * + * #### Example: + * + * // Automatically convert Decimal128s to Numbers + * mongoose.Schema.Decimal128.get(v => v == null ? v : Number(v)); + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +Decimal128.get = SchemaType.get; + /** * Get/set the function used to cast arbitrary values to decimals. * diff --git a/lib/schema/documentarray.js b/lib/schema/documentarray.js index 2ba0905ebc5..d3a1ac581a3 100644 --- a/lib/schema/documentarray.js +++ b/lib/schema/documentarray.js @@ -605,6 +605,18 @@ DocumentArrayPath.defaultOptions = {}; DocumentArrayPath.set = SchemaType.set; +/** + * Attaches a getter for all DocumentArrayPath instances + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +DocumentArrayPath.get = SchemaType.get; + /*! * Module exports. */ From e7710549c71b8f2f1d6bbad4b103f303ebae0963 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 26 May 2023 10:55:48 -0400 Subject: [PATCH 02/23] Update lib/schema/date.js Co-authored-by: hasezoey --- lib/schema/date.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema/date.js b/lib/schema/date.js index 5e3b226cab5..87e8f02a5de 100644 --- a/lib/schema/date.js +++ b/lib/schema/date.js @@ -71,7 +71,7 @@ SchemaDate._cast = castDate; SchemaDate.set = SchemaType.set; /** - * Attaches a getter for all ObjectId instances + * Attaches a getter for all Date instances * * #### Example: * From c69661a65ea42ee5995f611db8d92648dfb86129 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 26 May 2023 10:55:54 -0400 Subject: [PATCH 03/23] Update lib/schema/date.js Co-authored-by: hasezoey --- lib/schema/date.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema/date.js b/lib/schema/date.js index 87e8f02a5de..41625703492 100644 --- a/lib/schema/date.js +++ b/lib/schema/date.js @@ -75,7 +75,7 @@ SchemaDate.set = SchemaType.set; * * #### Example: * - * // Always convert to string when getting an ObjectId + * // Always convert Dates to string * mongoose.Date.get(v => v.toString()); * * const Model = mongoose.model('Test', new Schema({ date: { type: Date, default: () => new Date() } })); From 91d4557c3e7f21903b3636cf943a0f268b03c14b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 26 May 2023 10:55:59 -0400 Subject: [PATCH 04/23] Update lib/schema/bigint.js Co-authored-by: hasezoey --- lib/schema/bigint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema/bigint.js b/lib/schema/bigint.js index 3c05a53d13c..bea5abc61df 100644 --- a/lib/schema/bigint.js +++ b/lib/schema/bigint.js @@ -68,7 +68,7 @@ SchemaBigInt.set = SchemaType.set; * #### Example: * * // Convert bigints to numbers - * mongoose.Schema.BigInt.get(v => v == null ? number : Number(v)); + * mongoose.Schema.BigInt.get(v => v == null ? v : Number(v)); * * @param {Function} getter * @return {this} From bd980d44dad8e081967f1ed2abeeb1ff9108bfb7 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 26 May 2023 11:10:02 -0400 Subject: [PATCH 05/23] address code review comments, add missing `get()` to UUID --- lib/schema/documentarray.js | 2 +- lib/schema/uuid.js | 20 ++++++++++++++++++++ test/schematype.test.js | 24 ++++++++++++------------ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/lib/schema/documentarray.js b/lib/schema/documentarray.js index d3a1ac581a3..7de35055ce7 100644 --- a/lib/schema/documentarray.js +++ b/lib/schema/documentarray.js @@ -37,7 +37,7 @@ let Subdocument; */ function DocumentArrayPath(key, schema, options, schemaOptions) { - if (schema.options.timeseries) { + if (schema.options && schema.options.timeseries) { throw new InvalidSchemaOptionError(key, 'timeseries'); } const schemaTypeIdOption = DocumentArrayPath.defaultOptions && diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 8c16b84ef13..7656ceccec6 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -153,6 +153,26 @@ SchemaUUID._cast = function(value) { throw new CastError(SchemaUUID.schemaName, value, this.path); }; +/** + * Attaches a getter for all UUID instances. + * + * #### Example: + * + * // Note that `v` is a string by default + * mongoose.Schema.UUID.get(v => v.toUpperCase()); + * + * const Model = mongoose.model('Test', new Schema({ test: 'UUID' })); + * new Model({ test: uuid.v4() }).test; // UUID with all uppercase + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaUUID.get = SchemaType.get; + /** * Sets a default option for all UUID instances. * diff --git a/test/schematype.test.js b/test/schematype.test.js index 6e07390a2bc..7cb806cada8 100644 --- a/test/schematype.test.js +++ b/test/schematype.test.js @@ -198,6 +198,14 @@ describe('schematype', function() { }); }); + describe('get()', function() { + Object.values(mongoose.SchemaTypes).forEach(schemaType => { + it(`${schemaType.name} has a \`get\` method`, () => { + assert.strictEqual(typeof schemaType.get, 'function'); + }); + }); + }); + describe('set()', function() { describe('SchemaType.set()', function() { it('SchemaType.set, is a function', () => { @@ -205,18 +213,10 @@ describe('schematype', function() { }); }); - [ - mongoose.SchemaTypes.String, - mongoose.SchemaTypes.Number, - mongoose.SchemaTypes.Boolean, - mongoose.SchemaTypes.Array, - mongoose.SchemaTypes.Buffer, - mongoose.SchemaTypes.Date, - mongoose.SchemaTypes.ObjectId, - mongoose.SchemaTypes.Mixed, - mongoose.SchemaTypes.Decimal128, - mongoose.SchemaTypes.Map - ].forEach((type) => { + const typesToTest = Object.values(mongoose.SchemaTypes). + filter(t => t.name !== 'SubdocumentPath' && t.name !== 'DocumentArrayPath'); + + typesToTest.forEach((type) => { it(type.name + ', when given a default option, set its', () => { // Act type.set('someRandomOption', true); From cdaa9cf37c96292d7b639bebcc6c1b7d771031e1 Mon Sep 17 00:00:00 2001 From: Lorand Horvath <72015221+lorand-horvath@users.noreply.github.com> Date: Thu, 1 Jun 2023 21:07:02 +0300 Subject: [PATCH 06/23] Update mongodb driver 5.6.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f2f11de2bee..a738d3ebbdf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "7.2.2", + "version": "7.3.0", "author": "Guillermo Rauch ", "keywords": [ "mongodb", @@ -21,7 +21,7 @@ "dependencies": { "bson": "^5.3.0", "kareem": "2.5.1", - "mongodb": "5.5.0", + "mongodb": "5.6.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", From b8e7899de2eeea6d5b02737af84496b360a734af Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 2 Jun 2023 13:41:51 -0400 Subject: [PATCH 07/23] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a738d3ebbdf..5f7da55b605 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "7.3.0", + "version": "7.2.2", "author": "Guillermo Rauch ", "keywords": [ "mongodb", From e36b300fe0efb2f9113251d55b7bf6b92bb898e9 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Fri, 2 Jun 2023 17:07:27 -0400 Subject: [PATCH 08/23] `ordered` option --- lib/model.js | 51 ++++++++++++++++++++++++++++----------- test/model.create.test.js | 23 ++++++++++++++++++ types/models.d.ts | 1 + 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/lib/model.js b/lib/model.js index 65e0211e715..f43f2d29344 100644 --- a/lib/model.js +++ b/lib/model.js @@ -479,6 +479,7 @@ function generateVersionError(doc, modifiedPaths) { * newProduct === product; // true * * @param {Object} [options] options optional options + * @param {Boolean} [options.ordered] saves the docs in series rather than parallel. * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()). * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead. * @param {Boolean} [options.validateBeforeSave] set to false to save without validating. @@ -2838,25 +2839,47 @@ Model.create = async function create(doc, options) { if (args.length === 0) { return Array.isArray(doc) ? [] : null; } + let res = []; + if (options.ordered) { + for (let i = 0; i < args.length; i++) { + const doc = args[i]; + const Model = this.discriminators && doc[discriminatorKey] != null ? + this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) : + this; + if (Model == null) { + throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` + + `found for model "${this.modelName}"`); + } + let toSave = doc; + if (!(toSave instanceof Model)) { + toSave = new Model(toSave); + } - const res = await Promise.all(args.map(async doc => { - const Model = this.discriminators && doc[discriminatorKey] != null ? - this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) : - this; - if (Model == null) { - throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` + - `found for model "${this.modelName}"`); + await toSave.$save(options); + res.push(toSave); } - let toSave = doc; + return res; + } else { + res = await Promise.all(args.map(async doc => { + const Model = this.discriminators && doc[discriminatorKey] != null ? + this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) : + this; + if (Model == null) { + throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` + + `found for model "${this.modelName}"`); + } + let toSave = doc; - if (!(toSave instanceof Model)) { - toSave = new Model(toSave); - } + if (!(toSave instanceof Model)) { + toSave = new Model(toSave); + } - await toSave.$save(options); + await toSave.$save(options); + + return toSave; + })); + } - return toSave; - })); if (!Array.isArray(doc) && args.length === 1) { return res[0]; diff --git a/test/model.create.test.js b/test/model.create.test.js index 646b76c01f5..6979bd52379 100644 --- a/test/model.create.test.js +++ b/test/model.create.test.js @@ -167,5 +167,28 @@ describe('model', function() { }); }); }); + describe('ordered', function() { + it('runs the document insertion in a series when using the ordered option gh-4038', async function() { + const countSchema = new Schema({ n: Number }); + const testSchema = new Schema({ name: { type: String, unique: true }, reference: Number }); + + const Count = db.model('gh4038', countSchema); + + testSchema.pre('save', async function(next) { + const doc = await Count.findOneAndUpdate({}, { $inc: { n: 1 } }, { new: true, upsert: true }); + this.reference = doc.n; + next(); + }); + + const Test = db.model('gh4038Test', testSchema); + const data = []; + for (let i = 0; i < 11; i++) { + data.push({ name: 'Test' + Math.abs(i - 4) }); + } + await Test.create(data, { ordered: true }).catch(err => err); + const docs = await Test.find(); + assert.equal(docs.length, 5); + }); + }); }); }); diff --git a/types/models.d.ts b/types/models.d.ts index f99492dca31..ca844fcb94e 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -123,6 +123,7 @@ declare module 'mongoose' { SessionOption { checkKeys?: boolean; j?: boolean; + ordered?: boolean; safe?: boolean | WriteConcern; timestamps?: boolean | QueryTimestampsConfig; validateBeforeSave?: boolean; From 9e6d84ffbd65794dee8177ef3863c94025682c70 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 4 Jun 2023 13:08:54 -0400 Subject: [PATCH 09/23] feat(populate): pass virtual to `match` function to allow merging match options Fix #12443 --- .../populate/getModelsMapForPopulate.js | 6 +-- test/model.populate.test.js | 50 +++++++++++++++++++ types/index.d.ts | 2 +- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/lib/helpers/populate/getModelsMapForPopulate.js b/lib/helpers/populate/getModelsMapForPopulate.js index dbe3707766a..7ac134266d7 100644 --- a/lib/helpers/populate/getModelsMapForPopulate.js +++ b/lib/helpers/populate/getModelsMapForPopulate.js @@ -436,13 +436,13 @@ function _virtualPopulate(model, docs, options, _virtualRes) { data.justOne = justOne; // `match` - let match = get(options, 'match', null) || - get(data, 'virtual.options.match', null) || + const baseMatch = get(data, 'virtual.options.match', null) || get(data, 'virtual.options.options.match', null); + let match = get(options, 'match', null) || baseMatch; let hasMatchFunction = typeof match === 'function'; if (hasMatchFunction) { - match = match.call(doc, doc); + match = match.call(doc, doc, data.virtual); } if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) { diff --git a/test/model.populate.test.js b/test/model.populate.test.js index 452561f8f2a..d16e0513a19 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -10454,4 +10454,54 @@ describe('model: populate:', function() { assert.equal(normal.test.length, 0); }); }); + + it('calls match function with virtual as parameter (gh-12443)', async function() { + const parentSchema = mongoose.Schema({ name: String }); + parentSchema.virtual('children', { + ref: 'Child', + localField: '_id', + foreignField: 'parentId', + justOne: false, + match: { + isDeleted: false + } + }); + const Parent = db.model('Parent', parentSchema); + + const childSchema = mongoose.Schema({ + name: String, + parentId: 'ObjectId', + isDeleted: Boolean + }); + const Child = db.model('Child', childSchema); + + const { _id } = await Parent.create({ name: 'Darth Vader' }); + await Child.create([ + { name: 'Luke', parentId: _id, isDeleted: false }, + { name: 'Leia', parentId: _id, isDeleted: false }, + { name: 'Chad', parentId: _id, isDeleted: true } + ]); + + let doc = await Parent.findById(_id).populate({ + path: 'children' + }); + assert.deepStrictEqual(doc.children.map(c => c.name).sort(), ['Leia', 'Luke']); + + doc = await Parent.findById(_id).populate({ + path: 'children', + match: (_doc, virtual) => ({ + ...virtual.options.match, + name: /(Luke|Chad)/ + }) + }); + assert.deepStrictEqual(doc.children.map(c => c.name).sort(), ['Luke']); + + doc = await Parent.findById(_id).populate({ + path: 'children', + match: () => ({ + name: /(Luke|Chad)/ + }) + }); + assert.deepStrictEqual(doc.children.map(c => c.name).sort(), ['Chad', 'Luke']); + }); }); diff --git a/types/index.d.ts b/types/index.d.ts index 55e2aa2360b..cfed86c51c3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -512,7 +512,7 @@ declare module 'mongoose' { count?: boolean; /** Add an extra match condition to `populate()`. */ - match?: FilterQuery | Function; + match?: FilterQuery | ((doc: Record, virtual?: this) => Record | null); /** Add a default `limit` to the `populate()` query. */ limit?: number; From cd0912b00ef680b3d7ab9e66538a11bc7549070e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 4 Jun 2023 13:52:08 -0400 Subject: [PATCH 10/23] types: allow overwriting `Paths` in `select()` to tell TypeScript which fields are projected Fix #13224 --- test/types/queries.test.ts | 11 +++++++++++ types/query.d.ts | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index 4cde75fb3ac..f72787681c2 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -475,3 +475,14 @@ async function gh13142() { if (!blog) return; expectType>>(blog); } + +async function gh13224() { + const userSchema = new Schema({ name: String, age: Number }); + const UserModel = model('User', userSchema); + + const u = await UserModel.findOne().select<{ name: string }>(['name']).orFail(); + expectType(u.name); + expectError(u.age); + + expectError(UserModel.findOne().select<{ notInSchema: string }>(['name']).orFail()); +} diff --git a/types/query.d.ts b/types/query.d.ts index 6815e14ad47..6932a565666 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -622,7 +622,15 @@ declare module 'mongoose' { ): QueryWithHelpers; /** Specifies which document fields to include or exclude (also known as the query "projection") */ - select(arg: string | string[] | Record): this; + select( + arg: string | string[] | Record + ): QueryWithHelpers< + UnpackedIntersection<{}, Paths>, + DocType, + THelpers, + UnpackedIntersection<{}, Paths>, + QueryOp + >; /** Determines if field selection has been made. */ selected(): boolean; From 178dd53964ac46314d67c676bd6d649a5241e14f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 6 Jun 2023 21:17:58 -0400 Subject: [PATCH 11/23] docs(populate): add more info on `match` option for `populate()` re: #12443 --- docs/populate.md | 23 +++++++++++++++++++++++ lib/schema.js | 1 + 2 files changed, 24 insertions(+) diff --git a/docs/populate.md b/docs/populate.md index 58226e65130..32dbca8b6ea 100644 --- a/docs/populate.md +++ b/docs/populate.md @@ -727,6 +727,29 @@ AuthorSchema.virtual('posts', { }); ``` +You can overwrite the `match` option when calling `populate()` as follows. + +```javascript +// Overwrite the `match` option specified in `AuthorSchema.virtual()` for this +// single `populate()` call. +await Author.findOne().populate({ path: posts, match: {} }); +``` + +You can also set the `match` option to a function in your `populate()` call. +If you want to merge your `populate()` match option, rather than overwriting, use the following. + +```javascript +await Author.findOne().populate({ + path: posts, + // Add `isDeleted: false` to the virtual's default `match`, so the `match` + // option would be `{ tags: author.favoriteTags, isDeleted: false }` + match: (author, virtual) => ({ + ...virtual.options.match(author), + isDeleted: false + }) +}); +``` +

Populating Maps

[Maps](schematypes.html#maps) are a type that represents an object with arbitrary diff --git a/lib/schema.js b/lib/schema.js index 25a5f773639..b25e89c24a7 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2192,6 +2192,7 @@ Schema.prototype.indexes = function() { * @param {Boolean|Function} [options.justOne=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), will be a single doc or `null`. Otherwise, the populate virtual will be an array. * @param {Boolean} [options.count=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), this populate virtual will contain the number of documents rather than the documents themselves when you `populate()`. * @param {Function|null} [options.get=null] Adds a [getter](https://mongoosejs.com/docs/tutorials/getters-setters.html) to this virtual to transform the populated doc. + * @param {Object|Function} [options.match=null] Apply a default [`match` option to populate](https://mongoosejs.com/docs/populate.html#match), adding an additional filter to the populate query. * @return {VirtualType} */ From fb7e19fe888672d9bdc01d22d62f8bafc506481f Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Mon, 12 Jun 2023 17:16:34 -0400 Subject: [PATCH 12/23] added test case --- test/document.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/document.test.js b/test/document.test.js index 19c9b711a07..5f5011be0d2 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -12198,6 +12198,19 @@ describe('document', function() { const fromDb = await Test.findById(x._id).lean(); assert.equal(fromDb.c.x.y, 1); }); + it('should use schema-level validateModifiedOnly option if not in options asdf', async function() { + const testSchema = new Schema({ title: { type: String, required: true }, other: String }, { validateModifiedOnly: true }); + + const Test = db.model('Test', testSchema); + + const docs = await Test.create([{ }], { validateBeforeSave: false }); + + const doc = docs[0]; + doc.other = 'hello world'; + assert.equal(doc.validateSync(), undefined); + const error = await doc.save().then(() => null, err => err); + assert.equal(error, null); + }); }); describe('Check if instance function that is supplied in schema option is availabe', function() { @@ -12208,3 +12221,4 @@ describe('Check if instance function that is supplied in schema option is availa assert.equal(TestDocument.instanceFn(), 'Returned from DocumentInstanceFn'); }); }); + From 538b2896193c82226c2e947acd5811aa45606abd Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Mon, 12 Jun 2023 17:17:32 -0400 Subject: [PATCH 13/23] remove identifier --- 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 5f5011be0d2..88e345314cd 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -12198,7 +12198,7 @@ describe('document', function() { const fromDb = await Test.findById(x._id).lean(); assert.equal(fromDb.c.x.y, 1); }); - it('should use schema-level validateModifiedOnly option if not in options asdf', async function() { + it('should use schema-level validateModifiedOnly option if not in options', async function() { const testSchema = new Schema({ title: { type: String, required: true }, other: String }, { validateModifiedOnly: true }); const Test = db.model('Test', testSchema); From a973c3d5a5138f6868e6e0215cd532afc0c086c9 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 13 Jun 2023 12:39:44 -0400 Subject: [PATCH 14/23] put test in correct section --- test/document.test.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/test/document.test.js b/test/document.test.js index 88e345314cd..769748352a8 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -2463,6 +2463,20 @@ describe('document', function() { await doc.save(); }); + it('should use schema-level validateModifiedOnly option if not in options', async function() { + const testSchema = new Schema({ title: { type: String, required: true }, other: String }, { validateModifiedOnly: true }); + + const Test = db.model('Test', testSchema); + + const docs = await Test.create([{ }], { validateBeforeSave: false }); + + const doc = docs[0]; + doc.other = 'hello world'; + assert.equal(doc.validateSync(), undefined); + const error = await doc.save().then(() => null, err => err); + assert.equal(error, null); + }); + it('handles non-errors', async function() { const schema = new Schema({ name: { type: String, required: true } @@ -12198,19 +12212,6 @@ describe('document', function() { const fromDb = await Test.findById(x._id).lean(); assert.equal(fromDb.c.x.y, 1); }); - it('should use schema-level validateModifiedOnly option if not in options', async function() { - const testSchema = new Schema({ title: { type: String, required: true }, other: String }, { validateModifiedOnly: true }); - - const Test = db.model('Test', testSchema); - - const docs = await Test.create([{ }], { validateBeforeSave: false }); - - const doc = docs[0]; - doc.other = 'hello world'; - assert.equal(doc.validateSync(), undefined); - const error = await doc.save().then(() => null, err => err); - assert.equal(error, null); - }); }); describe('Check if instance function that is supplied in schema option is availabe', function() { From 7f9f60a2c49f20d7f27bb7aaba12cb1c3fc8b88e Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 13 Jun 2023 12:48:22 -0400 Subject: [PATCH 15/23] add to options and typescript options --- lib/schema.js | 2 ++ types/schemaoptions.d.ts | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/lib/schema.js b/lib/schema.js index b25e89c24a7..0269d1bc0f7 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -70,6 +70,7 @@ let id = 0; * - [toObject](https://mongoosejs.com/docs/guide.html#toObject) - object - no default * - [typeKey](https://mongoosejs.com/docs/guide.html#typeKey) - string - defaults to 'type' * - [validateBeforeSave](https://mongoosejs.com/docs/guide.html#validateBeforeSave) - bool - defaults to `true` + * - [validateModifiedOnly](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) - bool - defaults to `false` * - [versionKey](https://mongoosejs.com/docs/guide.html#versionKey): string or object - defaults to "__v" * - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html). * - [collation](https://mongoosejs.com/docs/guide.html#collation): object - defaults to null (which means use no collation) @@ -549,6 +550,7 @@ Schema.prototype.defaultOptions = function(options) { shardKey: null, read: null, validateBeforeSave: true, + validateModifiedOnly: false, // the following are only applied at construction time _id: true, id: id, diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 997d79054c9..8d6e14a8208 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -154,6 +154,11 @@ declare module 'mongoose' { * objects which don't pass validation, you can set validateBeforeSave to false. */ validateBeforeSave?: boolean; + /** + * By default, validation will run on all paths before saving to the database. You can choose to have it run + * only on paths that were modified by setting validateModifiedOnly to true. + */ + validateModifiedOnly?: boolean; /** * The versionKey is a property set on each document when first created by Mongoose. This keys value * contains the internal revision of the document. The versionKey option is a string that represents From 1a74ac2a795e240178166b0dc0efd18ef8f5f5af Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 13 Jun 2023 12:53:25 -0400 Subject: [PATCH 16/23] fix: lint --- test/document.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/document.test.js b/test/document.test.js index 769748352a8..1e6c9ef7afd 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -2465,11 +2465,11 @@ describe('document', function() { it('should use schema-level validateModifiedOnly option if not in options', async function() { const testSchema = new Schema({ title: { type: String, required: true }, other: String }, { validateModifiedOnly: true }); - + const Test = db.model('Test', testSchema); - + const docs = await Test.create([{ }], { validateBeforeSave: false }); - + const doc = docs[0]; doc.other = 'hello world'; assert.equal(doc.validateSync(), undefined); From 1f2a26eb24ae5f975614e2cf3ef7529d043f39ef Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 13 Jun 2023 14:35:36 -0400 Subject: [PATCH 17/23] Update schemaoptions.d.ts --- types/schemaoptions.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 8d6e14a8208..93af4489d31 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -155,8 +155,8 @@ declare module 'mongoose' { */ validateBeforeSave?: boolean; /** - * By default, validation will run on all paths before saving to the database. You can choose to have it run - * only on paths that were modified by setting validateModifiedOnly to true. + * By default, validation will run on modified and required paths before saving to the database. + * You can choose to have Mongoose only validate modified paths by setting validateModifiedOnly to true. */ validateModifiedOnly?: boolean; /** From 30e7833c12a1193d41d768113aad00d69d2982c2 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 13 Jun 2023 15:21:22 -0400 Subject: [PATCH 18/23] types: fix some issues with `select()` generic re: #13224 --- test/types/queries.test.ts | 20 ++++++++++++++++---- types/query.d.ts | 22 +++++++++++++++++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index f72787681c2..7861b81009f 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -17,7 +17,7 @@ import { QueryOptions } from 'mongoose'; import { ObjectId } from 'mongodb'; -import { expectError, expectType } from 'tsd'; +import { expectAssignable, expectError, expectType } from 'tsd'; import { autoTypedModel } from './models.test'; import { AutoTypedSchemaType } from './schema.test'; @@ -480,9 +480,21 @@ async function gh13224() { const userSchema = new Schema({ name: String, age: Number }); const UserModel = model('User', userSchema); - const u = await UserModel.findOne().select<{ name: string }>(['name']).orFail(); - expectType(u.name); - expectError(u.age); + const u1 = await UserModel.findOne().select(['name']).orFail(); + expectType(u1.name); + expectType(u1.age); + expectAssignable(u1.toObject); + + const u2 = await UserModel.findOne().select<{ name?: string }>(['name']).orFail(); + expectType(u2.name); + expectError(u2.age); + expectAssignable(u2.toObject); + + const users = await UserModel.find().select<{ name?: string }>(['name']); + const u3 = users[0]; + expectType(u3!.name); + expectError(u3!.age); + expectAssignable(u3.toObject); expectError(UserModel.findOne().select<{ notInSchema: string }>(['name']).orFail()); } diff --git a/types/query.d.ts b/types/query.d.ts index 6932a565666..ab05d38dd83 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -622,13 +622,29 @@ declare module 'mongoose' { ): QueryWithHelpers; /** Specifies which document fields to include or exclude (also known as the query "projection") */ - select( + select( arg: string | string[] | Record ): QueryWithHelpers< - UnpackedIntersection<{}, Paths>, + IfEquals< + RawDocTypeOverride, + {}, + ResultType, + ResultType extends any[] ? + ResultType extends HydratedDocument[] ? + HydratedDocument[] : + RawDocTypeOverride[] : + ResultType extends HydratedDocument ? + HydratedDocument : + RawDocTypeOverride + >, DocType, THelpers, - UnpackedIntersection<{}, Paths>, + IfEquals< + RawDocTypeOverride, + {}, + RawDocType, + RawDocTypeOverride + >, QueryOp >; From 848f8b71d42512bbed3a30affb847df9c67758ad Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:15:54 -0400 Subject: [PATCH 19/23] not working --- lib/model.js | 19 +++++++++++++++++++ test/versioning.test.js | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/model.js b/lib/model.js index 56168cccb72..db4917b3ef0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -356,12 +356,31 @@ Model.prototype.$__handleSave = function(options, callback) { const optionsWithCustomValues = Object.assign({}, options, saveOptions); const where = this.$__where(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; + console.log('what is optimisticConcurrency', optimisticConcurrency) if (optimisticConcurrency) { const key = this.$__schema.options.versionKey; const val = this.$__getValue(key); if (val != null) { where[key] = val; } + if (Array.isArray(optimisticConcurrency) && optimisticConcurrency.every(x => typeof x === 'string')) { + const modPaths = this.modifiedPaths(); + console.log('what is modPaths', modPaths) + for (let i = 0; i < optimisticConcurrency.length; i ++) { + if (modPaths.includes(optimisticConcurrency[i])) { + // apply optimistic concurrency? + this.constructor.collection.findOne(where, optionsWithCustomValues) + .then(documentExists => { + const matchedCount = !documentExists ? 0 : 1; + callback(null, { $where: where, matchedCount }); + }) + .catch(callback); + } else { + continue; + } + } + return; + } } this.constructor.collection.findOne(where, optionsWithCustomValues) .then(documentExists => { diff --git a/test/versioning.test.js b/test/versioning.test.js index 764c1d2a410..98a90494b3a 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -512,6 +512,23 @@ describe('versioning', function() { assert.equal(err.name, 'VersionError'); }); + it('should support optimisticConcurrency being an array of strings asdf', async function() { + const thingSchema = new Schema({ price: Number, name: String }, { optimisticConcurrency: ['price', 'name'] }); + const Thing = db.model('Thing', thingSchema); + + const thing = await Thing.create({ price: 1, name: 'Test' }); + await thing.save(); + assert.equal(thing.__v, 0); + const thing_1 = await Thing.findById(thing.id); + const thing_2 = await Thing.findById(thing.id); + thing_1.set({ price: 2, name: 'Testerson' }); + await thing_1.save(); + assert.equal(thing_1.__v, 1); + thing_2.set({ price: 1, name: 'Test' }); + const err = await thing_2.save().then(() => null, err => err); + assert.equal(err.name, 'VersionError'); + }); + it('gh-1898', async function() { const schema = new Schema({ tags: [String], name: String }); From 40528ce119a1f1114cc3067daa8e3dc43f66bf3f Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 13 Jun 2023 17:51:16 -0400 Subject: [PATCH 20/23] set version if modified path and optCon is array --- lib/model.js | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/lib/model.js b/lib/model.js index db4917b3ef0..de8a128c66c 100644 --- a/lib/model.js +++ b/lib/model.js @@ -356,31 +356,12 @@ Model.prototype.$__handleSave = function(options, callback) { const optionsWithCustomValues = Object.assign({}, options, saveOptions); const where = this.$__where(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; - console.log('what is optimisticConcurrency', optimisticConcurrency) if (optimisticConcurrency) { const key = this.$__schema.options.versionKey; const val = this.$__getValue(key); if (val != null) { where[key] = val; } - if (Array.isArray(optimisticConcurrency) && optimisticConcurrency.every(x => typeof x === 'string')) { - const modPaths = this.modifiedPaths(); - console.log('what is modPaths', modPaths) - for (let i = 0; i < optimisticConcurrency.length; i ++) { - if (modPaths.includes(optimisticConcurrency[i])) { - // apply optimistic concurrency? - this.constructor.collection.findOne(where, optionsWithCustomValues) - .then(documentExists => { - const matchedCount = !documentExists ? 0 : 1; - callback(null, { $where: where, matchedCount }); - }) - .catch(callback); - } else { - continue; - } - } - return; - } } this.constructor.collection.findOne(where, optionsWithCustomValues) .then(documentExists => { @@ -737,7 +718,17 @@ Model.prototype.$__delta = function() { const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { - this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; + if (Array.isArray(optimisticConcurrency)) { + const modPaths = this.modifiedPaths(); + for(let i = 0; i < modPaths.length; i++) { + if (modPaths.includes(optimisticConcurrency[i])) { + this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; + break; + } + } + } else { + this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; + } } if (!dirty.length && VERSION_ALL !== this.$__.version) { From 97dec73f9a663ef3298879b185ed1438d734c869 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Tue, 13 Jun 2023 18:01:04 -0400 Subject: [PATCH 21/23] don't apply optimistic concurrency if optCon array --- lib/model.js | 2 +- test/versioning.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/model.js b/lib/model.js index de8a128c66c..82ee7364348 100644 --- a/lib/model.js +++ b/lib/model.js @@ -356,7 +356,7 @@ Model.prototype.$__handleSave = function(options, callback) { const optionsWithCustomValues = Object.assign({}, options, saveOptions); const where = this.$__where(); const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; - if (optimisticConcurrency) { + if (optimisticConcurrency && !Array.isArray(optimisticConcurrency)) { const key = this.$__schema.options.versionKey; const val = this.$__getValue(key); if (val != null) { diff --git a/test/versioning.test.js b/test/versioning.test.js index 98a90494b3a..ae26c801bf8 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -512,7 +512,7 @@ describe('versioning', function() { assert.equal(err.name, 'VersionError'); }); - it('should support optimisticConcurrency being an array of strings asdf', async function() { + it('should support optimisticConcurrency being an array of strings', async function() { const thingSchema = new Schema({ price: Number, name: String }, { optimisticConcurrency: ['price', 'name'] }); const Thing = db.model('Thing', thingSchema); @@ -526,7 +526,7 @@ describe('versioning', function() { assert.equal(thing_1.__v, 1); thing_2.set({ price: 1, name: 'Test' }); const err = await thing_2.save().then(() => null, err => err); - assert.equal(err.name, 'VersionError'); + assert.ok(!err) }); it('gh-1898', async function() { From 9e08552f7d285f270a2f00fd2433a2c8113199a3 Mon Sep 17 00:00:00 2001 From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:03:41 -0400 Subject: [PATCH 22/23] made requested fixes --- lib/model.js | 8 +++----- test/versioning.test.js | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/model.js b/lib/model.js index 82ee7364348..052414d2b76 100644 --- a/lib/model.js +++ b/lib/model.js @@ -719,12 +719,10 @@ Model.prototype.$__delta = function() { const optimisticConcurrency = this.$__schema.options.optimisticConcurrency; if (optimisticConcurrency) { if (Array.isArray(optimisticConcurrency)) { + const optCon = new Set(optimisticConcurrency); const modPaths = this.modifiedPaths(); - for(let i = 0; i < modPaths.length; i++) { - if (modPaths.includes(optimisticConcurrency[i])) { - this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; - break; - } + if (modPaths.find(path => optCon.has(path))) { + this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; } } else { this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE; diff --git a/test/versioning.test.js b/test/versioning.test.js index ae26c801bf8..cbae5b1e928 100644 --- a/test/versioning.test.js +++ b/test/versioning.test.js @@ -526,7 +526,7 @@ describe('versioning', function() { assert.equal(thing_1.__v, 1); thing_2.set({ price: 1, name: 'Test' }); const err = await thing_2.save().then(() => null, err => err); - assert.ok(!err) + assert.ok(!err); }); it('gh-1898', async function() { From e1dae1aebe0db881cd857c34977f7b0f0b380e75 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 14 Jun 2023 12:47:07 -0400 Subject: [PATCH 23/23] feat(aggregate): add `Aggregate.prototype.finally()` to be consistent with Promise API for TypeScript --- lib/aggregate.js | 17 ++++++++++++++++- types/aggregate.d.ts | 9 +++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/aggregate.js b/lib/aggregate.js index e2dace83389..a160f57420c 100644 --- a/lib/aggregate.js +++ b/lib/aggregate.js @@ -1094,7 +1094,7 @@ Aggregate.prototype.then = function(resolve, reject) { }; /** - * Executes the query returning a `Promise` which will be + * Executes the aggregation returning a `Promise` which will be * resolved with either the doc(s) or rejected with the error. * Like [`.then()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.then), but only takes a rejection handler. * Compatible with `await`. @@ -1108,6 +1108,21 @@ Aggregate.prototype.catch = function(reject) { return this.exec().then(null, reject); }; +/** + * Executes the aggregate returning a `Promise` which will be + * resolved with `.finally()` chained. + * + * More about [Promise `finally()` in JavaScript](https://thecodebarbarian.com/using-promise-finally-in-node-js.html). + * + * @param {Function} [onFinally] + * @return {Promise} + * @api public + */ + +Aggregate.prototype.finally = function(onFinally) { + return this.exec().finally(onFinally); +}; + /** * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js) * You do not need to call this function explicitly, the JavaScript runtime diff --git a/types/aggregate.d.ts b/types/aggregate.d.ts index 9c7a5f5a51e..29b4776833e 100644 --- a/types/aggregate.d.ts +++ b/types/aggregate.d.ts @@ -16,6 +16,9 @@ declare module 'mongoose' { */ [Symbol.asyncIterator](): AsyncIterableIterator>; + // Returns a string representation of this aggregation. + [Symbol.toStringTag]: string; + options: AggregateOptions; /** @@ -73,6 +76,12 @@ declare module 'mongoose' { /** Appends a new $fill operator to this aggregate pipeline */ fill(arg: PipelineStage.Fill['$fill']): this; + /** + * Executes the aggregation returning a `Promise` which will be + * resolved with `.finally()` chained. + */ + finally: Promise['finally']; + /** Appends new custom $graphLookup operator(s) to this aggregate pipeline, performing a recursive search on a collection. */ graphLookup(options: PipelineStage.GraphLookup['$graphLookup']): this;