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() type error #10981

Closed
decanTyme opened this issue Nov 17, 2021 · 6 comments
Closed

findByIdAndUpdate() type error #10981

decanTyme opened this issue Nov 17, 2021 · 6 comments
Labels
has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue

Comments

@decanTyme
Copy link

decanTyme commented Nov 17, 2021

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

What is the current behavior?
There seems to be a type error when using the interface pattern described here:
Complete guide for Typescript with Mongoose for Node.js

No overload matches this call.
  The last overload gave the following error.
    Argument of type 'IProduct' is not assignable to parameter of type 'UpdateQuery<IProductDoc>'.
      Type 'IProduct' is not assignable to type 'ReadonlyPartial<_UpdateQueryDef<LeanDocument<IProductDoc>>>'.
        Types of property 'brand' are incompatible.
          Type 'Record<string, any> | ObjectId' is not assignable to type 'string | ObjectId | undefined'.
            Type 'Record<string, any>' is not assignable to type 'string | ObjectId | undefined'.
              Type 'Record<string, any>' is not assignable to type 'string'. ts(2769)

index.d.ts(972, 5): The last overload is declared here.

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

Here's how I actually implemented it:

// Custom mongoose base document
export interface IMongooseBaseDoc {
  _id: Types.ObjectId;
  createdAt?: Date;
  updatedAt?: Date;
}

export interface IProduct {
  brand: Types.ObjectId | Record<string, any>; //? Need fix
  // ...
}

export interface IProductDoc extends IProduct, IMongooseBaseDoc {
  images: Types.Array<IImage>;
  brand: IBrandDoc["_id"]; // another model
}

export interface IProductPopulatedDoc extends IProductDoc {
  brand: IBrandDoc; // this is why it needs to be `Record<string, any>` or typescript will complain
}

export type TSchema<T> = Record<
  keyof T,
  SchemaTypeOptions<any> | Schema | SchemaType
>

// ...
const ProductSchema = new Schema<TSchema<IProductDoc>>(...)

export default model<TSchema<IProductDoc>>("Product", ProductSchema);

The TSchema used is from #9715 in courtesy of #9715 (comment). The IBrandDoc is just something similar to IProductDoc but on another model. I made my own working base document as stated in Mongoose Docs on TypeScript Support.

There's nothing really wrong with the inheritance pattern described in the blogpost. It does work when an interface has no references to another model and the typesetting is sufficiently strong. However, when it is used in findByIdAndUpdate(), it creates the type error above. As far as I know, findOneAndUpdate(), updateOne(), and updateMany() also produces similar errors.

const data: IProduct = {
  brand: args.brand,
  // ...
};

const savedProduct = await new Product(data).save(); // works!

const updatedProduct = await Product.findByIdAndUpdate(
  args._id,
  data, //! Error: Type 'Record<string, any>' is not assignable to type 'string'. ts(2769)
  options
);

My tsconfig.json:

{
  "compilerOptions": {
    "target": "esnext",

    "module": "commonjs",
    "rootDir": "./",
    "moduleResolution": "node",
    "baseUrl": "./src",
    "typeRoots": ["@types"],

    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,

    "strict": true,
    "skipLibCheck": true
  },
  "exclude": ["./dist"]
}

What is the expected behavior?
I don't really know if I'm doing something wrong there, but it should work as is given that the IProduct interface works perfectly with the other model methods. Interfaces without references to other models work like a charm.

A temporary workaround would be to cast the data object above to any (e.g., <any>data) when using it as an argument in findByIdAndUpdate(), but I don't know the future implications for this. I wonder if the blogpost is outdated now that Mongoose v6 is out.

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

Node: v17.1.0
Mongoose: v6.0.12
MongoDB: v4.1.4 (did not explicitly install, it is a dep from when Mongoose was installed)

Update:
Took a little breather and it turns out I just needed to use the IProductDoc instead of just the IProduct since updating meant that it assumes it's already a saved document. In other words, it already has an _id and such. No errors ever since. My bad. I guess the takeaway here is that when the going gets tough, just don't forget to take a break sometimes.

Update 11/20/21:
I retract my earlier revelation. I do in fact need data to be just the plain IProduct instead of the IProductDoc. I just found out I can't append the _id in the update params for use in findByIdAndUpdate() as it gives the expected "_id" to be unique error, so using IProductDoc gives object creation type errors. Found something similar on #10689.

@decanTyme decanTyme reopened this Nov 20, 2021
@IslandRhythms IslandRhythms added the needs clarification This issue doesn't have enough information to be actionable. Close after 14 days of inactivity label Nov 24, 2021
@IslandRhythms
Copy link
Collaborator

What does IProductDoc look like? where is args coming from? Could you provide a clearer reproduction script?

@decanTyme
Copy link
Author

decanTyme commented Nov 25, 2021

What does IProductDoc look like? where is args coming from?

The IProductDoc is just the base IProduct that extends to my own "base" IMongooseBaseDoc and some overwrites. The args is provided by GraphQL as params on its field config resolve props. More on this in the codesandbox below.

Could you provide a clearer reproduction script?

Here's a scaled-down version of my codebase. If you look at the Product.mutation.ts file the error comes up, but in Brand.mutation.ts it doesn't since it has no references to another model, and thus no Record<string, any>.

Thanks for the reply. Hopefully the sandbox above clears things. I really don't know what to even look for here.

@IslandRhythms IslandRhythms added has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue and removed needs clarification This issue doesn't have enough information to be actionable. Close after 14 days of inactivity labels Nov 29, 2021
@vkarpov15 vkarpov15 added this to the 6.0.17 milestone Nov 29, 2021
@tr3ysmith
Copy link

tr3ysmith commented Dec 8, 2021

Yeah I'm seeing this issue now too. Any help would be great. Below is my setup. Error is almost identical. It built fine yesterday, now today it doesnt. The build locally works fine, its just when building in Docker on our Github Actions that it fails.

Schema File:

import mongoose, { Document } from "mongoose";
import { BaseDocument } from "../@types";

export interface IProduct extends BaseDocument {
    name: string,
    code: string,
    description: string,
    price: Number
}

// -----------------------------

export default mongoose.model<IProduct>('Product', ProductSchema);

Types File:

import { Document } from 'mongoose';

export interface BaseDocument extends Document {
    createdBy: string;
    updatedBy: string;
}

@jbeckton
Copy link

jbeckton commented Dec 11, 2021

I just upgraded from mongoose 6.0.7 to 6.1.1 and now everywhere I use findOneAndUpdate is showing this No overload matches this call. as mentioned in the OP.

Screen Shot 2021-12-10 at 7 32 11 PM

if I cast to Document or any it fixes the issue but this does not seem correct since I am not actually passing a Document type.
Screen Shot 2021-12-10 at 7 35 14 PM

@decanTyme
Copy link
Author

I just upgraded from mongoose 6.0.7 to 6.1.1 and now everywhere I use findOneAndUpdate is showing this No overload matches this call. as mentioned in the OP.

Strange, upgrading to 6.1.1 for me actually removed the error. I can confirm it now runs fine from both my codebase (local and live) and codesandbox without any casting. I guess I still can't close this one then.

@vkarpov15 vkarpov15 removed this from the 6.1.4 milestone Dec 16, 2021
@vkarpov15
Copy link
Collaborator

If it works in 6.1.1 then this issue should be ok to close

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has repro script There is a repro script, the Mongoose devs need to confirm that it reproduces the issue
Projects
None yet
Development

No branches or pull requests

5 participants