Automattic / mongoose

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

circular constraint issue #13129

Closed mwenko closed 1 year ago

mwenko commented 1 year ago

Prerequisites

Mongoose version

7.0.0

Node.js version

16.14.2

MongoDB server version

4.4

Typescript version (if applicable)

4.9.5

Description

I get a compilation error when specifying the Schema inside a connection.model call.

Steps to Reproduce

I have the following line of code in a typescript project:

const allUserDocuments: UserDocument[] = await connection
        .model("Users", UserSchema)
        .find({})
        .exec();

When compiling the project, I get:

node_modules/typescript/lib/lib.es5.d.ts:1584:11 - error TS2313: Type parameter 'P' has a circular constraint.

1584     [P in K]: T[P];
               ~

  example.ts:60:52
         const allUserDocuments: UserDocument[] = await connection
                                                          ~~~~~~~~~~
             .model("Users", UserSchema)
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Circularity originates in type at this location.

If I remove the UserSchema from the parameters like that:

    const allUserDocuments: UserDocument[] = await connection
        .model("Users")
        .find({})
        .exec();

it works.

doing skipLibCheck: true also resolves the problem, but that's not sth I wanna do.

Expected Behavior

This error should not happen.

mwenko commented 1 year ago

I'm using NestJS with a mongoose package that probably needs to be updated: https://github.com/nestjs/mongoose

As this could be the issue I'm going to wait until a new version of it has been released and come back here if the issue hasn't been resolved.

vkarpov15 commented 1 year ago

Can you please provide a more complete code sample, like what your schema definitions look like?

davidvorona commented 1 year ago

Mongoose version: 7.0.2 Node version: 16.16.0 Typescript version: 4.9.5

I've also been getting this issue, and might be able to provide some more insight. An example of the offending code w/ schema definitions:

import { Schema, model, InferSchemaType } from "mongoose";

const DisbursementSchema = new Schema({
    paymentId: {
        type: String,
        required: true
    },
    paymentDate: {
        type: Date,
        required: true
    },
    buyer: {
        id: {
            type: Schema.Types.ObjectId,
            ref: "user"
        },
        username: {
            type: String
        },
        email: {
            type: String
        },
        city: {
            type: String
        },
        state: {
            type: String
        },
        deliveryZip: {
            type: String
        },
    },
    seller: {
        id: {
            type: Schema.Types.ObjectId,
            ref: "user"
        },
        username: {
            type: String
        },
        email: {
            type: String
        },
        createDate: {
            type: Date
        }
    },
    purchases: [
        ...
    ]
});

export type DisbursementModel = InferSchemaType<typeof DisbursementSchema>;

DisbursementSchema.index({ "paymentId": 1, "seller.id": 1 }, { unique: true });

export default model("disbursement", DisbursementSchema);

Compiling the project results in the following error, but only when used with the --build flag:

> tsc --build

node_modules/typescript/lib/lib.es5.d.ts:1584:11 - error TS2313: Type parameter 'P' has a circular constraint.

1584     [P in K]: T[P];
               ~

  src/models/accounting/Disbursement.ts:207:33
    207 export type DisbursementModel = InferSchemaType<typeof DisbursementSchema>;
                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Circularity originates in type at this location.

Found 1 error.

Avoiding the --build flag fixes the issue, though I was able to use the --build flag before I switched to automatic schema type inference. For those that do need the perks of typescript's build flag and don't want to maintain separate schema interfaces, I can see this being an issue.

vkarpov15 commented 1 year ago

@davidvorona what is purchases in your schema? I don't see how the schema you pasted would cause a circular constraint, but I'll try it out.

davidvorona commented 1 year ago

This happened with very simple models as well, but here's the whole purchases array:

