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

findByIdAndUpdate is NOT Callable typescript (fails to execute on union type of Models) #10305

Closed
DVGY opened this issue May 29, 2021 · 8 comments
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary typescript Types or Types-test related issue / Pull Request

Comments

@DVGY
Copy link

DVGY commented May 29, 2021

Do you want to request a feature or report a bug?
Bug
What is the current behavior?
This expression is not callable. findByIdAndUpdate

If the current behavior is a bug, please provide the steps to reproduce.

import Reviews, Users, Trips  from '../models/allModel';

type UserDefinedModel = typeof Reviews | typeof Users | typeof Trips;

export const updateOne = (userDefinedModel: UserDefinedModel): RequestHandler =>
  catchAsync(
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      const { id } = req.params;

      const doc = await userDefinedModel.findByIdAndUpdate(id, req.body, {
        new: true,
        runValidators: true,
      });

      const modelName = userDefinedModel.modelName;

      if (!doc) {
        return next(new AppError(`No ${modelName} found with that Id`, 400));
      }

      res.status(200).json({
        status: 'success',
        [modelName]: { doc },
      });
    }
  );

image
image

I can share my private repo with the member of this repo. Let me know.

{
  "compilerOptions": {
    "target": "ES2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
    /* Concatenate and emit output to single file. */
    "outDir": "build" /* Redirect output structure to the directory. */,
    "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
    /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
    "declaration": true,
    "stripInternal": true,

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    /* Parse in strict mode and emit "use strict" for each source file. */

    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,

    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
    "typeRoots": ["src/@types", "./node_modules/@types"]
  },
  "include": ["src"]
}

What is the expected behavior?

findByIdAndUpdate should be callable
find should be callable

If I replace userDefinedModel with specific model like Users.findByIdAndUpdate or Users.find(), it works.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
Node: 14.15.4, "mongoose": "^5.12.12", MongoDB 4.4

@DVGY DVGY changed the title findByIdAndUpdate is NOT Callable typescript findByIdAndUpdate is NOT Callable typescript (fails to execute on union type of Models) May 29, 2021
@IslandRhythms
Copy link
Collaborator

can you provide allModels?

@IslandRhythms IslandRhythms added the typescript Types or Types-test related issue / Pull Request label Jun 4, 2021
@vkarpov15
Copy link
Collaborator

@DVGY without seeing what userDefinedModel looks like, we won't be able to repro this. Can you please provide a standalone script that demonstrates this issue?

@vkarpov15 vkarpov15 added needs clarification This issue doesn't have enough information to be actionable. Close after 14 days of inactivity and removed typescript Types or Types-test related issue / Pull Request labels Jun 4, 2021
@DVGY
Copy link
Author

DVGY commented Jun 5, 2021

@DVGY without seeing what userDefinedModel looks like, we won't be able to repro this. Can you please provide a standalone script that demonstrates this issue?

@vkarpov15 @IslandRhythms Here is the codesandbox

code

Checkout the file src/models/updateOneController.ts

Thanks

@vkarpov15 vkarpov15 removed the needs clarification This issue doesn't have enough information to be actionable. Close after 14 days of inactivity label Jun 7, 2021
@vkarpov15 vkarpov15 added this to the 5.12.14 milestone Jun 7, 2021
@IslandRhythms
Copy link
Collaborator

import * as mongoose from "mongoose";
import * as express from "express";
enum UserRole {
  ADMIN = "ADMIN",
  LEAD_GUIDE = "LEAD_GUIDE",
  GUIDE = "GUIDE",
  USER = "USER"
}
interface IUsers extends mongoose.Document {
  name: string;
  email: string;
  password: string;
}

const usersSchema = new mongoose.Schema<IUsers>({
  name: {
    type: String,
    required: [true, "Please tell us your name"]
  },
  email: {
    type: String,
    required: [true, "Please tell us your name"],
    unique: true,
    lowercase: true
  },
  password: {
    type: String,
    required: [true, "Please enter a password"],
    minlength: 8,
    select: false
  }
});

//--------------------------------------------------//
//             PRE MIDDLEWARE                       //
//--------------------------------------------------//

const Users = mongoose.model<IUsers>("Users", usersSchema);


