Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot use generic model in mongoose: Argument of type 'x' is not assignable to parameter of type MongooseFilterQuery

I am trying to pass a generic Mongoose model into a function as a parameter.

import mongoose, { Document, Model, Schema } from 'mongoose';

interface User {
    name: string;
    age: number;
    favouriteAnimal: string;
}

const UserSchema = new Schema({
    name: String,
    age: Number,
    favouriteAnimal: String,
});

const UserModel = mongoose.model<User & Document>('User', UserSchema);

function doSomething() {
    return UserModel.findOne({ user: 'john' }); // This is fine.
}

async function doSomethingGeneric<T extends User & Document>(model: Model<T>, key: keyof T) {
    const user = await model.findOne({ user: 'john' }); // Compiler errors on this line.
    return user![key];
}

This is the error output from the compiler:

error TS2345: Argument of type '{ user: string; }' is not assignable to parameter of type 'MongooseFilterQuery<Pick<T, Exclude<keyof T, "toString" | "removeListener" | "off" | "update" | "invalidate" | "increment" | "model" | "$isDeleted" | "remove" | "save" | "__v" | "$isDefault" | "$session" | ... 47 more ... | "modelName">>>'.

Type '{ user: string; }' is not assignable to type '{ [P in keyof Pick<T, Exclude<keyof T, "toString" | "removeListener" | "off" | "update" | "invalidate" | "increment" | "model" | "$isDeleted" | "remove" | "save" | "__v" | "$isDefault" | "$session" | ... 47 more ... | "modelName">>]?: (P extends "_id" ? [...] extends [...] ? Condition<...> : Condition<...> : [...] e...'.

23    const user = await model.findOne({ user: 'john' });

The type of UserModel is mongoose.Model<User & mongoose.Document, {}>, so I would have expected the generic to satisfy the same type constraints as the non-generic method. Where am I going wrong?

Edit: I have recreated the issue in the TS playground so you can see it directly.

like image 984
Vaelin Avatar asked Apr 26 '20 12:04

Vaelin


1 Answers

This has been confirmed to be a Typescript bug: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/39358

A possible workaround is to manually cast the query object to the right type:

async function doSomethingGeneric<T extends User & Document>(model: Model<T>, key: keyof T) {
    const user = await model.findOne({ user: 'john' } as FilterQuery<T>);
    return user![key];                    /* add this ^^^^^^^^^^^^^^^^^ */
}
like image 199
Susccy Avatar answered Oct 20 '22 02:10

Susccy