mongoose icon indicating copy to clipboard operation
mongoose copied to clipboard

Move lean({ virtuals: true }) support from mongoose-lean-virtuals into core Mongoose

Open Jokero opened this issue 3 years ago • 6 comments

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

  1. Copy example from mentioned PR https://github.com/Automattic/mongoose/pull/11908/files#diff-fc5a8155d58dc5904b33fcc871f11ee08cb6f6e7adbe69e7bfbd7d173e82d8d2 (Automatically Inferred Types), create a type with InferSchemaType and try to access fullName:
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
  1. 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.

Jokero avatar Nov 11 '22 23:11 Jokero

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 avatar Nov 26 '22 21:11 carlosingles

@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:

  1. 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();
}
  1. 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 avatar Nov 28 '22 21:11 Jokero

@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;

vkarpov15 avatar Jan 02 '23 19:01 vkarpov15

@Jokero are you using mongoose-lean-virtuals? lean() doesn't include virtuals by default.

vkarpov15 avatar Jan 06 '23 16:01 vkarpov15

@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 })

Jokero avatar Jan 06 '23 21:01 Jokero