Skip to content

Commit

Permalink
feat(types): added optional stricter typing for Model attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
hsource committed Jun 22, 2020
1 parent e33d2bd commit ad54f72
Show file tree
Hide file tree
Showing 22 changed files with 765 additions and 443 deletions.
87 changes: 76 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,58 @@ 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.
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 a dummy class, derive a static type, and cast the result of `sequelize.define` to the static type.

```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;
// We recommend you declare an interface for the attributes, for stricter typechecking
interface MyModelAttributes {
id: number;
name: string;
}

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

// We need to declare a class for our model.
// We and users of this class should never call this. Instead, we only use it
// to create the static type via the `typeof` call below.
export class MyModel extends Model<MyModelAttributes, MyModelCreationAttributes> implements MyModelAttributes {
public readonly id!: number;
public name!: string;
}

type MyModelStatic = typeof MyModel;

// TS can't derive a proper class definition from a `.define` call, therefor we need to cast here.
const MyDefineModel = <MyModelStatic>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);
}
```

// Need to declare the static model so `findOne` etc. use correct types.
type MyModelStatic = typeof Model & {
new (values?: object, options?: BuildOptions): MyModel;
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.

```ts
// We need to declare a class for our model.
// We and users of this class should never call this. Instead, we only use it
// to create the static type via the `typeof` call below.
export class MyModel extends Model {
public readonly id!: number;
public name!: string;
}

type MyModelStatic = typeof MyModel;

// 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 = <MyModelStatic>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

0 comments on commit ad54f72

Please sign in to comment.