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

fix(types): models with attributes cannot be used in some functions #13010

Merged
merged 2 commits into from
Mar 22, 2021

Conversation

KapitanOczywisty
Copy link
Contributor

@KapitanOczywisty KapitanOczywisty commented Feb 6, 2021

Pull Request check-list

Please make sure to review and check all of these items:

  • Does npm run test or npm run test-DIALECT pass with this change (including linting)?
  • Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving?
  • Have you added new tests to prevent regressions?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
  • Did you update the typescript typings accordingly (if applicable)?
  • Did you follow the commit message conventions explained in CONTRIBUTING.md?

Description of change

Models with attributes aren't accepted in some functions, this PR fixes these cases. Example:

interface UserAttributes {
    id: number;
    name: string;
}

class User extends Model<UserAttributes> implements UserAttributes {
    id!: number;
    name!: string;
}

interface ProjectAttributes {
    id: number;
    name: string;
}

interface UserProjectAttributes {
    id: number;
    customField: string;
}

class Project extends Model<ProjectAttributes> implements ProjectAttributes {
    id!: number;
    name!: string;
}

class UserProject extends Model<UserProjectAttributes> implements UserProjectAttributes {
    id!: number;
    customField!: string;
}

User.belongsToMany(Project, { through: { model: UserProject } });
Project.belongsToMany(User, { through: { model: UserProject } });

User.findAll({ include: [Project] });

image

Fixes #12842 sequelize/sequelize-typescript#882

Related PR: sequelize/sequelize-typescript#900 Original change: #12405

@KapitanOczywisty
Copy link
Contributor Author

@papb sequelize/sequelize-typescript#900 merged

@intellix
Copy link

intellix commented Mar 13, 2021

This is the last roadblock for me to update from Sequelize 4 -> 6.5+ as I've refactored all of the codebase and these are the last errors. Would this be a minor or major version release?

@intellix
Copy link

intellix commented Mar 21, 2021

Since this didn't make the latest release I decided to go with a patch-package so it doesn't block me from upgrading:

https://github.com/ds300/patch-package

patches/sequelize+6.6.0.patch

diff --git a/node_modules/sequelize/types/lib/associations/belongs-to-many.d.ts b/node_modules/sequelize/types/lib/associations/belongs-to-many.d.ts
index 9aa9e14..b04a728 100644
--- a/node_modules/sequelize/types/lib/associations/belongs-to-many.d.ts
+++ b/node_modules/sequelize/types/lib/associations/belongs-to-many.d.ts
@@ -8,6 +8,7 @@ import {
   InstanceUpdateOptions,
   Model,
   ModelCtor,
+  ModelType,
   Transactionable,
   WhereOptions,
 } from '../model';
@@ -21,7 +22,7 @@ export interface ThroughOptions {
    * The model used to join both sides of the N:M association.
    * Can be a string if you want the model to be generated by sequelize.
    */
-  model: typeof Model | string;
+  model: ModelType | string;
 
   /**
    * If true the generated join table will be paranoid
@@ -59,7 +60,7 @@ export interface BelongsToManyOptions extends ManyToManyOptions {
    * The name of the table that is used to join source and target in n:m associations. Can also be a
    * sequelize model if you want to define the junction table yourself and add extra attributes to it.
    */
-  through: typeof Model | string | ThroughOptions;
+  through: ModelType | string | ThroughOptions;
 
   /**
    * The name of the foreign key in the join table (representing the target model) or an object representing
diff --git a/node_modules/sequelize/types/lib/hooks.d.ts b/node_modules/sequelize/types/lib/hooks.d.ts
index 6688145..d19f930 100644
--- a/node_modules/sequelize/types/lib/hooks.d.ts
+++ b/node_modules/sequelize/types/lib/hooks.d.ts
@@ -1,3 +1,4 @@
+import { ModelType } from '../index';
 import { ValidationOptions } from './instance-validator';
 import Model, {
   BulkCreateOptions,
@@ -65,7 +66,7 @@ export interface SequelizeHooks<
   TCreationAttributes = TAttributes
 > extends ModelHooks<M, TAttributes> {
   beforeDefine(attributes: ModelAttributes<M, TCreationAttributes>, options: ModelOptions<M>): void;
-  afterDefine(model: typeof Model): void;
+  afterDefine(model: ModelType): void;
   beforeInit(config: Config, options: Options): void;
   afterInit(sequelize: Sequelize): void;
   beforeConnect(config: Config): HookReturn;
diff --git a/node_modules/sequelize/types/lib/model-manager.d.ts b/node_modules/sequelize/types/lib/model-manager.d.ts
index 29eac49..41e72da 100644
--- a/node_modules/sequelize/types/lib/model-manager.d.ts
+++ b/node_modules/sequelize/types/lib/model-manager.d.ts
@@ -1,4 +1,4 @@
-import { Model } from './model';
+import { Model, ModelType } from './model';
 import { Sequelize } from './sequelize';
 
 export class ModelManager {
@@ -7,8 +7,8 @@ export class ModelManager {
   public all: typeof Model[];
 
   constructor(sequelize: Sequelize);
-  public addModel<T extends typeof Model>(model: T): T;
-  public removeModel(model: typeof Model): void;
+  public addModel<T extends ModelType>(model: T): T;
+  public removeModel(model: ModelType): void;
   public getModel(against: unknown, options?: { attribute?: string }): typeof Model;
 }
 
diff --git a/node_modules/sequelize/types/lib/model.d.ts b/node_modules/sequelize/types/lib/model.d.ts
index 808fbfc..5e79fb1 100644
--- a/node_modules/sequelize/types/lib/model.d.ts
+++ b/node_modules/sequelize/types/lib/model.d.ts
@@ -379,7 +379,7 @@ export interface IncludeThroughOptions extends Filterable<any>, Projectable {
 /**
  * Options for eager-loading associated models, also allowing for all associations to be loaded at once
  */
-export type Includeable = typeof Model | Association | IncludeOptions | { all: true, nested?: true } | string;
+export type Includeable = ModelType | Association | IncludeOptions | { all: true, nested?: true } | string;
 
 /**
  * Complex include options
@@ -392,7 +392,7 @@ export interface IncludeOptions extends Filterable<any>, Projectable, Paranoid {
   /**
    * The model you want to eagerly load
    */
-  model?: typeof Model;
+  model?: ModelType;
 
   /**
    * The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` /
@@ -1232,7 +1232,7 @@ export interface ModelAttributeColumnReferencesOptions {
   /**
    * If this column references another table, provide it here as a Model, or a string
    */
-  model?: string | typeof Model;
+  model?: string | ModelType;
 
   /**
    * The column of the foreign table that this column references
@@ -1660,7 +1660,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
     this: ModelStatic<M>,
     schema: string,
     options?: SchemaOptions
-  ): { new(): M } & typeof Model;
+  ): ModelCtor<Model>;
 
   /**
    * Get the tablename of the model, taking schema into account. The method will return The name as a string
@@ -2127,7 +2127,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
   /**
    * Unscope the model
    */
-  public static unscoped<M extends typeof Model>(this: M): M;
+  public static unscoped<M extends ModelType>(this: M): M;
 
   /**
    * A hook that is run before validation
@@ -2833,7 +2833,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
   public isSoftDeleted(): boolean;
 }
 
-export type ModelType = typeof Model;
+export type ModelType<TModelAttributes = any, TCreationAttributes = TModelAttributes> = new () => Model<TModelAttributes, TCreationAttributes>;
 
 // Do not switch the order of `typeof Model` and `{ new(): M }`. For
 // instances created by `sequelize.define` to typecheck well, `typeof Model`
diff --git a/node_modules/sequelize/types/lib/query-interface.d.ts b/node_modules/sequelize/types/lib/query-interface.d.ts
index 9f721c5..0faa892 100644
--- a/node_modules/sequelize/types/lib/query-interface.d.ts
+++ b/node_modules/sequelize/types/lib/query-interface.d.ts
@@ -8,7 +8,7 @@ import {
   WhereOptions,
   Filterable,
   Poolable,
-  ModelCtor, ModelStatic
+  ModelCtor, ModelStatic, ModelType
 } from './model';
 import QueryTypes = require('./query-types');
 import { Sequelize, RetryOptions } from './sequelize';
@@ -487,7 +487,7 @@ export class QueryInterface {
     insertValues: object,
     updateValues: object,
     where: object,
-    model: typeof Model,
+    model: ModelType,
     options?: QueryOptions
   ): Promise<object>;
 
@@ -540,13 +540,13 @@ export class QueryInterface {
     tableName: TableName,
     identifier: WhereOptions<any>,
     options?: QueryOptions,
-    model?: typeof Model
+    model?: ModelType
   ): Promise<object>;
 
   /**
    * Returns selected rows
    */
-  public select(model: typeof Model | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise<object[]>;
+  public select(model: ModelType | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise<object[]>;
 
   /**
    * Increments a row value
@@ -566,7 +566,7 @@ export class QueryInterface {
     tableName: TableName,
     options: QueryOptionsWithWhere,
     attributeSelector: string | string[],
-    model?: typeof Model
+    model?: ModelType
   ): Promise<string[]>;
 
   /**
diff --git a/node_modules/sequelize/types/lib/sequelize.d.ts b/node_modules/sequelize/types/lib/sequelize.d.ts
index f03d916..b88d5ec 100644
--- a/node_modules/sequelize/types/lib/sequelize.d.ts
+++ b/node_modules/sequelize/types/lib/sequelize.d.ts
@@ -20,6 +20,7 @@ import {
   WhereOperators,
   ModelCtor,
   Hookable,
+  ModelType,
 } from './model';
 import { ModelManager } from './model-manager';
 import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType, ColumnsDescription } from './query-interface';
@@ -747,8 +748,8 @@ export class Sequelize extends Hooks {
    * @param name
    * @param fn   A callback function that is called with factory
    */
-  public static afterDefine(name: string, fn: (model: typeof Model) => void): void;
-  public static afterDefine(fn: (model: typeof Model) => void): void;
+  public static afterDefine(name: string, fn: (model: ModelType) => void): void;
+  public static afterDefine(fn: (model: ModelType) => void): void;
 
   /**
    * A hook that is run before Sequelize() call
@@ -1046,8 +1047,8 @@ export class Sequelize extends Hooks {
    * @param name
    * @param fn   A callback function that is called with factory
    */
-  public afterDefine(name: string, fn: (model: typeof Model) => void): void;
-  public afterDefine(fn: (model: typeof Model) => void): void;
+  public afterDefine(name: string, fn: (model: ModelType) => void): void;
+  public afterDefine(fn: (model: ModelType) => void): void;
 
   /**
    * A hook that is run before Sequelize() call
diff --git a/node_modules/sequelize/types/lib/utils.d.ts b/node_modules/sequelize/types/lib/utils.d.ts
index b7b5df8..b06f12c 100644
--- a/node_modules/sequelize/types/lib/utils.d.ts
+++ b/node_modules/sequelize/types/lib/utils.d.ts
@@ -1,5 +1,5 @@
 import { DataType } from './data-types';
-import { Model, ModelCtor, WhereOptions } from './model';
+import { Model, ModelCtor, ModelType, WhereOptions } from './model';
 
 export type Primitive = 'string' | 'number' | 'boolean';
 
@@ -40,9 +40,9 @@ export function mapOptionFieldNames<M extends Model, T extends OptionsForMapping
   options: T, model: ModelCtor<M>
 ): T;
 
-export function mapWhereFieldNames(attributes: object, model: typeof Model): object;
+export function mapWhereFieldNames(attributes: object, model: ModelType): object;
 /** Used to map field names in values */
-export function mapValueFieldNames(dataValues: object, fields: string[], model: typeof Model): object;
+export function mapValueFieldNames(dataValues: object, fields: string[], model: ModelType): object;
 
 export function isColString(value: string): boolean;
 export function canTreatArrayAsAnd(arr: unknown[]): boolean;

@KapitanOczywisty
Copy link
Contributor Author

Since this didn't make the latest release I decided to go with a patch-package so it doesn't block me from upgrading

This is very useful suggestion, tbh I've entirely dropped sequelize from the latest project because of broken typings. I don't know if I ever give sequelize another try, but patch-package will come handy for sure, thanks!

@KapitanOczywisty
Copy link
Contributor Author

@papb

@theoludwig
Copy link

Could you @papb check this code, please ? Thanks! 😄

@papb
Copy link
Member

papb commented Mar 22, 2021

Hello everyone! I am very sorry for taking long. Thanks for bringing this into my attention.

@papb
Copy link
Member

papb commented Mar 22, 2021

This is very useful suggestion, tbh I've entirely dropped sequelize from the latest project because of broken typings.

Sorry to hear! Hopefully in the near future our typings will be much better.

I don't know if I ever give sequelize another try, but patch-package will come handy for sure, thanks!

Yeah I really like patch-package too, it is a great idea. I wish it would work with projects with a build step... Luckily for @intellix sequelize is pure JS so it works haha

Copy link
Member

@papb papb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KapitanOczywisty Awesome work! Thank you very much. I apologize again for taking long (and for others reading, I also apologize for taking even-super-much-longer on other things), and I'm sorry to hear that you've dropped Sequelize. Hopefully we will heavily improve in the near future so you can consider coming back.

@papb papb merged commit de5f21d into sequelize:main Mar 22, 2021
@github-actions
Copy link
Contributor

🎉 This PR is included in version 6.6.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@papb
Copy link
Member

papb commented Mar 22, 2021

@intellix Now you don't need your patch with patch-package anymore 😁 sorry to take long!

@mmahalwy
Copy link
Contributor

@KapitanOczywisty what are you using now? haha

@KapitanOczywisty
Copy link
Contributor Author

@mmahalwy I'm testing https://www.prisma.io/ , however I don't have any opinion on that yet. There is also https://typeorm.io/ , but I wanted to try newer design. In one instance I've just used mariadb.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Typescript: 'belongsToMany' fails with custom 'through' Model if we provide TModelAttributes
5 participants