Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.79k stars 3.8k forks source link

TypeScript does not return an error when assigning the result of a `lean` query to a variable of type `InstanceType<Model<MyModel>>` #14697

Closed nikzanda closed 5 days ago

nikzanda commented 2 weeks ago

Prerequisites

Mongoose version

8.4.4

Node.js version

20.10.0

MongoDB server version

6.0.2

Typescript version (if applicable)

5.4.5

Description

Reproduction link: https://stackblitz.com/edit/stackblitz-starters-9hjjfm?file=src%2Findex.ts

TypeScript should not allow me to assign the result of await UserModel.findOne().lean(); to the variable let user: UserInstance | null = null; in fact, the user.set or user.save instruction results in an error.

  let user: UserInstance | null = null;
  if (condition) {
    user = await UserModel.findOne().lean(); // Here it should report an error
    if (user) {
      user.set({
        name: 'John Doe',
      });
      await user.save();
    }
  }

The code below image image

results in an error when executed:

Screenshot 2024-06-28 at 18 24 06

Steps to Reproduce

Expected Behavior

TypeScript should not allow the code to compile

IslandRhythms commented 1 week ago
import {
    Schema,
    connection,
    connect,
    Model,
    model,
    SchemaOptions,
    SchemaTypes,
  } from 'mongoose';

  export interface IUser {
    name: string;
    createdAt: Date;
    updatedAt: Date;
  }

  export interface IUserVirtuals {
    id: string;
  }

  type UserModelType = Model<IUser, {}, {}, IUserVirtuals>;

  export type UserInstance = InstanceType<UserModelType>;

  const options: SchemaOptions<IUser> = {
    timestamps: true,
    optimisticConcurrency: true,
  };

  const userSchema = new Schema<IUser, UserModelType>(
    {
      name: {
        type: SchemaTypes.String,
        required: true,
        trim: true,
      },
    },
    options
  );

  const User = model<IUser, UserModelType>('User', userSchema);

  async function run() {
    await connect('mongodb://localhost:27017');
    await connection.dropDatabase();

    await User.create({
        name: 'John',
      });

      let user: UserInstance | null = null;
      user = await User.findOne().lean();
        if (user) {
          user.name = 'John Doe'
          await user.save();
        }
    console.log(user);
  }

  run();
nikzanda commented 1 week ago
await User.create({
        name: 'John',
      });

      let user: UserInstance | null = null;
      user = await User.findOne().lean();
        if (user) {
          user.name = 'John Doe'
          await user.save();
        }
    console.log(user);

Sorry, but your code does not work, as the documentation states:

_The lean option tells Mongoose to skip hydrating the result documents. This makes queries faster and less memory intensive, but the result documents are plain old JavaScript objects (POJOs), not Mongoose documents._

Therefore, the save method cannot be called and TypeScript does not report any errors:

Screenshot 2024-07-02 at 20 47 51
vkarpov15 commented 1 week ago

As a workaround, you can do user = await User.findOne().lean<IUser>();. I'm investigating why this issue is happening.

nikzanda commented 1 week ago

Thank you