From a68cc5c4b723639e019e9a986b3abde97027e353 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 15 Apr 2024 12:59:30 -0400 Subject: [PATCH 1/3] types(query+populate): apply populate overrides to doc `toObject()` result Fix #14441 --- test/types/populate.test.ts | 46 +++++++++++++++++++++++++++++++++++++ types/query.d.ts | 16 +++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/test/types/populate.test.ts b/test/types/populate.test.ts index c846e7b346e..fe9afa4ef84 100644 --- a/test/types/populate.test.ts +++ b/test/types/populate.test.ts @@ -373,3 +373,49 @@ async function gh13070() { const doc2 = await Child.populate<{ child: IChild }>(doc, 'child'); const name: string = doc2.child.name; } + +function gh14441() { + 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 }); + model('Child', childSchema); + + ParentModel.findOne({}) + .populate<{ child: Child }>('child') + .orFail() + .then(doc => { + expectType(doc.child.name); + const docObject = doc.toObject(); + expectType(docObject.child.name); + }); + + ParentModel.findOne({}) + .populate<{ child: Child }>('child') + .lean() + .orFail() + .then(doc => { + expectType(doc.child.name); + }); + + ParentModel.find({}) + .populate<{ child: Child }>('child') + .orFail() + .then(docs => { + expectType(docs[0]!.child.name); + const docObject = docs[0]!.toObject(); + expectType(docObject.child.name); + }); +} diff --git a/types/query.d.ts b/types/query.d.ts index 1e97ad6331d..71e3a03ec24 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -205,6 +205,18 @@ declare module 'mongoose' { ? (ResultType extends any[] ? Require_id>[] : Require_id>) : ResultType; + type MergePopulatePaths = QueryOp extends QueryOpThatReturnsDocument + ? ResultType extends null + ? ResultType + : ResultType extends (infer U)[] + ? U extends Document + ? HydratedDocument, Record, TQueryHelpers>[] + : (MergeType)[] + : ResultType extends Document + ? HydratedDocument, Record, TQueryHelpers> + : MergeType + : MergeType; + class Query implements SessionOperation { _mongooseOptions: MongooseQueryOptions; @@ -608,7 +620,7 @@ declare module 'mongoose' { model?: string | Model, match?: any ): QueryWithHelpers< - UnpackedIntersection, + MergePopulatePaths, DocType, THelpers, UnpackedIntersection, @@ -617,7 +629,7 @@ declare module 'mongoose' { populate( options: PopulateOptions | (PopulateOptions | string)[] ): QueryWithHelpers< - UnpackedIntersection, + MergePopulatePaths, DocType, THelpers, UnpackedIntersection, From fe1ed868ac4813c7e3f04af3e40bc9ade268341c Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 23 Apr 2024 16:55:41 -0400 Subject: [PATCH 2/3] types(populate): more graceful handling of query `populate()` without generics re: #14525 --- types/query.d.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/types/query.d.ts b/types/query.d.ts index 71e3a03ec24..3145dfef477 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -614,7 +614,28 @@ declare module 'mongoose' { polygon(...coordinatePairs: number[][]): this; /** Specifies paths which should be populated with other documents. */ - populate( + populate( + path: string | string[], + select?: string | any, + model?: string | Model, + match?: any + ): QueryWithHelpers< + ResultType, + DocType, + THelpers, + RawDocType, + QueryOp + >; + populate( + options: PopulateOptions | (PopulateOptions | string)[] + ): QueryWithHelpers< + ResultType, + DocType, + THelpers, + RawDocType, + QueryOp + >; + populate( path: string | string[], select?: string | any, model?: string | Model, @@ -626,7 +647,7 @@ declare module 'mongoose' { UnpackedIntersection, QueryOp >; - populate( + populate( options: PopulateOptions | (PopulateOptions | string)[] ): QueryWithHelpers< MergePopulatePaths, From 5b2545ea000627c0d97cb01eb5baec47f24b1ac3 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 24 Apr 2024 16:47:47 -0400 Subject: [PATCH 3/3] test: add test case covering code review comments from #14525 --- test/types/queries.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index d0af45a42a5..297360b62d1 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -612,3 +612,27 @@ function gh14473() { const query2: FilterQuery = { deletedAt: { $lt: new Date() } }; }; } + +async function gh14525() { + type BeAnObject = Record; + + interface SomeDoc { + something: string; + func(this: TestDoc): string; + } + + interface PluginExtras { + pfunc(): number; + } + + type TestDoc = Document & PluginExtras; + + type ModelType = Model; + + const doc = await ({} as ModelType).findOne({}).populate('test').orFail().exec(); + + doc.func(); + + let doc2 = await ({} as ModelType).create({}); + doc2 = await ({} as ModelType).findOne({}).populate('test').orFail().exec(); +}