purchases: [
{
            ledgerId: {
                type: Schema.Types.ObjectId,
                ref: "ledger",
                required: true
            },
            item: {
                name: {
                    type: String
                },
                type: {
                    type: String,
                    enum: itemTypes
                }
            },
            purchasePrice: {
                type: Number
            },
            quantity: {
                type: Number
            },
            shipping: {
                cost: {
                    type: Number
                },
                // 0b01 isCombined, 0b10 isInternational, 0b11 isCombined/isInternational
                strategy: {
                    type: Number
                }
            },
            taxPrice: {
                type: Number
            },
            transactionFee: {
                percent: {
                    type: Number
                },
                discount: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            processingFee: {
                percent: {
                    type: Number
                },
                fixed: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            foreignTransactionFee: {
                percent: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            groupModeratorFee: {
                percent: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            paymentType: {
                type: String
            },
            purchaseDate: {
                type: Date
            },
            netPayment: {
                type: Number
            },
            disbursementInfo: {
                id: {
                    type: Schema.Types.ObjectId
                },
                isDisbursed: {
                    type: Boolean,
                    default: false,
                    required: true
                },
                date: {
                    type: Date
                },
                by: {
                    type: String
                },
                eWallet: {
                    entity: {
                        type: String
                    },
                    account: {
                        type: String
                    },
                    fullName: {
                        type: String
                    },
                    bankAccountNumber: {
                        type: Number
                    },
                    routingNumber: {
                        type: Number
                    }
                }
            },
            exceptionInfo: {
                id: {
                    type: Schema.Types.ObjectId
                },
                type: {
                    type: String,
                    enum: exceptionTypes
                },
                date: {
                    type: Date
                },
                by: {
                    type: String
                }
            }
        }
]
vkarpov15 commented 1 year ago

I'm still unable to repro, below script compiles fine with --build.

import { Schema, model, InferSchemaType } from "mongoose";

const DisbursementSchema = new Schema({
    paymentId: {
        type: String,
        required: true
    },
    paymentDate: {
        type: Date,
        required: true
    },
    buyer: {
        id: {
            type: Schema.Types.ObjectId,
            ref: "user"
        },
        username: {
            type: String
        },
        email: {
            type: String
        },
        city: {
            type: String
        },
        state: {
            type: String
        },
        deliveryZip: {
            type: String
        },
    },
    seller: {
        id: {
            type: Schema.Types.ObjectId,
            ref: "user"
        },
        username: {
            type: String
        },
        email: {
            type: String
        },
        createDate: {
            type: Date
        }
    },
purchases: [
{
            ledgerId: {
                type: Schema.Types.ObjectId,
                ref: "ledger",
                required: true
            },
            item: {
                name: {
                    type: String
                },
                type: {
                    type: String,
                    //enum: itemTypes
                }
            },
            purchasePrice: {
                type: Number
            },
            quantity: {
                type: Number
            },
            shipping: {
                cost: {
                    type: Number
                },
                // 0b01 isCombined, 0b10 isInternational, 0b11 isCombined/isInternational
                strategy: {
                    type: Number
                }
            },
            taxPrice: {
                type: Number
            },
            transactionFee: {
                percent: {
                    type: Number
                },
                discount: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            processingFee: {
                percent: {
                    type: Number
                },
                fixed: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            foreignTransactionFee: {
                percent: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            groupModeratorFee: {
                percent: {
                    type: Number
                },
                total: {
                    type: Number
                }
            },
            paymentType: {
                type: String
            },
            purchaseDate: {
                type: Date
            },
            netPayment: {
                type: Number
            },
            disbursementInfo: {
                id: {
                    type: Schema.Types.ObjectId
                },
                isDisbursed: {
                    type: Boolean,
                    default: false,
                    required: true
                },
                date: {
                    type: Date
                },
                by: {
                    type: String
                },
                eWallet: {
                    entity: {
                        type: String
                    },
                    account: {
                        type: String
                    },
                    fullName: {
                        type: String
                    },
                    bankAccountNumber: {
                        type: Number
                    },
                    routingNumber: {
                        type: Number
                    }
                }
            },
            exceptionInfo: {
                id: {
                    type: Schema.Types.ObjectId
                },
                type: {
                    type: String,
                    //enum: exceptionTypes
                },
                date: {
                    type: Date
                },
                by: {
                    type: String
                }
            }
        }
]
});

export type DisbursementModel = InferSchemaType<typeof DisbursementSchema>;

DisbursementSchema.index({ "paymentId": 1, "seller.id": 1 }, { unique: true });

export default model("disbursement", DisbursementSchema);

Given that compilation only fails with --build, it is likely due to your tsconfig.json. Can you please provide your tsconfig.json file?

For example, the tsconfig.json we're using is this one:

{
  "compilerOptions": {"strict": true},
  "include": ["./index.ts"]
}
mwenko commented 1 year ago

I'm still unable to repro, below script compiles fine with --build.

I found that setting esModuleInterop to false inside tsconfig.json works & the error disappeared. So I assume this is the setting that causes conflicts.

{
        "module": "commonjs",
        "esModuleInterop": false,
        "removeComments": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "importHelpers": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "target": "es2017",
        "lib": ["ES2019", "DOM"],
        "sourceMap": true,
        "outDir": "./dist",
        "baseUrl": "./",
        "incremental": true,
        "resolveJsonModule": true,
}

But what should I do if I wanna have it set to true?

Jokero commented 1 year ago

@vkarpov15 I noticed same circular error in version 7. TL;DR: it's because of incremental=true in tsconfig.json

Simple example:

src/locality.ts:

import { InferSchemaType, Schema } from 'mongoose';

const schema = new Schema({});

export type LocalityRaw = InferSchemaType<typeof schema>;

Output:

> tsc

node_modules/typescript/lib/lib.es5.d.ts:1584:11 - error TS2313: Type parameter 'P' has a circular constraint.

1584     [P in K]: T[P];
               ~

  src/locality.ts:5:27
    5 export type LocalityRaw = InferSchemaType<typeof schema>;
                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Circularity originates in type at this location.

Found 1 error in node_modules/typescript/lib/lib.es5.d.ts:1584

tsconfig.json:

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "esnext",
        "outDir": "dist",
        "incremental": true
    },
    "include": [
        "src/**/*"
    ]
}

But if I change incremental to false or remove it at all, it starts working. In version 6.10.4 I didn't have such issue.

joeskeen commented 1 year ago

FWIW, I tried setting incremental=false in my tsconfig.json and it didn't make a difference. I did, however, revert back to 6.10.4 and it works fine now.

vkarpov15 commented 1 year ago

@Jokero I've confirmed that I get the same error you see, I'm investigating to see if I can find a fix or workaround.