From 6c424db854560151f108486124aa423ba5924a2d Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 21 Apr 2026 15:59:07 -0400 Subject: [PATCH 1/2] types(models): propagate inferred Model type to QueryWithHelpers to support methods on docs returned from statics Fix #15532 --- test/types/models.test.ts | 36 ++++++ types/models.d.ts | 253 +++++++++++++++++++++++--------------- 2 files changed, 190 insertions(+), 99 deletions(-) diff --git a/test/types/models.test.ts b/test/types/models.test.ts index a9fba72c32..6164f43357 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -250,6 +250,42 @@ function inheritance() { } } +async function gh15532() { + const userSchema = new Schema({ + name: { type: String, required: true } + }); + + const BaseUserModel = model('User', userSchema); + class UserModel extends BaseUserModel { + updateName(name: string) { + this.name = name; + return this.save(); + } + + static findByName(name: string) { + return this.findOne({ name }); + } + } + + const foundDoc = await UserModel.findOne({ name: 'test' }).orFail(); + expect(foundDoc.updateName('foo')).type.toBe>(); + + type ExplicitUserDoc = HydratedDocument<{ name: string }>; + const foundExplicitDoc = await UserModel.findOne({ name: 'test' }).orFail(); + expect(foundExplicitDoc).type.toBe(); + const foundExplicitDocById = await UserModel.findById('0'.repeat(24)).orFail(); + expect(foundExplicitDocById).type.toBe(); + + const foundDocFromStatic = await UserModel.findByName('test').orFail(); + expect(foundDocFromStatic.updateName('foo')).type.toBe>(); + + const foundDocsAfterUpdate = await UserModel.updateOne({ name: 'test' }, { name: 'foo' }).find({ name: 'foo' }); + expect(foundDocsAfterUpdate[0].updateName('bar')).type.toBe>(); + + const foundDocsAfterDistinct = await UserModel.distinct('name').find({ name: 'foo' }); + expect(foundDocsAfterDistinct[0].updateName('baz')).type.toBe>(); +} + Project.createCollection({ expires: '5 seconds' }); Project.createCollection({ expireAfterSeconds: 5 }); expect(Project.createCollection).type.not.toBeCallableWith({ expireAfterSeconds: '5 seconds' }); diff --git a/types/models.d.ts b/types/models.d.ts index ff99e26301..a1a9ff1a8b 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -210,6 +210,13 @@ declare module 'mongoose' { ObtainSchemaGeneric['lean'] : false; + type ModelConstructor = new (...args: any) => any; + type DefaultModelResultDoc = { readonly __mongooseDefaultModelResultDoc?: never }; + type ModelResultType = InstanceType; + type ModelThis = TModel; + type ModelResultDoc = + [TResultDoc] extends [DefaultModelResultDoc] ? ModelResultType : TResultDoc; + /** * Models are fancy constructors compiled from `Schema` definitions. * An instance of a model is called a document. @@ -282,12 +289,13 @@ declare module 'mongoose' { collection: Collection; /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ - countDocuments( + countDocuments( + this: ModelThis, filter?: QueryFilter, options?: (mongodb.CountOptions & MongooseBaseQueryOptions & mongodb.Abortable) | null ): QueryWithHelpers< number, - THydratedDocumentType, + ModelResultType, TQueryHelpers, TRawDocType, 'countDocuments', @@ -341,12 +349,13 @@ declare module 'mongoose' { * Behaves like `remove()`, but deletes all documents that match `conditions` * regardless of the `single` option. */ - deleteMany( + deleteMany( + this: ModelThis, filter?: QueryFilter, options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< mongodb.DeleteResult, - THydratedDocumentType, + ModelResultType, TQueryHelpers, TLeanResultType, 'deleteMany', @@ -369,12 +378,13 @@ declare module 'mongoose' { * Behaves like `remove()`, but deletes at most one document regardless of the * `single` option. */ - deleteOne( + deleteOne( + this: ModelThis, filter?: QueryFilter, options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< mongodb.DeleteResult, - THydratedDocumentType, + ModelResultType, TQueryHelpers, TLeanResultType, 'deleteOne', @@ -431,25 +441,27 @@ declare module 'mongoose' { * equivalent to `findOne({ _id: id })`. If you want to query by a document's * `_id`, use `findById()` instead of `findOne()`. */ - findById( + findById( + this: ModelThis, id: any, projection: ProjectionType | null | undefined, options: QueryOptions & { lean: true } ): QueryWithHelpers< TLeanResultType | null, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOne', TInstanceMethods & TVirtuals >; - findById( + findById( + this: ModelThis, id?: any, projection?: ProjectionType | null | undefined, options?: QueryOptions | null ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TLeanResultType | null : ModelResultDoc | null, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOne', @@ -457,13 +469,14 @@ declare module 'mongoose' { >; /** Finds one document. */ - findOne( + findOne( + this: ModelThis, filter: QueryFilter, projection: ProjectionType | null | undefined, options: QueryOptions & { lean: true } & mongodb.Abortable ): QueryWithHelpers< TLeanResultType | null, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOne', @@ -482,12 +495,25 @@ declare module 'mongoose' { TInstanceMethods & TVirtuals >; findOne( + filter: QueryFilter, + projection: ProjectionType | null | undefined, + options: QueryOptions & mongodb.Abortable | null | undefined + ): QueryWithHelpers< + HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, + ResultDoc, + TQueryHelpers, + TLeanResultType, + 'findOne', + TInstanceMethods & TVirtuals + >; + findOne( + this: ModelThis, filter?: QueryFilter, projection?: ProjectionType | null | undefined, options?: QueryOptions & mongodb.Abortable | null | undefined ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TLeanResultType | null : ModelResultDoc | null, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOne', @@ -656,7 +682,7 @@ declare module 'mongoose' { watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions & { hydrate?: boolean }): mongodb.ChangeStream; /** Adds a `$where` clause to this query */ - $where(argument: string | Function): QueryWithHelpers, THydratedDocumentType, TQueryHelpers, TRawDocType, 'find', TInstanceMethods & TVirtuals>; + $where(this: ModelThis, argument: string | Function): QueryWithHelpers>, ModelResultType, TQueryHelpers, TRawDocType, 'find', TInstanceMethods & TVirtuals>; /** Registered discriminators for this model. */ discriminators: { [name: string]: Model } | undefined; @@ -665,7 +691,8 @@ declare module 'mongoose' { translateAliases(raw: any): any; /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ - distinct( + distinct( + this: ModelThis, field: DocKey, filter?: QueryFilter, options?: QueryOptions @@ -675,7 +702,7 @@ declare module 'mongoose' { ? WithoutUndefined[DocKey]>> : unknown >, - THydratedDocumentType, + ModelResultType, TQueryHelpers, TLeanResultType, 'distinct', @@ -699,9 +726,9 @@ declare module 'mongoose' { >; /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */ - estimatedDocumentCount(options?: QueryOptions): QueryWithHelpers< + estimatedDocumentCount(this: ModelThis, options?: QueryOptions): QueryWithHelpers< number, - THydratedDocumentType, + ModelResultType, TQueryHelpers, TLeanResultType, 'estimatedDocumentCount', @@ -712,11 +739,12 @@ declare module 'mongoose' { * Returns a document with its `_id` if at least one document exists in the database that matches * the given `filter`, and `null` otherwise. */ - exists( + exists( + this: ModelThis, filter: QueryFilter ): QueryWithHelpers< { _id: InferId } | null, - THydratedDocumentType, + ModelResultType, TQueryHelpers, TLeanResultType, 'findOne', @@ -734,13 +762,14 @@ declare module 'mongoose' { >; /** Creates a `find` query: gets a list of documents that match `filter`. */ - find( + find( + this: ModelThis, filter: QueryFilter, projection: ProjectionType | null | undefined, options: QueryOptions & { lean: true } & mongodb.Abortable ): QueryWithHelpers< GetLeanResultType, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'find', @@ -758,13 +787,14 @@ declare module 'mongoose' { 'find', TInstanceMethods & TVirtuals >; - find( + find( + this: ModelThis, filter?: QueryFilter, projection?: ProjectionType | null | undefined, options?: QueryOptions & mongodb.Abortable ): QueryWithHelpers< - ResultDoc[], - ResultDoc, + ModelResultDoc[], + ModelResultDoc, TQueryHelpers, TLeanResultType, 'find', @@ -784,45 +814,49 @@ declare module 'mongoose' { >; /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ - findByIdAndDelete( + findByIdAndDelete( + this: ModelThis, id: mongodb.ObjectId | any, options: QueryOptions & { includeResultMetadata: true, lean: true } ): QueryWithHelpers< ModifyResult, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndDelete', TInstanceMethods & TVirtuals >; - findByIdAndDelete( + findByIdAndDelete( + this: ModelThis, id: mongodb.ObjectId | any, options: QueryOptions & { lean: true } ): QueryWithHelpers< TLeanResultType | null, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndDelete', TInstanceMethods & TVirtuals >; - findByIdAndDelete( + findByIdAndDelete( + this: ModelThis, id: mongodb.ObjectId | any, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers< - HasLeanOption extends true ? ModifyResult : ModifyResult, - ResultDoc, + HasLeanOption extends true ? ModifyResult : ModifyResult>, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndDelete', TInstanceMethods & TVirtuals >; - findByIdAndDelete( + findByIdAndDelete( + this: ModelThis, id?: mongodb.ObjectId | any, options?: QueryOptions | null ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TLeanResultType | null : ModelResultDoc | null, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndDelete', @@ -831,13 +865,14 @@ declare module 'mongoose' { /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ - findByIdAndUpdate( + findByIdAndUpdate( + this: ModelThis, filter: QueryFilter, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true, lean: true } ): QueryWithHelpers< ModifyResult, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', @@ -855,49 +890,53 @@ declare module 'mongoose' { 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findByIdAndUpdate( + findByIdAndUpdate( + this: ModelThis, id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { lean: true } ): QueryWithHelpers< TLeanResultType | null, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findByIdAndUpdate( + findByIdAndUpdate( + this: ModelThis, id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers< - HasLeanOption extends true ? ModifyResult : ModifyResult, - ResultDoc, + HasLeanOption extends true ? ModifyResult : ModifyResult>, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findByIdAndUpdate( + findByIdAndUpdate( + this: ModelThis, id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType : ResultDoc, - ResultDoc, + HasLeanOption extends true ? TLeanResultType : ModelResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findByIdAndUpdate( + findByIdAndUpdate( + this: ModelThis, id?: mongodb.ObjectId | any, update?: UpdateQuery, options?: QueryOptions | null ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TLeanResultType | null : ModelResultDoc | null, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', @@ -905,12 +944,13 @@ declare module 'mongoose' { >; /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ - findOneAndDelete( + findOneAndDelete( + this: ModelThis, filter: QueryFilter, options: QueryOptions & { lean: true } ): QueryWithHelpers< TLeanResultType | null, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndDelete', @@ -927,12 +967,13 @@ declare module 'mongoose' { 'findOneAndDelete', TInstanceMethods & TVirtuals >; - findOneAndDelete( + findOneAndDelete( + this: ModelThis, filter: QueryFilter, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers< - HasLeanOption extends true ? ModifyResult : ModifyResult, - ResultDoc, + HasLeanOption extends true ? ModifyResult : ModifyResult>, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndDelete', @@ -949,12 +990,13 @@ declare module 'mongoose' { 'findOneAndDelete', TInstanceMethods & TVirtuals >; - findOneAndDelete( + findOneAndDelete( + this: ModelThis, filter?: QueryFilter | null, options?: QueryOptions | null ): QueryWithHelpers< - HasLeanOption extends true ? TRawDocType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TRawDocType | null : ModelResultDoc | null, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndDelete', @@ -973,13 +1015,14 @@ declare module 'mongoose' { >; /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ - findOneAndReplace( + findOneAndReplace( + this: ModelThis, filter: QueryFilter, replacement: TRawDocType | AnyObject, options: QueryOptions & { lean: true } ): QueryWithHelpers< TLeanResultType | null, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndReplace', @@ -997,13 +1040,14 @@ declare module 'mongoose' { 'findOneAndReplace', TInstanceMethods & TVirtuals >; - findOneAndReplace( + findOneAndReplace( + this: ModelThis, filter: QueryFilter, replacement: TRawDocType | AnyObject, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers< - HasLeanOption extends true ? ModifyResult : ModifyResult, - ResultDoc, + HasLeanOption extends true ? ModifyResult : ModifyResult>, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndReplace', @@ -1021,13 +1065,14 @@ declare module 'mongoose' { 'findOneAndReplace', TInstanceMethods & TVirtuals >; - findOneAndReplace( + findOneAndReplace( + this: ModelThis, filter: QueryFilter, replacement: TRawDocType | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType : ResultDoc, - ResultDoc, + HasLeanOption extends true ? TLeanResultType : ModelResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndReplace', @@ -1045,13 +1090,14 @@ declare module 'mongoose' { 'findOneAndReplace', TInstanceMethods & TVirtuals >; - findOneAndReplace( + findOneAndReplace( + this: ModelThis, filter?: QueryFilter, replacement?: TRawDocType | AnyObject, options?: QueryOptions | null ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TLeanResultType | null : ModelResultDoc | null, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndReplace', @@ -1071,13 +1117,14 @@ declare module 'mongoose' { >; /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ - findOneAndUpdate( + findOneAndUpdate( + this: ModelThis, filter: QueryFilter, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true, lean: true } ): QueryWithHelpers< ModifyResult, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', @@ -1095,13 +1142,14 @@ declare module 'mongoose' { 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findOneAndUpdate( + findOneAndUpdate( + this: ModelThis, filter: QueryFilter, update: UpdateQuery, options: QueryOptions & { lean: true } ): QueryWithHelpers< GetLeanResultType | null, - ResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', @@ -1119,13 +1167,14 @@ declare module 'mongoose' { 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findOneAndUpdate( + findOneAndUpdate( + this: ModelThis, filter: QueryFilter, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers< - HasLeanOption extends true ? ModifyResult : ModifyResult, - ResultDoc, + HasLeanOption extends true ? ModifyResult : ModifyResult>, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', @@ -1143,13 +1192,14 @@ declare module 'mongoose' { 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findOneAndUpdate( + findOneAndUpdate( + this: ModelThis, filter: QueryFilter, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType : ResultDoc, - ResultDoc, + HasLeanOption extends true ? TLeanResultType : ModelResultDoc, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', @@ -1167,25 +1217,26 @@ declare module 'mongoose' { 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findOneAndUpdate( + findOneAndUpdate( + this: ModelThis, filter?: QueryFilter, update?: UpdateQuery, options?: QueryOptions | null ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TLeanResultType | null : ModelResultDoc | null, + ModelResultDoc, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', TInstanceMethods & TVirtuals >; - findOneAndUpdate( + findOneAndUpdate( filter?: Query, update?: UpdateQuery, options?: QueryOptions | null ): QueryWithHelpers< - HasLeanOption extends true ? TLeanResultType | null : ResultDoc | null, - ResultDoc, + HasLeanOption extends true ? TLeanResultType | null : THydratedDocumentType | null, + THydratedDocumentType, TQueryHelpers, TLeanResultType, 'findOneAndUpdate', @@ -1193,11 +1244,12 @@ declare module 'mongoose' { >; /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */ - replaceOne( + replaceOne( + this: ModelThis, filter?: QueryFilter, replacement?: TRawDocType | AnyObject, options?: (mongodb.ReplaceOptions & QueryOptions) | null - ): QueryWithHelpers; + ): QueryWithHelpers, TQueryHelpers, TLeanResultType, 'replaceOne', TInstanceMethods & TVirtuals>; replaceOne( filter?: Query, replacement?: TRawDocType | AnyObject, @@ -1211,11 +1263,12 @@ declare module 'mongoose' { schema: TSchema; /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */ - updateMany( + updateMany( + this: ModelThis, filter: QueryFilter, update: UpdateQuery | UpdateWithAggregationPipeline, options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions) | null - ): QueryWithHelpers; + ): QueryWithHelpers, TQueryHelpers, TLeanResultType, 'updateMany', TInstanceMethods & TVirtuals>; updateMany( filter: Query, update: UpdateQuery | UpdateWithAggregationPipeline, @@ -1223,11 +1276,12 @@ declare module 'mongoose' { ): QueryWithHelpers; /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ - updateOne( + updateOne( + this: ModelThis, filter: QueryFilter, update: UpdateQuery | UpdateWithAggregationPipeline, options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions) | null - ): QueryWithHelpers; + ): QueryWithHelpers, TQueryHelpers, TLeanResultType, 'updateOne', TInstanceMethods & TVirtuals>; updateOne( filter: Query, update: UpdateQuery | UpdateWithAggregationPipeline, @@ -1235,21 +1289,22 @@ declare module 'mongoose' { ): QueryWithHelpers; /** Creates a Query, applies the passed conditions, and returns the Query. */ - where( + where( + this: ModelThis, path: string, val?: any - ): QueryWithHelpers extends true ? TRawDocType[] : ResultDoc[], ResultDoc, TQueryHelpers, TRawDocType, 'find', TInstanceMethods>; - where(obj: object): QueryWithHelpers< - HasLeanOption extends true ? TRawDocType[] : ResultDoc[], - ResultDoc, + ): QueryWithHelpers extends true ? TRawDocType[] : ModelResultDoc[], ModelResultDoc, TQueryHelpers, TRawDocType, 'find', TInstanceMethods>; + where(this: ModelThis, obj: object): QueryWithHelpers< + HasLeanOption extends true ? TRawDocType[] : ModelResultDoc[], + ModelResultDoc, TQueryHelpers, TLeanResultType, 'find', TInstanceMethods & TVirtuals >; - where(): QueryWithHelpers< - HasLeanOption extends true ? TRawDocType[] : ResultDoc[], - ResultDoc, + where(this: ModelThis): QueryWithHelpers< + HasLeanOption extends true ? TRawDocType[] : ModelResultDoc[], + ModelResultDoc, TQueryHelpers, TLeanResultType, 'find', From d6856b66ba078ba1a5c86cab72503a22577d5f27 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 21 Apr 2026 16:06:47 -0400 Subject: [PATCH 2/2] test(types): add coverage handling explicit schema generics re: #15532 --- test/types/models.test.ts | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 6164f43357..ddc37f4954 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -286,6 +286,98 @@ async function gh15532() { expect(foundDocsAfterDistinct[0].updateName('baz')).type.toBe>(); } +async function gh15532ExplicitSchemaGeneric() { + interface IUser { + name: string; + } + + const userSchema = new Schema({ + name: { type: String, required: true } + }); + + const BaseUserModel = model('ExplicitGenericUser', userSchema); + class UserModel extends BaseUserModel { + updateName(name: string) { + this.name = name; + return this.save(); + } + + static findByName(name: string) { + return this.findOne({ name }); + } + } + + const foundDoc = await UserModel.findOne({ name: 'test' }).orFail(); + expect(foundDoc.name).type.toBe(); + expect(foundDoc.updateName('foo')).type.toBe>(); + + const foundById = await UserModel.findById('0'.repeat(24)).orFail(); + expect(foundById.updateName('foo')).type.toBe>(); + + const foundDocs = await UserModel.find({ name: 'test' }); + expect(foundDocs[0].updateName('foo')).type.toBe>(); + + const updatedDoc = await UserModel.findOneAndUpdate({ name: 'test' }, { name: 'foo' }, { new: true }).orFail(); + expect(updatedDoc.updateName('bar')).type.toBe>(); + + type ExplicitUserDoc = HydratedDocument<{ name: string; extra: number }>; + const foundExplicit = await UserModel.findOne({ name: 'test' }).orFail(); + expect(foundExplicit).type.toBe(); + expect(foundExplicit.extra).type.toBe(); + + const foundExplicitById = await UserModel.findById('0'.repeat(24)).orFail(); + expect(foundExplicitById).type.toBe(); + + const foundExplicitMany = await UserModel.find({ name: 'test' }); + expect(foundExplicitMany[0]).type.toBe(); + + const foundFromStatic = await UserModel.findByName('test').orFail(); + expect(foundFromStatic.updateName('foo')).type.toBe>(); + + const updatedThenFound = await UserModel.updateOne({ name: 'test' }, { name: 'foo' }).find({ name: 'foo' }); + expect(updatedThenFound[0].updateName('bar')).type.toBe>(); +} + +async function gh15532ExplicitSchemaAndModelGeneric() { + interface IUser { + name: string; + } + interface IUserMethods { + schemaMethod(): string; + } + type UserModelType = Model; + + const userSchema = new Schema({ + name: { type: String, required: true } + }, { + methods: { + schemaMethod() { + return this.name; + } + } + }); + + const BaseUserModel = model('ExplicitGenericUser2', userSchema); + class UserModel extends BaseUserModel { + updateName(name: string) { + this.name = name; + return this.save(); + } + + static findByName(name: string) { + return this.findOne({ name }); + } + } + + const foundDoc = await UserModel.findOne({ name: 'test' }).orFail(); + expect(foundDoc.updateName('foo')).type.toBe>(); + expect(foundDoc.schemaMethod()).type.toBe(); + + const foundDocFromStatic = await UserModel.findByName('test').orFail(); + expect(foundDocFromStatic.updateName('foo')).type.toBe>(); + expect(foundDocFromStatic.schemaMethod()).type.toBe(); +} + Project.createCollection({ expires: '5 seconds' }); Project.createCollection({ expireAfterSeconds: 5 }); expect(Project.createCollection).type.not.toBeCallableWith({ expireAfterSeconds: '5 seconds' });