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

feat(types): added optional stricter typing for Model attributes #12405

Merged
merged 5 commits into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 63 additions & 11 deletions docs/manual/other-topics/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,21 @@ Example of a minimal TypeScript project:
import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
import { HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, Association, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize';

class User extends Model {
// These are the minimum attributes needed to create a User
interface UserCreationAttributes {
name: string;
preferredName: string | null;
}

// These are all the attributes in the User model
interface UserAttributes extends UserCreationAttributes {
id: number;
}

// You can choose to omit the `UserAttributes` and `UserCreationAttributes`
// generic types to simplify your types. This will come at the cost of making
// typechecking slightly less strict.
class User extends Model<UserAttributes, UserCreationAttributes> implements UserAttributes {
public id!: number; // Note that the `null assertion` `!` is required in strict mode.
public name!: string;
public preferredName!: string | null; // for nullable fields
Expand Down Expand Up @@ -49,7 +63,16 @@ class User extends Model {

const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');

class Project extends Model {
interface ProjectAttributes {
ownerId: number;
name: string;
}

interface ProjectAttributes extends ProjectCreationAttributes {
id: number;
}

class Project extends Model<ProjectAttributes, ProjectCreationAttributes> implements ProjectAttributes {
public id!: number;
public ownerId!: number;
public name!: string;
Expand All @@ -58,7 +81,12 @@ class Project extends Model {
public readonly updatedAt!: Date;
}

class Address extends Model {
interface AddressAttributes {
userId: number;
address: string;
}

class Address extends Model<AddressAttributes> implements AddressAttributes {
public userId!: number;
public address!: string;

Expand Down Expand Up @@ -149,21 +177,45 @@ async function stuff() {

## Usage of `sequelize.define`

TypeScript doesn't know how to generate a `class` definition when we use the `sequelize.define` method to define a Model. Therefore, we need to do some manual work and declare an interface and a type, and eventually cast the result of `.define` to the _static_ type.
In Sequelize versions before v5, the default way of defining a model involved using `sequelize.define`. It's still possible to define models with that, and you can also add typings to these models using interfaces.

```ts
// We need to declare an interface for our model that is basically what our class would be
interface MyModel extends Model {
// We recommend you declare an interface for the attributes, for stricter typechecking
interface MyModelAttributes {
readonly id: number;
name: string;
}

interface MyModelCreationAttributes extends Optional<MyModelAttributes, 'id'> {}

// We need to declare an interface for our model that is basically what our class would be
interface MyModel extends Model<MyModelAttributes, MyModelCreationAttributes>, MyModelAttributes {}

const MyDefineModel = sequelize.define<MyModel>('MyDefineModel', {
id: {
primaryKey: true,
type: DataTypes.INTEGER.UNSIGNED,
}
});

async function stuffTwo() {
const myModel = await MyDefineModel.findByPk(1, {
rejectOnEmpty: true,
});
console.log(myModel.id);
}
```

If you're comfortable with somewhat less strict typing for the attributes on a model, you can save some code by defining the Instance to just extend `Model` without any attributes in the generic types.

// Need to declare the static model so `findOne` etc. use correct types.
type MyModelStatic = typeof Model & {
new (values?: object, options?: BuildOptions): MyModel;
```ts
// We need to declare an interface for our model that is basically what our class would be
interface MyModel extends Model {
readonly id: number;
name: string;
}

// TS can't derive a proper class definition from a `.define` call, therefor we need to cast here.
const MyDefineModel = <MyModelStatic>sequelize.define('MyDefineModel', {
const MyDefineModel = sequelize.define<MyModel>('MyDefineModel', {
id: {
primaryKey: true,
type: DataTypes.INTEGER.UNSIGNED,
Expand Down
6 changes: 6 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ export { BaseError as Error } from './lib/errors';
export { useInflection } from './lib/utils';
export { Utils, QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable };
export { Validator as validator } from './lib/utils/validator-extras';

/**
* Type helper for making certain fields of an object optional. This is helpful
* for creating the `CreationAttributes` from your `Attributes` for a Model.
*/
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
24 changes: 12 additions & 12 deletions types/lib/associations/belongs-to-many.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class BelongsToMany<S extends Model = Model, T extends Model = Model> ext
* The options for the getAssociations mixin of the belongsToMany association.
* @see BelongsToManyGetAssociationsMixin
*/
export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions {
export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions<any> {
/**
* A list of the attributes from the join table that you want to select.
*/
Expand Down Expand Up @@ -149,9 +149,9 @@ export type BelongsToManyGetAssociationsMixin<TModel> = (
* @see BelongsToManySetAssociationsMixin
*/
export interface BelongsToManySetAssociationsMixinOptions
extends FindOptions,
BulkCreateOptions,
InstanceUpdateOptions,
extends FindOptions<any>,
BulkCreateOptions<any>,
InstanceUpdateOptions<any>,
InstanceDestroyOptions {
through?: JoinTableAttributes;
}
Expand Down Expand Up @@ -191,9 +191,9 @@ export type BelongsToManySetAssociationsMixin<TModel, TModelPrimaryKey> = (
* @see BelongsToManyAddAssociationsMixin
*/
export interface BelongsToManyAddAssociationsMixinOptions
extends FindOptions,
BulkCreateOptions,
InstanceUpdateOptions,
extends FindOptions<any>,
BulkCreateOptions<any>,
InstanceUpdateOptions<any>,
InstanceDestroyOptions {
through?: JoinTableAttributes;
}
Expand Down Expand Up @@ -233,9 +233,9 @@ export type BelongsToManyAddAssociationsMixin<TModel, TModelPrimaryKey> = (
* @see BelongsToManyAddAssociationMixin
*/
export interface BelongsToManyAddAssociationMixinOptions
extends FindOptions,
BulkCreateOptions,
InstanceUpdateOptions,
extends FindOptions<any>,
BulkCreateOptions<any>,
InstanceUpdateOptions<any>,
InstanceDestroyOptions {
through?: JoinTableAttributes;
}
Expand Down Expand Up @@ -274,7 +274,7 @@ export type BelongsToManyAddAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the createAssociation mixin of the belongsToMany association.
* @see BelongsToManyCreateAssociationMixin
*/
export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions {
export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions<any> {
through?: JoinTableAttributes;
}
/**
Expand Down Expand Up @@ -455,7 +455,7 @@ export type BelongsToManyHasAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the countAssociations mixin of the belongsToMany association.
* @see BelongsToManyCountAssociationsMixin
*/
export interface BelongsToManyCountAssociationsMixinOptions extends Transactionable, Filterable {
export interface BelongsToManyCountAssociationsMixinOptions extends Transactionable, Filterable<any> {
/**
* Apply a scope on the related model, or remove its default scope by passing false.
*/
Expand Down
7 changes: 4 additions & 3 deletions types/lib/associations/belongs-to.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class BelongsTo<S extends Model = Model, T extends Model = Model> extends
* The options for the getAssociation mixin of the belongsTo association.
* @see BelongsToGetAssociationMixin
*/
export interface BelongsToGetAssociationMixinOptions extends FindOptions {
export interface BelongsToGetAssociationMixinOptions extends FindOptions<any> {
/**
* Apply a scope on the related model, or remove its default scope by passing false.
*/
Expand Down Expand Up @@ -61,7 +61,7 @@ export type BelongsToGetAssociationMixin<TModel> = (options?: BelongsToGetAssoci
* The options for the setAssociation mixin of the belongsTo association.
* @see BelongsToSetAssociationMixin
*/
export interface BelongsToSetAssociationMixinOptions extends SaveOptions {
export interface BelongsToSetAssociationMixinOptions extends SaveOptions<any> {
/**
* Skip saving this after setting the foreign key if false.
*/
Expand Down Expand Up @@ -95,7 +95,8 @@ export type BelongsToSetAssociationMixin<TModel, TPrimaryKey> = (
* The options for the createAssociation mixin of the belongsTo association.
* @see BelongsToCreateAssociationMixin
*/
export interface BelongsToCreateAssociationMixinOptions extends CreateOptions, BelongsToSetAssociationMixinOptions {}
export interface BelongsToCreateAssociationMixinOptions
extends CreateOptions<any>, BelongsToSetAssociationMixinOptions {}

/**
* The createAssociation mixin applied to models with belongsTo.
Expand Down
16 changes: 8 additions & 8 deletions types/lib/associations/has-many.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class HasMany<S extends Model = Model, T extends Model = Model> extends A
* The options for the getAssociations mixin of the hasMany association.
* @see HasManyGetAssociationsMixin
*/
export interface HasManyGetAssociationsMixinOptions extends FindOptions {
export interface HasManyGetAssociationsMixinOptions extends FindOptions<any> {
/**
* Apply a scope on the related model, or remove its default scope by passing false.
*/
Expand Down Expand Up @@ -74,7 +74,7 @@ export type HasManyGetAssociationsMixin<TModel> = (options?: HasManyGetAssociati
* The options for the setAssociations mixin of the hasMany association.
* @see HasManySetAssociationsMixin
*/
export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {}
export interface HasManySetAssociationsMixinOptions extends FindOptions<any>, InstanceUpdateOptions<any> {}

/**
* The setAssociations mixin applied to models with hasMany.
Expand Down Expand Up @@ -110,7 +110,7 @@ export type HasManySetAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the addAssociations mixin of the hasMany association.
* @see HasManyAddAssociationsMixin
*/
export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {}
export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions<any> {}

/**
* The addAssociations mixin applied to models with hasMany.
Expand Down Expand Up @@ -146,7 +146,7 @@ export type HasManyAddAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the addAssociation mixin of the hasMany association.
* @see HasManyAddAssociationMixin
*/
export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {}
export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions<any> {}

/**
* The addAssociation mixin applied to models with hasMany.
Expand Down Expand Up @@ -182,7 +182,7 @@ export type HasManyAddAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the createAssociation mixin of the hasMany association.
* @see HasManyCreateAssociationMixin
*/
export interface HasManyCreateAssociationMixinOptions extends CreateOptions {}
export interface HasManyCreateAssociationMixinOptions extends CreateOptions<any> {}

/**
* The createAssociation mixin applied to models with hasMany.
Expand Down Expand Up @@ -218,7 +218,7 @@ export type HasManyCreateAssociationMixin<TModel> = (
* The options for the removeAssociation mixin of the hasMany association.
* @see HasManyRemoveAssociationMixin
*/
export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {}
export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions<any> {}

/**
* The removeAssociation mixin applied to models with hasMany.
Expand Down Expand Up @@ -254,7 +254,7 @@ export type HasManyRemoveAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the removeAssociations mixin of the hasMany association.
* @see HasManyRemoveAssociationsMixin
*/
export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {}
export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions<any> {}

/**
* The removeAssociations mixin applied to models with hasMany.
Expand Down Expand Up @@ -362,7 +362,7 @@ export type HasManyHasAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the countAssociations mixin of the hasMany association.
* @see HasManyCountAssociationsMixin
*/
export interface HasManyCountAssociationsMixinOptions extends Transactionable, Filterable {
export interface HasManyCountAssociationsMixinOptions extends Transactionable, Filterable<any> {
/**
* Apply a scope on the related model, or remove its default scope by passing false.
*/
Expand Down
6 changes: 3 additions & 3 deletions types/lib/associations/has-one.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class HasOne<S extends Model = Model, T extends Model = Model> extends As
* The options for the getAssociation mixin of the hasOne association.
* @see HasOneGetAssociationMixin
*/
export interface HasOneGetAssociationMixinOptions extends FindOptions {
export interface HasOneGetAssociationMixinOptions extends FindOptions<any> {
/**
* Apply a scope on the related model, or remove its default scope by passing false.
*/
Expand Down Expand Up @@ -59,7 +59,7 @@ export type HasOneGetAssociationMixin<TModel> = (options?: HasOneGetAssociationM
* The options for the setAssociation mixin of the hasOne association.
* @see HasOneSetAssociationMixin
*/
export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMixinOptions, SaveOptions {
export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMixinOptions, SaveOptions<any> {
/**
* Skip saving this after setting the foreign key if false.
*/
Expand Down Expand Up @@ -93,7 +93,7 @@ export type HasOneSetAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the createAssociation mixin of the hasOne association.
* @see HasOneCreateAssociationMixin
*/
export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {}
export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions<any> {}

/**
* The createAssociation mixin applied to models with hasOne.
Expand Down