Skip to content
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

Issue with typings on SchemaDefinition #9857

Closed
amorimfilipe opened this issue Jan 24, 2021 · 2 comments
Closed

Issue with typings on SchemaDefinition #9857

amorimfilipe opened this issue Jan 24, 2021 · 2 comments
Labels
typescript Types or Types-test related issue / Pull Request
Milestone

Comments

@amorimfilipe
Copy link

Do you want to request a feature or report a bug?

Not sure if it is a bug or a question.

What is the current behavior?

interface User {
    name: string
    active: boolean
    points: number
}

type UserDocument = mongoose.Document<User>
type UserSchemaDefinition = mongoose.SchemaDefinition<User>
type UserModel = mongoose.Model<UserDocument>

Behavior A

const schemaDefinition: UserSchemaDefinition = {
    name: String, // if add other type instead of String as interface requires string, it don't shows an error.
    active: Boolean,
    points: Number
}

const schema = new mongoose.Schema<UserDocument, UserModel>(schemaDefinition) // error on arg "schemaDefinition" below

// Argument of type '{ name: SchemaDefinitionProperty<string>; active: SchemaDefinitionProperty<boolean>; points: SchemaDefinitionProperty<number>; }' is not assignable to parameter of type '{ [path: string]: SchemaDefinitionProperty<undefined>; }'.
//   Property 'active' is incompatible with index signature.
//     Type 'SchemaDefinitionProperty<boolean>' is not assignable to type 'SchemaDefinitionProperty<undefined>'.
//       Type 'boolean' is not assignable to type 'SchemaDefinitionProperty<undefined>'.ts(2345)

if add boolean | number | date to type SchemaDefinitionProperty it don't gives an error.


Behavior B

const schemaDefinition: UserSchemaDefinition = {
    name: {
        type: String
    },
    active: {
        type: Boolean
    },
    points: {
        type: Number
    }
}

// Type '{ type: StringConstructor; }' is not assignable to type 'SchemaDefinitionProperty<string>'.
//   Types of property 'type' are incompatible.
//     Type 'StringConstructor' is not assignable to type 'string'.ts(2322)

// Type '{ type: BooleanConstructor; }' is not assignable to type 'SchemaDefinitionProperty<boolean>'.
//   Types of property 'type' are incompatible.
//     Type 'BooleanConstructor' is not assignable to type 'boolean | undefined'.
//       Type 'BooleanConstructor' is not assignable to type 'true'.ts(2322)

// Type '{ type: NumberConstructor; }' is not assignable to type 'SchemaDefinitionProperty<number>'.
//   Types of property 'type' are incompatible.
//     Type 'NumberConstructor' is not assignable to type 'number'.ts(2322)

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.

@Rossh87
Copy link
Contributor

Rossh87 commented Jan 25, 2021

There are several things going on here, sorry if this gets a little long.

First, the type parameter of mongoose.Document doesn't set the type of user-defined properties on instances of Document. It sets the type of the document's _id property. So instead of type UserDocument = mongoose.Document<User>, do this:
interface UserDocument extends User, mongoose.Document {}

Second, the Typescript types as currently written don't allow you to reliably infer the type of a SchemaDefinition from the type of a user-defined interface. It seems like the following works:

const schemaDefinition: UserSchemaDefinition = {
    name: String,
    active: Boolean,
    points: Number
}

However, it only works in the sense that it doesn't throw a compiler error. As @amorimfilipe noticed, the compiler will allow some obviously incorrect values for the keys of schemaDefinition. This is possible because mongoose.SchemaDefinitionProperty is a very permissive type.

Lastly, note that the type of schemaDefinition.name is SchemaDefinitionProperty<string>, NOT SchemaDefinitionProperty<String>. That's the cause of the error you're seeing in Behavior B: for any SchemaDefinitionProperty<T>, mongoose.SchemaTypeOptions expects its own property type to be of type T--- and String doesn't satisfy 'string'.

IMO, these types don't work as expected. However, it's pretty easy to avoid dealing with these intermediate types and still get a typesafe model to work with in the rest of your program by doing something like this. You can also do the following to fix the compiler error you noted in Behavior A:

interface User {
    name: string;
    active: boolean;
    points: number;
}

interface UserDocument extends User, mongoose.Document {}

//create an intermediary type to use in schema definition
interface UserSchemaProps {
    name: typeof String;
    active: typeof Boolean;
    points: typeof Number;
}

type UserSchemaDefinition = mongoose.SchemaDefinition<UserSchemaProps>;

const schemaDefinition: UserSchemaDefinition = {
    name: String, 
    active: Boolean,
    points: Number,
};

const schema = new mongoose.Schema<UserDocument>(schemaDefinition);

Note that the above still doesn't prevent passing bogus values in schemaDefinition. E.g.,

const schemaDefinition: UserSchemaDefinition = {
    name: '123', 
    active: Boolean,
    points: Number,
};

still typechecks.

@amorimfilipe
Copy link
Author

amorimfilipe commented Jan 26, 2021

@Rossh87 Thanks for the insight.

@vkarpov15 vkarpov15 added this to the 5.11.x milestone Jan 28, 2021
@vkarpov15 vkarpov15 added the typescript Types or Types-test related issue / Pull Request label Jan 28, 2021
@vkarpov15 vkarpov15 modified the milestones: 5.11.x, 5.11.15 Feb 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typescript Types or Types-test related issue / Pull Request
Projects
None yet
Development

No branches or pull requests

3 participants