Move lean({ virtuals: true }) support from mongoose-lean-virtuals into core Mongoose
Prerequisites
- [X] I have written a descriptive issue title
- [X] I have searched existing issues to ensure the bug has not already been reported
Mongoose version
6.7.2
Node.js version
18.12.0
MongoDB server version
N/A
Typescript version (if applicable)
4.8.4
Description
InferSchemaType does not infer types for virtual fields when schema has any. According to this PR https://github.com/Automattic/mongoose/pull/11908, it should work.
Steps to Reproduce
- Copy example from mentioned PR https://github.com/Automattic/mongoose/pull/11908/files#diff-fc5a8155d58dc5904b33fcc871f11ee08cb6f6e7adbe69e7bfbd7d173e82d8d2 (
Automatically Inferred Types), create a type withInferSchemaTypeand try to accessfullName:
import { Schema, InferSchemaType } from 'mongoose';
const schema = new Schema(
{
firstName: String,
lastName: String,
},
{
virtuals:{
fullName:{
get(){
return `${this.firstName} ${this.lastName}`;
}
// virtual setter and options can be defined here as well.
}
}
}
);
type User = InferSchemaType<typeof schema>;
const user: User = {};
user.fullName
- You will get the following error:
TS2339: Property 'fullName' does not exist on type '{ firstName?: string | undefined; lastName?: string | undefined; }'.
Expected Behavior
Type User should contain fullName: string field, and user.fullName shouldn't fail TS compilation.
As a work around, you can use the ObtainSchemaGeneric type to add the virtual types:
import { Schema, InferSchemaType, ObtainSchemaGeneric } from 'mongoose';
...
type User = InferSchemaType<typeof schema> & ObtainSchemaGeneric<typeof schema, 'TVirtuals'>;
const user = {} as User;
user.fullName // works
@carlosingles, thanks for the workaround, currently I hardcoded all virtuals in an interface.
@vkarpov15, I've tested the example from my initial message in 6.7.4, and the issue still persists (fullName is not available in a type returned byInferSchemaType).
If speak generally, I think it makes sense to have a way to get both interfaces (with and without virtuals). Based on first message example:
- full interface with virtuals which can be used with find queries
type FullUser = InferFullSchemaType<typeof schema>;
//{
// _id: ObjectId;
// id: string;
// firstName: string;
// lastName: string;
// fullName: string;
//}
function findOneUser(): Promise<FullUser> {
return UserModel.findOne().orFail().exec();
}
- persistent interface (only with persisted fields and without _id) that can be used with creation method
type PersistentUserData = InferPersistentSchemaType<typeof schema>;
//{
// firstName: string;
// lastName: string;
//}
function createUser(data: PersistentUserData): Promise<FullUser> {
return UserModel.create(data);
}
Theoretically _id field can be omitted manually, but this won't work if there are nested fields with _id.
@Jokero I took a look and that's expected behavior. InferSchemaType returns the raw document interface, which represents how the data is stored in MongoDB. The Model does correctly contain the virtuals, try the following:
const UserModel = model('AutoTypedVirtuals', schema);
type User = ReturnType<(typeof UserModel)['hydrate']>;
const user: User = new UserModel({ firstName: 'foo' });
user.fullName;
@Jokero are you using mongoose-lean-virtuals? lean() doesn't include virtuals by default.
@vkarpov15 yes, I'm using it like this lean({ autopopulate: true, virtuals: true }). In some cases I also specify the type explicitly lean<Quest | null>({ autopopulate: true, virtuals: true })