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): simplify Model definitions #14091

Closed
wants to merge 6 commits into from
Closed
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
345 changes: 150 additions & 195 deletions docs/manual/other-topics/typescript.md

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions docs/manual/other-topics/upgrade-to-v7.md
Expand Up @@ -2,6 +2,15 @@

Sequelize v7 is the next major release after v6. Below is a list of breaking changes to help you upgrade.

## Highlights

Sequelize 7 is a release with lots of breaking changes, but it is also filled with improvements and new features!

### Simplified TypeScript integration

Take a look at our [updated TypeScript guide](https://sequelize.org/v7/manual/typescript.html).
We hope you'll find that integrating Sequelize with TypeScript in v7 is much easier than in v6.

## Breaking Changes

### Support for Node 12 and up
Expand All @@ -16,6 +25,92 @@ As a result, the manual typings that were formerly best-effort guesses on top of
have been removed and all typings are now directly retrieved from the actual TypeScript code.
You'll likely find many tiny differences which however should be easy to fix.

#### Changes to class-based Model typings

Typings for Models have been greatly simplified. Instead of having to write something like the following:

```typescript
import { Model, Optional } from 'sequelize';

type UserAttributes = {
id: number;
firstName: string;
};

type UserCreationAttributes = Optional<UserAttributes, 'id'>;

class User extends Model<UserAttributes, UserCreationAttributes> {
declare id: number;
declare firstName: string;
}
```

You instead need to write it like this:

```typescript
import { Model, CreationOptional } from 'sequelize';

class User extends Model<User> {
Copy link
Member

Choose a reason for hiding this comment

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

Since I have never used this syntax before, I have no clue but I think one would have to call the Model's init function afterwards somehow?

Can you add that to the example?

// attribute 'id' has a default value, it can be omitted from Model.create and other creation methods
// `CreationOptional` marks it as such in the typings.
declare id: CreationOptional<number>;
declare firstName: string;
}
```

We recommend you take a look at [the updated TypeScript guide for Sequelize](https://sequelize.org/v7/manual/typescript.html).

Note: If you were already using `InferAttributes` & `InferCreationAttributes` in Sequelize 6,
then your code already looked like the one above. Simply change how you extend the Model class
from `Model<InferAttributes<User>, InferCreationAttributes<User>>` to `Model<User>`

#### Changes to `sequelize.define`-based model typings

`ModelDefined` has been updated to match how the `Model` class works.

Instead of writing this:

```typescript
import { ModelDefined, Optional } from 'sequelize';

type UserAttributes = {
id: number;
firstName: string;
};

type UserCreationAttributes = Optional<UserAttributes, 'id'>;

const User: ModelDefined<UserAttributes, UserCreationAttributes> = sequelize.define('User', {
id: DataTypes.INTEGER,
firstName: DataTypes.STRING,
});
```

You need to write it like this:

```typescript
import { ModelDefined, CreationOptional } from 'sequelize';

type UserAttributes = {
// attribute 'id' has a default value, it can be omitted from Model.create and other creation methods
// `CreationOptional` marks it as such in the typings.
id: CreationOptional<number>;
firstName: string;
};

const User: ModelDefined<UserAttributes> = sequelize.define('User', {
id: DataTypes.INTEGER,
firstName: DataTypes.STRING,
});
```

#### Changes to Static Model typings

We improved the typing of `ModelStatic` in Sequelize v6. It now fulfils the role that was previously split
between `ModelCtor`, `ModelType`, and `ModelStatic`.

It is typically safe to simply replace any use of those with `ModelStatic`.

### Changes to `ConnectionManager`

*This only impacts you if you used `ConnectionManager` directly.*
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -81,7 +81,7 @@
"eslint": "8.8.0",
"eslint-plugin-jsdoc": "37.8.2",
"eslint-plugin-mocha": "10.0.3",
"expect-type": "0.12.0",
"expect-type": "0.13.0",
"fast-glob": "3.2.11",
"fs-jetpack": "4.3.1",
"husky": "7.0.4",
Expand Down
6 changes: 3 additions & 3 deletions src/associations/base.d.ts
@@ -1,9 +1,9 @@
import { ColumnOptions, Model, ModelCtor, Hookable } from '../model';
import { ColumnOptions, Model, ModelStatic, Hookable } from '../model';

export abstract class Association<S extends Model = Model, T extends Model = Model> {
public associationType: string;
public source: ModelCtor<S>;
public target: ModelCtor<T>;
public source: ModelStatic<S>;
public target: ModelStatic<T>;
public isSelfAssociation: boolean;
public isSingleAssociation: boolean;
public isMultiAssociation: boolean;
Expand Down
9 changes: 4 additions & 5 deletions src/associations/belongs-to-many.d.ts
Expand Up @@ -8,8 +8,7 @@ import {
InstanceDestroyOptions,
InstanceUpdateOptions,
Model,
ModelCtor,
ModelType,
ModelStatic,
Transactionable,
} from '../model';
import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, MultiAssociationAccessors } from './base';
Expand All @@ -22,7 +21,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: ModelType | string;
model: ModelStatic<any> | string;

/**
* If true the generated join table will be paranoid
Expand Down Expand Up @@ -60,7 +59,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: ModelType | string | ThroughOptions;
through: ModelStatic<any> | string | ThroughOptions;

/**
* The name of the foreign key in the join table (representing the target model) or an object representing
Expand Down Expand Up @@ -98,7 +97,7 @@ export class BelongsToMany<S extends Model = Model, T extends Model = Model> ext
public sourceKey: string;
public targetKey: string;
public accessors: MultiAssociationAccessors;
constructor(source: ModelCtor<S>, target: ModelCtor<T>, options: BelongsToManyOptions);
constructor(source: ModelStatic<S>, target: ModelStatic<T>, options: BelongsToManyOptions);
}

/**
Expand Down
5 changes: 2 additions & 3 deletions src/associations/belongs-to.d.ts
@@ -1,8 +1,7 @@
import { DataType } from '../data-types';
import { CreateOptions, CreationAttributes, FindOptions, Model, ModelCtor, SaveOptions } from '../model';
import { CreateOptions, CreationAttributes, FindOptions, Model, ModelStatic, SaveOptions } from '../model';
import { Association, AssociationOptions, SingleAssociationAccessors } from './base';

// type ModelCtor<M extends Model> = InstanceType<typeof M>;
/**
* Options provided when associating models with belongsTo relationship
*
Expand All @@ -23,7 +22,7 @@ export interface BelongsToOptions extends AssociationOptions {

export class BelongsTo<S extends Model = Model, T extends Model = Model> extends Association<S, T> {
public accessors: SingleAssociationAccessors;
constructor(source: ModelCtor<S>, target: ModelCtor<T>, options: BelongsToOptions);
constructor(source: ModelStatic<S>, target: ModelStatic<T>, options: BelongsToOptions);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/associations/has-many.d.ts
Expand Up @@ -6,7 +6,7 @@ import {
FindOptions,
InstanceUpdateOptions,
Model,
ModelCtor,
ModelStatic,
Transactionable,
} from '../model';
import { Association, ManyToManyOptions, MultiAssociationAccessors } from './base';
Expand All @@ -30,7 +30,7 @@ export interface HasManyOptions extends ManyToManyOptions {

export class HasMany<S extends Model = Model, T extends Model = Model> extends Association<S, T> {
public accessors: MultiAssociationAccessors;
constructor(source: ModelCtor<S>, target: ModelCtor<T>, options: HasManyOptions);
constructor(source: ModelStatic<S>, target: ModelStatic<T>, options: HasManyOptions);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/associations/has-one.d.ts
@@ -1,5 +1,5 @@
import { DataType } from '../data-types';
import { CreateOptions, CreationAttributes, FindOptions, Model, ModelCtor, SaveOptions } from '../model';
import { CreateOptions, CreationAttributes, FindOptions, Model, ModelStatic, SaveOptions } from '../model';
import { Association, AssociationOptions, SingleAssociationAccessors } from './base';

/**
Expand All @@ -21,7 +21,7 @@ export interface HasOneOptions extends AssociationOptions {

export class HasOne<S extends Model = Model, T extends Model = Model> extends Association<S, T> {
public accessors: SingleAssociationAccessors;
constructor(source: ModelCtor<S>, target: ModelCtor<T>, options: HasOneOptions);
constructor(source: ModelStatic<S>, target: ModelStatic<T>, options: HasOneOptions);
}

/**
Expand Down
9 changes: 4 additions & 5 deletions src/dialects/abstract/query-interface.d.ts
Expand Up @@ -9,7 +9,6 @@ import {
Filterable,
Poolable,
ModelStatic,
ModelType,
CreationAttributes,
Attributes,
} from '../../model';
Expand Down Expand Up @@ -490,7 +489,7 @@ export class QueryInterface {
insertValues: object,
updateValues: object,
where: object,
model: ModelType,
model: ModelStatic<any>,
options?: QueryOptions
): Promise<object>;

Expand Down Expand Up @@ -543,13 +542,13 @@ export class QueryInterface {
tableName: TableName,
identifier: WhereOptions<any>,
options?: QueryOptions,
model?: ModelType
model?: ModelStatic<any>
): Promise<object>;

/**
* Returns selected rows
*/
public select(model: ModelType | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise<object[]>;
public select(model: ModelStatic<any> | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise<object[]>;

/**
* Increments a row value
Expand All @@ -569,7 +568,7 @@ export class QueryInterface {
tableName: TableName,
options: QueryOptionsWithWhere,
attributeSelector: string | string[],
model?: ModelType
model?: ModelStatic<any>
): Promise<string[]>;

/**
Expand Down
8 changes: 4 additions & 4 deletions src/dialects/abstract/query.d.ts
@@ -1,6 +1,6 @@
import { Connection } from './connection-manager';
import { QueryTypes } from '../../query-types';
import { Model, ModelType, IncludeOptions } from '../../model';
import { Model, ModelStatic, IncludeOptions } from '../../model';
import { Sequelize } from '../../sequelize';

type BindOrReplacements = { [key: string]: unknown } | unknown[];
Expand All @@ -13,7 +13,7 @@ export interface AbstractQueryGroupJoinDataOptions {

export interface AbstractQueryOptions {
instance?: Model;
model?: ModelType;
model?: ModelStatic<any>;
type?: QueryTypes;

fieldMap?: boolean;
Expand Down Expand Up @@ -79,10 +79,10 @@ export class AbstractQuery {
/**
* Model type definition.
*
* @type {ModelType}
* @type {ModelStatic}
* @memberof AbstractQuery
*/
public model: ModelType;
public model: ModelStatic<any>;

/**
* Returns the current sequelize instance.
Expand Down