From 5ccad6e74298d831935d502e082810317a5cc97b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 2 Oct 2022 22:24:29 -0400 Subject: [PATCH 1/2] fix(types): correct DocType for auto typed query helpers Re: #12342 --- test/types/queries.test.ts | 63 ++++++++++++++++++++++++++++++++++++++ types/schemaoptions.d.ts | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index 0725c42855b..239e5eeb488 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -359,3 +359,66 @@ function gh12142() { } ); } + +async function gh12342_manual() { + interface Project { + name?: string, stars?: number + } + + interface ProjectQueryHelpers { + byName(name: string): QueryWithHelpers< + HydratedDocument[], + HydratedDocument, + ProjectQueryHelpers + > + } + + type ProjectModelType = Model; + + const ProjectSchema = new Schema< + Project, + Model, + {}, + ProjectQueryHelpers + >({ + name: String, + stars: Number + }); + + ProjectSchema.query.byName = function byName( + this: QueryWithHelpers, ProjectQueryHelpers>, + name: string + ) { + return this.find({ name: name }); + }; + + // 2nd param to `model()` is the Model class to return. + const ProjectModel = model('Project', schema); + + expectType[]>( + await ProjectModel.findOne().where('stars').gt(1000).byName('mongoose') + ); +} + +async function gh12342_auto() { + interface Project { + name?: string, stars?: number + } + + const ProjectSchema = new Schema({ + name: String, + stars: Number + }, { + query: { + byName(name: string) { + return this.find({ name }); + } + } + }); + + const ProjectModel = model('Project', ProjectSchema); + + expectType[]>( + await ProjectModel.findOne().where('stars').gt(1000).byName('mongoose') + ); +} diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 1a87a8ccf9f..c3d35bd4272 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -201,7 +201,7 @@ declare module 'mongoose' { /** * Query helper functions. */ - query?: Record>(this: T, ...args: any) => T> | QueryHelpers, + query?: Record>>(this: T, ...args: any) => T> | QueryHelpers, /** * Set whether to cast non-array values to arrays. From 1f9517035216494786721b2591c4fcd00b3a0e42 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 2 Oct 2022 22:24:47 -0400 Subject: [PATCH 2/2] docs(typescript): clean up query helpers examples Fix #12342 --- docs/typescript/query-helpers.md | 70 ++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/docs/typescript/query-helpers.md b/docs/typescript/query-helpers.md index 7c108d465f2..de6bb1daf60 100644 --- a/docs/typescript/query-helpers.md +++ b/docs/typescript/query-helpers.md @@ -3,6 +3,8 @@ [Query helpers](http://thecodebarbarian.com/mongoose-custom-query-methods.html) let you define custom helper methods on Mongoose queries. Query helpers make queries more semantic using chaining syntax. +The following is an example of how query helpers work in JavaScript. + ```javascript ProjectSchema.query.byName = function(name) { return this.find({ name: name }); @@ -14,9 +16,9 @@ var Project = mongoose.model('Project', ProjectSchema); Project.find().where('stars').gt(1000).byName('mongoose'); ``` -In TypeScript, Mongoose does support manually typed and automatically typed Query Helpers. +## Manually Typed Query Helpers -1- Manually typed: +In TypeScript, you can define query helpers using a separate query helpers interface. Mongoose's `Model` takes 3 generic parameters: 1. The `DocType` @@ -30,23 +32,34 @@ Below is an example of creating a `ProjectModel` with a `byName` query helper. import { HydratedDocument, Model, Query, Schema, model } from 'mongoose'; interface Project { - name: string; - stars: number; + name?: string; + stars?: number; } -type ProjectModelType = Model; -// Query helpers should return `Query> & ProjectQueryHelpers` -// to enable chaining. -type ProjectModelQuery = Query, ProjectQueryHelpers> & ProjectQueryHelpers; interface ProjectQueryHelpers { - byName(this: ProjectModelQuery, name: string): ProjectModelQuery; + byName(name: string): QueryWithHelpers< + HydratedDocument[], + HydratedDocument, + ProjectQueryHelpers + > } -const schema = new Schema({ - name: { type: String, required: true }, - stars: { type: Number, required: true } +type ProjectModelType = Model; + +const ProjectSchema = new Schema< + Project, + Model, + {}, + ProjectQueryHelpers +>({ + name: String, + stars: Number }); -schema.query.byName = function(name: string): ProjectModelQuery { + +ProjectSchema.query.byName = function byName( + this: QueryWithHelpers, ProjectQueryHelpers>, + name: string +) { return this.find({ name: name }); }; @@ -63,30 +76,27 @@ async function run(): Promise { } ``` -2- Automatically typed: +## Auto Typed Query Helpers + Mongoose does support auto typed Query Helpers that it are supplied in schema options. Query Helpers functions can be defined as following: ```typescript import { Schema, model } from 'mongoose'; - const schema = new Schema({ - name: { type: String, required: true }, - stars: { type: Number, required: true } - }, { - query: { - byName(name) { - return this.find({ name: name }); - } +const ProjectSchema = new Schema({ + name: String, + stars: Number +}, { + query: { + byName(name: string) { + return this.find({ name }); } - }); + } +}); - const ProjectModel = model( - 'Project', - schema - ); +const ProjectModel = model('Project', ProjectSchema); - // Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })` - await ProjectModel.find().where('stars').gt(1000).byName('mongoose'); -} +// Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })` +await ProjectModel.find().where('stars').gt(1000).byName('mongoose'); ``` \ No newline at end of file