const tripsSchema = new mongoose.Schema<ITrips>({
    name: {
      type: String,
      required: [true, "A trip must have a name"],
      unique: true,
      trim: true,
      maxlength: [40, "A trip name must have less or equal then 40 characters"],
      minlength: [10, "A trip name must have more  or equal then 10 characters"]
    },
    slug: String,
    duration: {
      type: Number,
      required: [true, "A trip must have a duration"]
    },
    price: {
      type: Number,
      required: [true, "A trip must have a price"]
    }
  });
  
  //---------------------------------------------------//
  //--------------------INTERFACES---------------------//
  //--------------------------------------------------//
  
  interface ITrips extends mongoose.Document {
    name: string;
    slug: string;
    duration: number;
    price: number;
    priceDiscount: number;
    maxGroupSize: number;
    difficulty: string;
    ratingsAverage: number;
    ratingsQuantity: number;
    summary: string;
    description: string;
    imageCover: string;
  }
  
  //--------------------------------------------------//
  //             VIRTUALS                             //
  //--------------------------------------------------//
  
  //---------------------------------------------------//
  //                 METHODS                           //
  //---------------------------------------------------//
  
  const Trips = mongoose.model<ITrips>("Trips", tripsSchema);

  const reviewsSchema = new mongoose.Schema<IReviews>(
    {
      review: {
        type: String,
        require: [true, "Review cannot be empty"]
      },
      rating: {
        type: Number,
        min: 1,
        max: 5
      },
      createdAt: {
        type: Date,
        default: Date.now
      },
      trip: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Trips",
        required: [true, "Review must belong to a Trip"]
      },
      user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Users",
        required: [true, "Review must belong to a User"]
      }
    },
    {
      toJSON: {},
      toObject: {}
    }
  );
  
  //---------------------------------------------------//
  //--------------------INTERFACES---------------------//
  //--------------------------------------------------//
  
  interface IReviews extends mongoose.Document {
    review: string;
    rating: number;
    createdAt: Date;
    trip: ITrips;
    user: IUsers;
  }
  
  const Reviews = mongoose.model<IReviews>("Reviews", reviewsSchema);
  


type UserDefinedModel = typeof Reviews | typeof Users | typeof Trips;

// Note
/**
 * findByIdAndUpdate() and find() is not callable on union of type different mongoose models
 *
 */

const updateOne = (
  // userDefinedModel: Model<any,{},{}> // This works
  userDefinedModel: UserDefinedModel // This does not
): express.RequestHandler => async (
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): Promise<void> => {
  const { id } = req.params;

  const doc = await userDefinedModel.findByIdAndUpdate(id, req.body, {
    new: true,
    runValidators: true
  });

  const doc2 = await userDefinedModel.find();

  const doc3 = await Reviews.findByIdAndUpdate();

  const modelName = userDefinedModel.modelName;

  if (!doc) {
    // Error
  }

  res.status(200).json({
    status: "success",
    [modelName]: { doc }
  });
};

@IslandRhythms IslandRhythms added the confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. label Jun 8, 2021
@vkarpov15
Copy link
Collaborator

Unfortunately this looks like a limitation in TypeScript: microsoft/TypeScript#33591 . TS doesn't handle this sort of union type behavior well, we would recommend defining this as a static instead.

reviewsSchema.static('expressUpdateOne', async function(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): Promise<void> {
  const Model = this;

  const { id } = req.params;

  const doc = await Model.findByIdAndUpdate(id, req.body, {
    new: true,
    runValidators: true
  });

  const doc2 = await userDefinedModel.find();

  const doc3 = await Model.findByIdAndUpdate();

  const modelName = Model.modelName;

  if (!doc) {
    // Error
  }

  res.status(200).json({
    status: "success",
    [modelName]: { doc }
  });
});

@vkarpov15 vkarpov15 removed this from the 5.12.14 milestone Jun 14, 2021
@vkarpov15 vkarpov15 added help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary typescript Types or Types-test related issue / Pull Request and removed confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. labels Jun 14, 2021
@DVGY
Copy link
Author

DVGY commented Aug 10, 2021

@vkarpov15 this update one will run of different models too, I see here you are registering it on reviewSchema. Can I register it on Model ?

@vkarpov15
Copy link
Collaborator

@DVGY sure, you can structure it as a global plugin: https://mongoosejs.com/docs/plugins.html

@ibnlisha
Copy link

I got the same error and found out it was related to the model export.
const User = models.User? models.User :model<dbUser>('User', userSchema). Asserting the model type did the trick.
const User = models.User? models.User as Model<dbUser>:model<dbUser>('User', userSchema)

@Automattic Automattic locked and limited conversation to collaborators May 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary typescript Types or Types-test related issue / Pull Request
Projects
None yet
Development

No branches or pull requests

4 participants