Typing errors when using nested creates with associations
Opened this issue · 4 comments
Issue
Following methods do not accept an object with an association,
- Model.create()
Versions
"sequelize": "^6.37.6""sequelize-typescript": "^2.1.6"
My tsconfig has no strict mode settings enabled. Interestingly, this problem doesn't occur when strictNullChecks is enabled. My tsconfig:
{
"compilerOptions": {
"outDir": "./dist",
"allowJs": true,
"module": "commonjs",
"target": "ES2020",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["./src/**/*"]
}
Explanation
I am using sequelize and typescript in a project. I am receiving an error when trying to create a new instance of a model while including one of its hasOne associations. Relevant documentation on the matter: https://sequelize.org/docs/v6/advanced-association-concepts/creating-with-associations/.
// Model A
export class ModelA extends Model<
InferAttributes<ModelA>,
InferCreationAttributes<ModelA>
> {
// ... Other fields/columns, skipping for brevity
@HasOne(() => ModelB, { onDelete: "SET NULL" })
modelB: ModelB;
}
// Model B
export class ModelB extends Model<
InferAttributes<ModelB>,
InferCreationAttributes<ModelB>
> {
@BelongsTo(() => ModelA)
modelA?: ModelA;
}
And when I try to create an instance of ModelA, including ModelB:
await ModelA.create({...propertiesOfModelA,
modelB: {...propertiesOfModelB}},
{include: [ModelB],})
I am met with this error:
Type '<fields of modelB argument>' is missing the following properties from type 'ModelB': $add, $set, and 35 more.ts(2740)
ModelA(90, 3): The expected type comes from property 'modelB' which is declared here on type 'Optional<InferCreationAttributes<ModelA, { omit: never; }>, NullishPropertiesOf<InferCreationAttributes<ModelA, { omit: never; }>>>'
I do not expect typescript to enforce that the modelB field in the values argument contain every single field, property, and method of an instance of ModelB. I can remove the stricter typing like this:
export class ModelA extends Model {}
But then Typescript has no way to enforce the types inside of the values argument passed into the create method.
I could typecast, but this feels like a bandaid:
await ModelA.create({...propertiesOfModelA, modelB: {...propertiesOfModelB} as ModelB,
{include: [ModelB],})
I could also define the type of ModelA's modelB field as partial, but this incorrectly communicates that all fields and columns in ModelBare optional (even if some are specified as required/not nullable).
@HasOne(() => ModelB, {onDelete: "SET NULL"})
modelB: Partial<ModelB>
Even if I were to define ModelB as such, the error persists:
export class ModelB extends Model<Partial<ModelB>> {}
My goal is to maintain type strictness in all relevant model methods (specifically create), so that I can still create models with associations and ensure I'm passing in the correct types.
I got the problem solved like this. Made the solution up by myself, it is not documented anywhere.
export class User extends Model<
InferAttributes<User>,
InferCreationAttributes<User> & {
preferences: CreationAttributes<Preferences>;
profile: CreationAttributes<Profile>;
}
>For your example:
export class ModelA extends Model<
InferAttributes<ModelA>,
InferCreationAttributes<ModelA> & {
modelB: CreationAttributes<ModelB>;
}
> I hope this helps. Also, if there are better solutions, please let me know.
@jleweli Out of curiosity, does your project have strict or strictNullChecks enabled in your tsconfig?
@hdeleon99 No, currently strictNullChecks is not enabled.