types(models): propagate inferred Model type to QueryWithHelpers to support methods on docs returned from statics#16233
types(models): propagate inferred Model type to QueryWithHelpers to support methods on docs returned from statics#16233
Conversation
…upport methods on docs returned from statics Fix #15532
There was a problem hiding this comment.
Pull request overview
This PR updates Mongoose’s TypeScript model method typings so that when a model static returns a query, the query’s inferred document type can be derived from this (the concrete model), preserving instance method typings on documents returned from statics (fixes #15532).
Changes:
- Introduces helper types to derive the result document type from the calling model constructor (
this). - Updates many
Modelquery-returning methods (e.g.findOne,find,findById,updateOne,distinct, etc.) to infer the “doc type” parameter forQueryWithHelpersfromthis. - Adds a TypeScript regression test case for #15532.
Reviewed changes
Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
types/models.d.ts |
Propagates model-derived result doc types through QueryWithHelpers via this-inferred generics. |
test/types/models.test.ts |
Adds a type-level regression test intended to cover #15532 behavior. |
| expect(foundDocsAfterUpdate[0].updateName('bar')).type.toBe<Promise<UserModel>>(); | ||
|
|
||
| const foundDocsAfterDistinct = await UserModel.distinct('name').find({ name: 'foo' }); | ||
| expect(foundDocsAfterDistinct[0].updateName('baz')).type.toBe<Promise<UserModel>>(); |
There was a problem hiding this comment.
This at first glance this looks good, but this somehow changes the return type of Model.findOne()(and likely others) to be Class instead of Document<Class>. This happens when the model type is manually created by using Model</**/> & typeof Class for adding statics onto the model. This is used by typegoose currently. Example Code:
async function repro() {
class TestClass {
public someProp!: string;
// the following functions do not affect the type, but are here for illustrative purposes
someFunc(this: HydratedDocument<TestClass>) {
return this.someProp;
}
static someStaticFunc(this: TestClassModel) {
return this.findOne();
}
}
const schema = new Schema({ someProp: String });
schema.loadClass(TestClass);
type TestClassModel = Model<TestClass> & typeof TestClass;
const model = mongoose.model('test', schema) as TestClassModel;
// Type: const foundDoc: TestClass | null
// Should be: const foundDoc: Document<TestClass> | null
const foundDoc = await model.findOne();
// Type: const foundDoc: TestClass | null
// Should be: const foundDoc: Document<TestClass> | null
const anotherDoc = await model.someStaticFunc();
}Edit: i found a workaround (might also be recommended, if i look at typescript: statics) changing & typeof Class to Pick<typeof Class, keyof typeof Class>
|
I'm going to close this PR for now and consider alternative approaches. Copilot's comment is valid: as written, this PR does nothing for |
Fix #15532
Summary
#15532 pointed out that if you have a model static that returns a query, the resulting document will not have methods if you are using automatic schema inference. This PR makes all model functions that return queries infer the model type from
this, which seems to solve the issue. By inferringthis, Mongoose models can get the "real model type" when creating the query.Examples