-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to write a generic function to resolve PopulatedDoc type variables? #15121
Comments
The approach is reasonable, you just need to type Take a look at the following script, would this approach work for you? import mongoose, { Schema } from 'mongoose';
const ParentModel = mongoose.model('Parent', new Schema({
child: { type: Schema.Types.ObjectId, ref: 'Child' },
name: String
}));
const childSchema: Schema = new Schema({ name: String });
const ChildModel = mongoose.model('Child', childSchema);
type ChildHydratedDocType = ReturnType<(typeof ChildModel)['hydrate']>;
(async function() {
const doc = await ParentModel.findOne({}).populate<{ child: ChildHydratedDocType }>('child').orFail();
const res1: ChildHydratedDocType = await findOrReturnInstance(doc.child, ChildModel);
const doc2 = await ParentModel.findOne().orFail();
const res2: ChildHydratedDocType = await findOrReturnInstance(doc.child, ChildModel);
})();
async function findOrReturnInstance<HydratedDocType extends mongoose.Document>(
docOrId: HydratedDocType | mongoose.Types.ObjectId,
Model: mongoose.Model<any, any, any, any, HydratedDocType>
) {
if (docOrId instanceof mongoose.Document) {
return docOrId;
}
return Model.findById(docOrId).orFail();
} |
The script is fine, although I would have preferred the type to be inferred already by I have another question: if I avoid using function fn(p: ParentInstance) {
// TODO
}
const parent = await Parent.findOne({}).populate<{ child: ChildInstance }>('child').orFail();
// There appears to be a compilation error here, because if I populate child, it will no longer be of type ObjectId.
fn(parent); |
Can you please elaborate on "although I would have preferred the type to be inferred already by findOrReturnInstance and not specified manually."? Re: |
Let me explain better: in the example you wrote, for |
Yeah the script compiles fine if you remove |
Yes, the script compiles fine, but if you remove the types from the Reproduction link here Do you think there's a way to make the function return the correct type without having to specify it manually each time? |
How about the following? I explicitly set the return type on import mongoose, { Schema } from 'mongoose';
const ParentModel = mongoose.model('Parent', new Schema({
child: { type: Schema.Types.ObjectId, ref: 'Child' },
name: String
}));
const childSchema = new Schema({ name: String });
const ChildModel = mongoose.model('Child', childSchema);
type ChildHydratedDocType = ReturnType<(typeof ChildModel)['hydrate']>;
(async function() {
const doc = await ParentModel.findOne({}).populate<{ child: ChildHydratedDocType }>('child').orFail();
const res1 = await findOrReturnInstance(doc.child, ChildModel);
const doc2 = await ParentModel.findOne().orFail();
const res2 = await findOrReturnInstance(doc.child, ChildModel);
const name1 = res1.name;
const name2 = res2.name;
f2(res1, name1);
f2(res2, name2);
})();
async function findOrReturnInstance<HydratedDocType extends mongoose.Document>(
docOrId: HydratedDocType | mongoose.Types.ObjectId,
Model: mongoose.Model<any, any, any, any, HydratedDocType>
): Promise<HydratedDocType> {
if (docOrId instanceof Model) {
return docOrId;
}
return Model.findById(docOrId).orFail();
}
function f2(doc: ChildHydratedDocType, name?: string | null) {
console.log(doc.name, name, doc.save());
} Compiles correctly, and my editor ([email protected]) seems to pick up correct type as shown in the following screenshot |
Yes, with the changes you introduced, the returned type is correct in this example. Explicitly specifying the type: Not explicitly specifying the type: Therefore, by not explicitly specifying the type, any embedded types will be taken from the main model interface rather than from the virtuals and/or document overrides interfaces. Do you think it is possible to resolve this last issue as well? Thank you for your help. 🙏🏻 |
Simply removing export interface IParent {
code: string;
name: string;
child: Types.ObjectId;
createdAt: Date;
updatedAt: Date;
} Another option if you want to keep export interface IParent {
code: string;
name: string;
child: PopulatedDoc<HydratedDocument<IChild>>;
createdAt: Date;
updatedAt: Date;
} As for "I cannot verify whether child has been populated or not", I don't think that is correct. You have a couple of options there.
Further, I think Another alternative you might consider is populate virtuals, which is a bit cleaner in this approach since a populate virtual is |
I agree with you that for a consistent data shape, it's better to populate all the data at once. However, my case is more complex: in my project, I use GraphQL, and the sample function I wrote when I opened the issue is often used in the resolvers for fields of type PopulatedDoc. In the resolver, I don't know if a populate has been done previously or not. Moreover, if a GraphQL query only extracts "id" and "name" for the Parent model, for performance reasons, it is not necessary and I don't want the populate on "child" to be executed, as it will not be extracted. That said, by modifying the typing to I have updated the reproduction link. |
I finally found the solution, instead of But unfortunately I found another error 😫 I reported it in the demo. How can I fix it? Thank you. 🙏🏻 |
You're right, I didn't remember that |
I figured out why your demo is failing: you haven't installed We will make Mongoose more resilient to this weird TypeScript behavior (unknown type becomes |
Ok, thank you very much for the help! Now that the objective of this issue has been achieved, I can finally close it. If I encounter any further problems, I will open another issue. |
types: make type inference logic resilient to no Buffer type due to missing `@types/node`
Prerequisites
Mongoose version
8.9.1
Node.js version
20.10.0
MongoDB version
6.0.2
Operating system
macOS
Operating system version (i.e. 20.04, 11.3, 10)
No response
Issue
In my project, I often find myself having to implement this code:
Is it possible to write a generic function so that I don't have to rewrite the same exact code but with different models?
I tried with this code but it doesn't work:
Is it the right approach or am I doing something wrong?
Attention, with this generic function I want to ensure that any virtual variables and instance methods, etc., are preserved.
Thank you for your help. 🙏🏻
The text was updated successfully, but these errors were encountered: