Definitelytyped: @types/sequelize Conflicting exports of `Model`

Created on 17 May 2017  Â·  23Comments  Â·  Source: DefinitelyTyped/DefinitelyTyped

  • [x] I tried using the @types/sequelize package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [ ] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @samuelneff @codeanimal @drinchev

When importing Model via import { Model } from 'sequelize'; it import both the Model interface and the Model property on SequelizeStaticAndInstance which causes some big problems for me (commenting out the property fixes all of them).

Can you suggest a solution? Maybe rename Model?

Most helpful comment

This code compiles for me with TS 2.2.1

import * as Sequelize from "sequelize";

export interface IUserAttributes { readonly id: number; }

export type User = Sequelize.Instance<IUserAttributes> & IUserAttributes & { };

export type UserModel = Sequelize.Model<User, IUserAttributes>;

function factory(sql: Sequelize.Sequelize): UserModel {
    return sql.define<User, IUserAttributes>('User', {
        id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
    }, {
        instanceMethods: { },
        classMethods: { },
        hooks: { },
        paranoid: true,
        tableName: 'user',
    } );
};
// Consuming it:
factory( new Sequelize('test', 'root', '') ).findAll({});

@SimonSchick , @GastonRosso can you use the code provided?

@SimonSchick can you give more code examples that don't compile, using the code I posted here ( including the way you consume the model ).

The definition files were initially created with the idea that the imports will look like :

import * as Sequelize from "sequelize".

Thanks!

All 23 comments

@samuelneff messed up your mention

which causes some big problems for me

Can you add some Minimal, Complete and Verifiable example. I would be happy to help you out!

Simply using go to definition on a reference to Model imported as described above should show you 2 definition locations 1 being to the Model interface and 1 to the property which obviously as conflicting types as one is the type itself and the other is an instance, I will try to extract an example for you.

I'm trying to create strong typings manually at the moment.

Little extract from the code base

// base.ts

import * as Sequelize from 'sequelize';
import * as config from 'config';

export const db = new Sequelize(
    config.get<string>('sql.database'),
    config.get<string>('sql.username'),
    config.get<string>('sql.password'),
    config.get<Sequelize.Options>('sql.options')
);

// index.ts

import { Sequelize, Model } from 'sequelize';
import { forEach } from 'lodash';

import { db as baseDB } from './base';

import { factory as userFactory, UserModel } from './models/User';
export { UserModel, User } from './models/User';

export const db: Sequelize & ModelRegistry = <any>baseDB;

export type ModelRegistry = {
    User: UserModel,
    [key: string]: Model<any, any>,
};

interface ISequelizeAssociatable extends Model<any, any> {
    associate(reg: ModelRegistry): void;
}

function isAssociatable(model: { associate?: Function }): model is ISequelizeAssociatable {
    return typeof model.associate === 'function';
}

const models: ModelRegistry = {
    User: userFactory(db),
};

forEach(models, (model, name) => {
    if (isAssociatable(model)) {
        model.associate(models);
    }
});

// models/User.ts

import { INTEGER, Sequelize, Instance, Model } from 'sequelize';

import { ModelRegistry } from '..';

export interface IUserAttributes {
    readonly id: number;
}

export type User = Instance<IUserAttributes> & IUserAttributes & {
};

export type UserModel = Model<User, IUserAttributes>;

export function factory (sql: Sequelize): UserModel { // ======== ERROR OCCURS HERE DUE TO CONFLICTING TYPE 
    return sql.define<User, IUserAttributes>('User', {
        id: {
            type: INTEGER,
            primaryKey: true,
            autoIncrement: true,
        },
    }, {
        instanceMethods: {
        },
        classMethods: {
            associate (this: UserModel, models: ModelRegistry) {
                const { User } = models;
                User.belongsTo(models.SomeOtherModel, {
                    foreignKey: 'id',
                    constraints: true,
                });
            },
        },
        hooks: {
        },
        paranoid: true,
        tableName: 'user',
    });
};

The resulting error message is something along the lines of:

[ts]
Type 'Model<User, IUserAttributes>' is not assignable to type 'SequelizeStaticAndInstance.Model<User, IUserAttributes>'.
  Types of property 'sync' are incompatible.
    Type '(options?: SyncOptions) => Bluebird<Model<User, IUserAttributes>>' is not assignable to type '(options?: SyncOptions) => Bluebird<this>'.
      Type 'Bluebird<Model<User, IUserAttributes>>' is not assignable to type 'Bluebird<this>'.
        Type 'Model<User, IUserAttributes>' is not assignable to type 'this'.

Does this info work for you @drinchev ?

@drinchev did you have time to take a look?

Will check out tomorrow morning. Sorry quite busy at work.

Sent from my iPhone

On 18 May 2017, at 18:03, Simon Schick notifications@github.com wrote:

@drinchev did you have time to take a look?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

Hey there.

The way that I use the sequelize types is the following :

import * as Sequelize from "sequelize";

export interface IUserAttributes { readonly id: number; }

export interface IUserInstance extends Sequelize.Instance<IUserAttributes>, IUserAttributes {};

export interface IUserClass extends Sequelize.Model<IUserInstance, IUserAttributes> {};

export function factory (sql: Sequelize.Sequelize): IUserClass {
        return sql.define<IUserInstance, IUserAttributes>('User', {
                id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
        }, {
                instanceMethods: { },
                classMethods: { },
                hooks: { },
                paranoid: true,
                tableName: 'user',
        } );
};

The benefits that you can have is having any class methods and instance methods strong typed as well.

export interface IUserInstance extends Sequelize.Instance<IUserAttributes>, IUserAttributes, IUserInstanceMethods {};

export IUserInstanceMethods = {
    setPassword(): Promise<void>;
};

const instanceMethods : IUserInstanceMethods = {
   setPassword() { return Promise.resolve(); }
};

export function factory (sql: Sequelize.Sequelize): IUserClass {
        return sql.define<IUserInstance, IUserAttributes>('User', {
                id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
        }, {
                instanceMethods: instanceMethods,
                classMethods: { },
                hooks: { },
                paranoid: true,
                tableName: 'user',
        } );
};

If we follow your naming the code should look :

import * as Sequelize from "sequelize";

export interface IUserAttributes { readonly id: number; }

export type User = Sequelize.Instance<IUserAttributes> & IUserAttributes & { };

export type UserModel = Sequelize.Model<User, IUserAttributes>;

export function factory (sql: Sequelize.Sequelize): UserModel {
        return sql.define<User, IUserAttributes>('User', {
                id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
        }, {
                instanceMethods: { },
                classMethods: { },
                hooks: { },
                paranoid: true,
                tableName: 'user',
        } );
};

As of your idea of improvement. You can discuss here about splitting the interface from the class, but I would need some more code examples.

Using your code I still get the same name conflict problem, as soon as I try to consume the class it thinks it's a Model<any, any>, removing the mentioned line again, resolves that error.

I'd suggest renaming the Model interface although that's a relatively big change.

Hi guys, I'm having the same issue... @SimonSchick did you find a solution or a workaround? Thanks in advance.

@GastonRosso other than commenting out the conflicting line? No.

This code compiles for me with TS 2.2.1

import * as Sequelize from "sequelize";

export interface IUserAttributes { readonly id: number; }

export type User = Sequelize.Instance<IUserAttributes> & IUserAttributes & { };

export type UserModel = Sequelize.Model<User, IUserAttributes>;

function factory(sql: Sequelize.Sequelize): UserModel {
    return sql.define<User, IUserAttributes>('User', {
        id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
    }, {
        instanceMethods: { },
        classMethods: { },
        hooks: { },
        paranoid: true,
        tableName: 'user',
    } );
};
// Consuming it:
factory( new Sequelize('test', 'root', '') ).findAll({});

@SimonSchick , @GastonRosso can you use the code provided?

@SimonSchick can you give more code examples that don't compile, using the code I posted here ( including the way you consume the model ).

The definition files were initially created with the idea that the imports will look like :

import * as Sequelize from "sequelize".

Thanks!

I'm using TS 2.3.2.
The import method doesn't matter, Sequelize.Model and Model still refer to the same thing.

I will give your code a test later.

So, in fact the * as Sequelize appears to resolve it, but Sequelize.Model still 2 definition references which still shouldn't be the case.
It's also somewhat annoying to always have to use * as imports as it requires you to always refer to the namespace, you really shouldn't have to use the * as import unless you want access to the constructor.

The export of sequelize.d.ts uses declaration merging. https://www.typescriptlang.org/docs/handbook/declaration-merging.html

It is implemented correctly, since sequelize itself exports only one class ( https://github.com/sequelize/sequelize/blob/master/lib/sequelize.js#L1248 ) and everything else is static members of that Sequelize class.

If you find this code style annoying, you can always create an npm typescript-based module that wraps sequelize and have a suitable for you declaration file.

@drinchev thanks for the solution, that way it works. I have one question more, if you need to add class methods... how can you type them? because you can't add them to the model interface (UserModel in your example)

Thank you!

Hey @GastonRosso

You can use a similar approach as with the instance methods.

import * as Sequelize from "sequelize";
import * as Promise   from "bluebird";

export interface IUserAttributes {
    id?: number;
    email?: string;
}

export interface IUserClass extends Sequelize.Model<IUserInstance, IUserAttributes>, IUserClassMethods {};

interface IUserClassMethods {
        associate( db : { [ name : string ] : Sequelize.Model<any, any> } ) : void;
}

/** Class methods implementation */
const classMethods : IUserClassMethods = {

    /** Associate the model */
    associate( db ) {
    const dbUser        = db["User"];
    const dbPost        = db["Post"];
    dbPost.belongsTo( dbUser );
    }

};

export interface IUserInstance extends Sequelize.Instance<IUserAttributes>, IUserAttributes, IUserInstanceMethods {};

export interface IUserInstanceMethods {
    setPassword(): Promise<boolean>;
};

const instanceMethods : IUserInstanceMethods = {
    setPassword() { return Promise.resolve(true); }
};

export function factory (sql: Sequelize.Sequelize): IUserClass {
    return <IUserClass> sql.define<IUserInstance, IUserAttributes>('User', {
            id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
            email: { type: Sequelize.STRING }
        }, {
                    instanceMethods: instanceMethods,
                    classMethods: classMethods,
                    hooks: { },
                    paranoid: true,
                    tableName: 'user',
            } );
};

I ended up importing everything but Model, Instance and Sequelize via import {...} from 'sequelize'. All other imports cause no problems, it's not pretty but it works for me.

@drinchev thank you very much for the example, very useful approach

The example isn't working for me... I'm getting the error message:

Namespace '".../node_modules/sequelize/types/index"' has no exported member 'Instance'

Here's the code:

export interface IF_UserAttributes extends Model {
  readonly username: string;
  readonly email: string;
  readonly password: string;
  readonly role: string;
}
export type User = Sequelize.Instance<IF_UserAttributes> & IF_UserAttributes & { };

Hi @TidyIQ,
I might be mistaken, but afaik Sequelize.Instance is an interface from @types/sequelize and if you're using Sequelize +v5 which has its own types introduced in v5.0.0-beta.16, then you're getting the types from sequelize/types which doesn't have this interface Sequelize.Instance exposed (or I'm doing something wrong here and just couldn't find this interface). 😄 But anyways, hope this helps.

Was this page helpful?
0 / 5 - 0 ratings