-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
TS: _id not required on DocumentArray properties of documents returned from query #14660
Comments
I could do something like this: export const getParents = async (): Promise<ExpectedParent[]> => {
const parents = await ParentModel.find<ExpectedParent>();
return parents;
}; However, this seems a bit heavy-handed as I am now overriding the entirety of the types stored within the mongoose schemas. I ended up landing on this utility type: // Recursive type to enforce `_id` in all subdocuments
type EnforceIdInSubdocuments<T> = {
[K in keyof T]: T[K] extends mongoose.Types.DocumentArray<infer U>
? mongoose.Require_id<U>[]
: T[K] extends object
? EnforceIdInSubdocuments<T[K]>
: T[K];
};
// Utility type to extract and enforce _id in subdocuments
type ModelWithEnforcedIds<T extends mongoose.Model<any>> = EnforceIdInSubdocuments<
mongoose.InferSchemaType<T['schema']>
>; Full example: import mongoose from 'mongoose';
const ChildSchema = new mongoose.Schema({
name: { type: String, required: true },
});
const ParentSchema = new mongoose.Schema({
_id: { type: String, required: true },
name: { type: String, required: true },
parts: { type: [ChildSchema] },
});
const ParentModel = mongoose.model('Parent', ParentSchema);
export const getParents = async (): Promise<ExpectedParent[]> => {
const parents = await ParentModel.find<ModelWithEnforcedIds<typeof ParentModel>>();
return parents;
};
interface ExpectedParent {
_id: string;
name: string;
parts: Array<{ _id: string; name: string }>;
}
// Recursive type to enforce `_id` in all subdocuments
type EnforceIdInSubdocuments<T> = {
[K in keyof T]: T[K] extends mongoose.Types.DocumentArray<infer U>
? mongoose.Require_id<U>[]
: T[K] extends object
? EnforceIdInSubdocuments<T[K]>
: T[K];
};
// Utility type to extract and enforce _id in subdocuments
type ModelWithEnforcedIds<T extends mongoose.Model<any>> = EnforceIdInSubdocuments<
mongoose.InferSchemaType<T['schema']>
>; I will leave this open as I feel like there's probably a better way to do this. |
This looks like expected behavior: you expect your const ChildSchema = new mongoose.Schema({
_id: { type: String, required: true }, // <-- add _id here
name: { type: String, required: true },
}); Or, if you intend to have ObjectId _id for ChildSchema, do the following: const ChildSchema = new mongoose.Schema({
_id: { type: 'ObjectId', required: true }, // <-- change _id type here
name: { type: String, required: true },
});
interface ExpectedParent {
_id: string;
name: string;
parts: Array<{ _id: mongoose.Types.ObjectId; name: string }>; // <-- change _id type here
} Does this help? |
Thanks for the reply @vkarpov15.
From my vantage point, the issue is that Mongoose's types do not assume that the As you are aware, each subdocument has an |
Mongoose does add an |
What you said is true, however I don't believe that it's particularly important. If we review the last two lines of the TS error output, we see: Type 'ObjectId | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2322) The issue is about the To demonstrate this, we can update the example to something like this: import mongoose, { Mongoose } from 'mongoose';
const ChildSchema = new mongoose.Schema({
name: { type: String, required: true },
});
const ParentSchema = new mongoose.Schema({
// _id: { type: String, required: true },
name: { type: String, required: true },
parts: { type: [ChildSchema] },
});
const ParentModel = mongoose.model('Parent', ParentSchema);
export const getParents = async (): Promise<ExpectedParent[]> => {
const parents = await ParentModel.find();
return parents; // TypeScript error here!
};
interface ExpectedParent {
_id: mongoose.Types.ObjectId;
name: string;
parts: Array<{ _id: mongoose.Types.ObjectId; name: string }>;
} which results in the following error: Type '(Document<unknown, {}, { name: string; parts: DocumentArray<{ name: string; }>; }> & { name: string; parts: DocumentArray<{ name: string; }>; } & { _id: ObjectId; })[]' is not assignable to type 'ExpectedParent[]'.
Type 'Document<unknown, {}, { name: string; parts: DocumentArray<{ name: string; }>; }> & { name: string; parts: DocumentArray<{ name: string; }>; } & { _id: ObjectId; }' is not assignable to type 'ExpectedParent'.
The types returned by 'parts.pop()' are incompatible between these types.
Type '(Subdocument<ObjectId> & { name: string; }) | undefined' is not assignable to type '{ _id: ObjectId; name: string; } | undefined'.
Type 'Subdocument<ObjectId> & { name: string; }' is not assignable to type '{ _id: ObjectId; name: string; } | undefined'.
Type 'Subdocument<ObjectId> & { name: string; }' is not assignable to type '{ _id: ObjectId; name: string; }'.
Types of property '_id' are incompatible.
Type 'ObjectId | undefined' is not assignable to type 'ObjectId'.
Type 'undefined' is not assignable to type 'ObjectId'.ts(2322) The core issue is that subdocument arrays come back with possible undefined ID values. |
Workaround is to define const ChildSchema = new mongoose.Schema({
_id: { type: mongoose.Schema.ObjectId, required: true, default: () => new mongoose.Types.ObjectId() },
name: { type: String, required: true },
}); We're investigating to see if we can fix this issue |
types: make `_id` required on Document type
Prerequisites
Mongoose version
8.2.3
Node.js version
20.14.0
MongoDB version
?
Operating system
macOS
Operating system version (i.e. 20.04, 11.3, 10)
14.1
Issue
I have a Mongoose model which has a subdocument. I have a function that queries the model via the
.find()
method. The function's output is expected to conform to an output interface, however it is not playing nicely with the the auto-generated TypeScript types for the model. The mongoose docs state: "Each subdocument has an_id
by default", however the types for theDocumentArray
properties returned from afind()
operation do not have_id
marked as required.This feels like a bug on the Mongoose side but I am new to using the library so it's possible that I'm missing something.
Questions:
_id
property when a document is retrieved via the.find()
operations? (safe for any situations where I explicitly instruct Mongoose to not place an_id
on the subdocument, of course)mongoose
code itself, how can I customize the output of the types in a succinct manner? I'd prefer to not write my own types for the Schema and Model as mongoose does a good job inferring these values and I would like to avoid maintaining redundant code. I see that.find()
is a generic function (link), so I could provide my own type that instructsmongoose
that the_id
is required, however I'm struggling to achieve that.Simplified example:
I see the following error from TS:
The text was updated successfully, but these errors were encountered: