不要怂,就是干,撸起袖子干!

Commit 871157bc by Harry Yu Committed by GitHub

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

1 parent e36212c8
...@@ -19,7 +19,21 @@ Example of a minimal TypeScript project: ...@@ -19,7 +19,21 @@ Example of a minimal TypeScript project:
import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize'; import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
import { HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, Association, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } 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 id!: number; // Note that the `null assertion` `!` is required in strict mode.
public name!: string; public name!: string;
public preferredName!: string | null; // for nullable fields public preferredName!: string | null; // for nullable fields
...@@ -49,7 +63,16 @@ class User extends Model { ...@@ -49,7 +63,16 @@ class User extends Model {
const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); 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 id!: number;
public ownerId!: number; public ownerId!: number;
public name!: string; public name!: string;
...@@ -58,7 +81,12 @@ class Project extends Model { ...@@ -58,7 +81,12 @@ class Project extends Model {
public readonly updatedAt!: Date; 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 userId!: number;
public address!: string; public address!: string;
...@@ -149,21 +177,45 @@ async function stuff() { ...@@ -149,21 +177,45 @@ async function stuff() {
## Usage of `sequelize.define` ## 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 ```ts
// We need to declare an interface for our model that is basically what our class would be // We recommend you declare an interface for the attributes, for stricter typechecking
interface MyModel extends Model { interface MyModelAttributes {
readonly id: number; 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. ```ts
type MyModelStatic = typeof Model & { // We need to declare an interface for our model that is basically what our class would be
new (values?: object, options?: BuildOptions): MyModel; 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 = sequelize.define<MyModel>('MyDefineModel', {
const MyDefineModel = <MyModelStatic>sequelize.define('MyDefineModel', {
id: { id: {
primaryKey: true, primaryKey: true,
type: DataTypes.INTEGER.UNSIGNED, type: DataTypes.INTEGER.UNSIGNED,
......
...@@ -17,3 +17,9 @@ export { BaseError as Error } from './lib/errors'; ...@@ -17,3 +17,9 @@ export { BaseError as Error } from './lib/errors';
export { useInflection } from './lib/utils'; export { useInflection } from './lib/utils';
export { Utils, QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable }; export { Utils, QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable };
export { Validator as validator } from './lib/utils/validator-extras'; 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>>;
...@@ -104,7 +104,7 @@ export class BelongsToMany<S extends Model = Model, T extends Model = Model> ext ...@@ -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. * The options for the getAssociations mixin of the belongsToMany association.
* @see BelongsToManyGetAssociationsMixin * @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. * A list of the attributes from the join table that you want to select.
*/ */
...@@ -149,9 +149,9 @@ export type BelongsToManyGetAssociationsMixin<TModel> = ( ...@@ -149,9 +149,9 @@ export type BelongsToManyGetAssociationsMixin<TModel> = (
* @see BelongsToManySetAssociationsMixin * @see BelongsToManySetAssociationsMixin
*/ */
export interface BelongsToManySetAssociationsMixinOptions export interface BelongsToManySetAssociationsMixinOptions
extends FindOptions, extends FindOptions<any>,
BulkCreateOptions, BulkCreateOptions<any>,
InstanceUpdateOptions, InstanceUpdateOptions<any>,
InstanceDestroyOptions { InstanceDestroyOptions {
through?: JoinTableAttributes; through?: JoinTableAttributes;
} }
...@@ -191,9 +191,9 @@ export type BelongsToManySetAssociationsMixin<TModel, TModelPrimaryKey> = ( ...@@ -191,9 +191,9 @@ export type BelongsToManySetAssociationsMixin<TModel, TModelPrimaryKey> = (
* @see BelongsToManyAddAssociationsMixin * @see BelongsToManyAddAssociationsMixin
*/ */
export interface BelongsToManyAddAssociationsMixinOptions export interface BelongsToManyAddAssociationsMixinOptions
extends FindOptions, extends FindOptions<any>,
BulkCreateOptions, BulkCreateOptions<any>,
InstanceUpdateOptions, InstanceUpdateOptions<any>,
InstanceDestroyOptions { InstanceDestroyOptions {
through?: JoinTableAttributes; through?: JoinTableAttributes;
} }
...@@ -233,9 +233,9 @@ export type BelongsToManyAddAssociationsMixin<TModel, TModelPrimaryKey> = ( ...@@ -233,9 +233,9 @@ export type BelongsToManyAddAssociationsMixin<TModel, TModelPrimaryKey> = (
* @see BelongsToManyAddAssociationMixin * @see BelongsToManyAddAssociationMixin
*/ */
export interface BelongsToManyAddAssociationMixinOptions export interface BelongsToManyAddAssociationMixinOptions
extends FindOptions, extends FindOptions<any>,
BulkCreateOptions, BulkCreateOptions<any>,
InstanceUpdateOptions, InstanceUpdateOptions<any>,
InstanceDestroyOptions { InstanceDestroyOptions {
through?: JoinTableAttributes; through?: JoinTableAttributes;
} }
...@@ -274,7 +274,7 @@ export type BelongsToManyAddAssociationMixin<TModel, TModelPrimaryKey> = ( ...@@ -274,7 +274,7 @@ export type BelongsToManyAddAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the createAssociation mixin of the belongsToMany association. * The options for the createAssociation mixin of the belongsToMany association.
* @see BelongsToManyCreateAssociationMixin * @see BelongsToManyCreateAssociationMixin
*/ */
export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions { export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions<any> {
through?: JoinTableAttributes; through?: JoinTableAttributes;
} }
/** /**
...@@ -455,7 +455,7 @@ export type BelongsToManyHasAssociationsMixin<TModel, TModelPrimaryKey> = ( ...@@ -455,7 +455,7 @@ export type BelongsToManyHasAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the countAssociations mixin of the belongsToMany association. * The options for the countAssociations mixin of the belongsToMany association.
* @see BelongsToManyCountAssociationsMixin * @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. * Apply a scope on the related model, or remove its default scope by passing false.
*/ */
......
...@@ -30,7 +30,7 @@ export class BelongsTo<S extends Model = Model, T extends Model = Model> extends ...@@ -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. * The options for the getAssociation mixin of the belongsTo association.
* @see BelongsToGetAssociationMixin * @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. * Apply a scope on the related model, or remove its default scope by passing false.
*/ */
...@@ -61,7 +61,7 @@ export type BelongsToGetAssociationMixin<TModel> = (options?: BelongsToGetAssoci ...@@ -61,7 +61,7 @@ export type BelongsToGetAssociationMixin<TModel> = (options?: BelongsToGetAssoci
* The options for the setAssociation mixin of the belongsTo association. * The options for the setAssociation mixin of the belongsTo association.
* @see BelongsToSetAssociationMixin * @see BelongsToSetAssociationMixin
*/ */
export interface BelongsToSetAssociationMixinOptions extends SaveOptions { export interface BelongsToSetAssociationMixinOptions extends SaveOptions<any> {
/** /**
* Skip saving this after setting the foreign key if false. * Skip saving this after setting the foreign key if false.
*/ */
...@@ -95,7 +95,8 @@ export type BelongsToSetAssociationMixin<TModel, TPrimaryKey> = ( ...@@ -95,7 +95,8 @@ export type BelongsToSetAssociationMixin<TModel, TPrimaryKey> = (
* The options for the createAssociation mixin of the belongsTo association. * The options for the createAssociation mixin of the belongsTo association.
* @see BelongsToCreateAssociationMixin * @see BelongsToCreateAssociationMixin
*/ */
export interface BelongsToCreateAssociationMixinOptions extends CreateOptions, BelongsToSetAssociationMixinOptions {} export interface BelongsToCreateAssociationMixinOptions
extends CreateOptions<any>, BelongsToSetAssociationMixinOptions {}
/** /**
* The createAssociation mixin applied to models with belongsTo. * The createAssociation mixin applied to models with belongsTo.
......
...@@ -36,7 +36,7 @@ export class HasMany<S extends Model = Model, T extends Model = Model> extends A ...@@ -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. * The options for the getAssociations mixin of the hasMany association.
* @see HasManyGetAssociationsMixin * @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. * Apply a scope on the related model, or remove its default scope by passing false.
*/ */
...@@ -74,7 +74,7 @@ export type HasManyGetAssociationsMixin<TModel> = (options?: HasManyGetAssociati ...@@ -74,7 +74,7 @@ export type HasManyGetAssociationsMixin<TModel> = (options?: HasManyGetAssociati
* The options for the setAssociations mixin of the hasMany association. * The options for the setAssociations mixin of the hasMany association.
* @see HasManySetAssociationsMixin * @see HasManySetAssociationsMixin
*/ */
export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {} export interface HasManySetAssociationsMixinOptions extends FindOptions<any>, InstanceUpdateOptions<any> {}
/** /**
* The setAssociations mixin applied to models with hasMany. * The setAssociations mixin applied to models with hasMany.
...@@ -110,7 +110,7 @@ export type HasManySetAssociationsMixin<TModel, TModelPrimaryKey> = ( ...@@ -110,7 +110,7 @@ export type HasManySetAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the addAssociations mixin of the hasMany association. * The options for the addAssociations mixin of the hasMany association.
* @see HasManyAddAssociationsMixin * @see HasManyAddAssociationsMixin
*/ */
export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {} export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions<any> {}
/** /**
* The addAssociations mixin applied to models with hasMany. * The addAssociations mixin applied to models with hasMany.
...@@ -146,7 +146,7 @@ export type HasManyAddAssociationsMixin<TModel, TModelPrimaryKey> = ( ...@@ -146,7 +146,7 @@ export type HasManyAddAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the addAssociation mixin of the hasMany association. * The options for the addAssociation mixin of the hasMany association.
* @see HasManyAddAssociationMixin * @see HasManyAddAssociationMixin
*/ */
export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {} export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions<any> {}
/** /**
* The addAssociation mixin applied to models with hasMany. * The addAssociation mixin applied to models with hasMany.
...@@ -182,7 +182,7 @@ export type HasManyAddAssociationMixin<TModel, TModelPrimaryKey> = ( ...@@ -182,7 +182,7 @@ export type HasManyAddAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the createAssociation mixin of the hasMany association. * The options for the createAssociation mixin of the hasMany association.
* @see HasManyCreateAssociationMixin * @see HasManyCreateAssociationMixin
*/ */
export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} export interface HasManyCreateAssociationMixinOptions extends CreateOptions<any> {}
/** /**
* The createAssociation mixin applied to models with hasMany. * The createAssociation mixin applied to models with hasMany.
...@@ -218,7 +218,7 @@ export type HasManyCreateAssociationMixin<TModel> = ( ...@@ -218,7 +218,7 @@ export type HasManyCreateAssociationMixin<TModel> = (
* The options for the removeAssociation mixin of the hasMany association. * The options for the removeAssociation mixin of the hasMany association.
* @see HasManyRemoveAssociationMixin * @see HasManyRemoveAssociationMixin
*/ */
export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {} export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions<any> {}
/** /**
* The removeAssociation mixin applied to models with hasMany. * The removeAssociation mixin applied to models with hasMany.
...@@ -254,7 +254,7 @@ export type HasManyRemoveAssociationMixin<TModel, TModelPrimaryKey> = ( ...@@ -254,7 +254,7 @@ export type HasManyRemoveAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the removeAssociations mixin of the hasMany association. * The options for the removeAssociations mixin of the hasMany association.
* @see HasManyRemoveAssociationsMixin * @see HasManyRemoveAssociationsMixin
*/ */
export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {} export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions<any> {}
/** /**
* The removeAssociations mixin applied to models with hasMany. * The removeAssociations mixin applied to models with hasMany.
...@@ -362,7 +362,7 @@ export type HasManyHasAssociationsMixin<TModel, TModelPrimaryKey> = ( ...@@ -362,7 +362,7 @@ export type HasManyHasAssociationsMixin<TModel, TModelPrimaryKey> = (
* The options for the countAssociations mixin of the hasMany association. * The options for the countAssociations mixin of the hasMany association.
* @see HasManyCountAssociationsMixin * @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. * Apply a scope on the related model, or remove its default scope by passing false.
*/ */
......
...@@ -28,7 +28,7 @@ export class HasOne<S extends Model = Model, T extends Model = Model> extends As ...@@ -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. * The options for the getAssociation mixin of the hasOne association.
* @see HasOneGetAssociationMixin * @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. * Apply a scope on the related model, or remove its default scope by passing false.
*/ */
...@@ -59,7 +59,7 @@ export type HasOneGetAssociationMixin<TModel> = (options?: HasOneGetAssociationM ...@@ -59,7 +59,7 @@ export type HasOneGetAssociationMixin<TModel> = (options?: HasOneGetAssociationM
* The options for the setAssociation mixin of the hasOne association. * The options for the setAssociation mixin of the hasOne association.
* @see HasOneSetAssociationMixin * @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. * Skip saving this after setting the foreign key if false.
*/ */
...@@ -93,7 +93,7 @@ export type HasOneSetAssociationMixin<TModel, TModelPrimaryKey> = ( ...@@ -93,7 +93,7 @@ export type HasOneSetAssociationMixin<TModel, TModelPrimaryKey> = (
* The options for the createAssociation mixin of the hasOne association. * The options for the createAssociation mixin of the hasOne association.
* @see HasOneCreateAssociationMixin * @see HasOneCreateAssociationMixin
*/ */
export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {} export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions<any> {}
/** /**
* The createAssociation mixin applied to models with hasOne. * The createAssociation mixin applied to models with hasOne.
......
...@@ -21,40 +21,50 @@ export type HookReturn = Promise<void> | void; ...@@ -21,40 +21,50 @@ export type HookReturn = Promise<void> | void;
* Options for Model.init. We mostly duplicate the Hooks here, since there is no way to combine the two * Options for Model.init. We mostly duplicate the Hooks here, since there is no way to combine the two
* interfaces. * interfaces.
*/ */
export interface ModelHooks<M extends Model = Model> { export interface ModelHooks<M extends Model = Model, TAttributes = any> {
beforeValidate(instance: M, options: ValidationOptions): HookReturn; beforeValidate(instance: M, options: ValidationOptions): HookReturn;
afterValidate(instance: M, options: ValidationOptions): HookReturn; afterValidate(instance: M, options: ValidationOptions): HookReturn;
beforeCreate(attributes: M, options: CreateOptions): HookReturn; beforeCreate(attributes: M, options: CreateOptions<TAttributes>): HookReturn;
afterCreate(attributes: M, options: CreateOptions): HookReturn; afterCreate(attributes: M, options: CreateOptions<TAttributes>): HookReturn;
beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn;
afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn;
beforeRestore(instance: M, options: InstanceRestoreOptions): HookReturn; beforeRestore(instance: M, options: InstanceRestoreOptions): HookReturn;
afterRestore(instance: M, options: InstanceRestoreOptions): HookReturn; afterRestore(instance: M, options: InstanceRestoreOptions): HookReturn;
beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; beforeUpdate(instance: M, options: InstanceUpdateOptions<TAttributes>): HookReturn;
afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; afterUpdate(instance: M, options: InstanceUpdateOptions<TAttributes>): HookReturn;
beforeSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; beforeSave(
afterSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; instance: M,
beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; options: InstanceUpdateOptions<TAttributes> | CreateOptions<TAttributes>
afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; ): HookReturn;
beforeBulkDestroy(options: DestroyOptions): HookReturn; afterSave(
afterBulkDestroy(options: DestroyOptions): HookReturn; instance: M,
beforeBulkRestore(options: RestoreOptions): HookReturn; options: InstanceUpdateOptions<TAttributes> | CreateOptions<TAttributes>
afterBulkRestore(options: RestoreOptions): HookReturn; ): HookReturn;
beforeBulkUpdate(options: UpdateOptions): HookReturn; beforeBulkCreate(instances: M[], options: BulkCreateOptions<TAttributes>): HookReturn;
afterBulkUpdate(options: UpdateOptions): HookReturn; afterBulkCreate(instances: M[], options: BulkCreateOptions<TAttributes>): HookReturn;
beforeFind(options: FindOptions): HookReturn; beforeBulkDestroy(options: DestroyOptions<TAttributes>): HookReturn;
beforeCount(options: CountOptions): HookReturn; afterBulkDestroy(options: DestroyOptions<TAttributes>): HookReturn;
beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn; beforeBulkRestore(options: RestoreOptions<TAttributes>): HookReturn;
beforeFindAfterOptions(options: FindOptions): HookReturn; afterBulkRestore(options: RestoreOptions<TAttributes>): HookReturn;
afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn; beforeBulkUpdate(options: UpdateOptions<TAttributes>): HookReturn;
afterBulkUpdate(options: UpdateOptions<TAttributes>): HookReturn;
beforeFind(options: FindOptions<TAttributes>): HookReturn;
beforeCount(options: CountOptions<TAttributes>): HookReturn;
beforeFindAfterExpandIncludeAll(options: FindOptions<TAttributes>): HookReturn;
beforeFindAfterOptions(options: FindOptions<TAttributes>): HookReturn;
afterFind(instancesOrInstance: M[] | M | null, options: FindOptions<TAttributes>): HookReturn;
beforeSync(options: SyncOptions): HookReturn; beforeSync(options: SyncOptions): HookReturn;
afterSync(options: SyncOptions): HookReturn; afterSync(options: SyncOptions): HookReturn;
beforeBulkSync(options: SyncOptions): HookReturn; beforeBulkSync(options: SyncOptions): HookReturn;
afterBulkSync(options: SyncOptions): HookReturn; afterBulkSync(options: SyncOptions): HookReturn;
} }
export interface SequelizeHooks extends ModelHooks { export interface SequelizeHooks<
beforeDefine(attributes: ModelAttributes, options: ModelOptions<Model>): void; M extends Model<TAttributes, TCreationAttributes> = Model,
TAttributes = any,
TCreationAttributes = TAttributes
> extends ModelHooks<M, TAttributes> {
beforeDefine(attributes: ModelAttributes<M, TCreationAttributes>, options: ModelOptions<M>): void;
afterDefine(model: typeof Model): void; afterDefine(model: typeof Model): void;
beforeInit(config: Config, options: Options): void; beforeInit(config: Config, options: Options): void;
afterInit(sequelize: Sequelize): void; afterInit(sequelize: Sequelize): void;
...@@ -67,33 +77,72 @@ export interface SequelizeHooks extends ModelHooks { ...@@ -67,33 +77,72 @@ export interface SequelizeHooks extends ModelHooks {
/** /**
* Virtual class for deduplication * Virtual class for deduplication
*/ */
export class Hooks { export class Hooks<
M extends Model<TModelAttributes, TCreationAttributes> = Model,
TModelAttributes extends {} = any,
TCreationAttributes extends {} = TModelAttributes
> {
/**
* A dummy variable that doesn't exist on the real object. This exists so
* Typescript can infer the type of the attributes in static functions. Don't
* try to access this!
*/
_model: M;
/**
* A similar dummy variable that doesn't exist on the real object. Do not
* try to access this in real code.
*/
_attributes: TModelAttributes;
/**
* A similar dummy variable that doesn't exist on the real object. Do not
* try to access this in real code.
*/
_creationAttributes: TCreationAttributes;
/** /**
* Add a hook to the model * Add a hook to the model
* *
* @param name Provide a name for the hook function. It can be used to remove the hook later or to order * @param name Provide a name for the hook function. It can be used to remove the hook later or to order
* hooks based on some sort of priority system in the future. * hooks based on some sort of priority system in the future.
*/ */
public static addHook<C extends typeof Hooks, K extends keyof SequelizeHooks>( public static addHook<
H extends Hooks,
K extends keyof SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>
>(
this: HooksStatic<H>,
hookType: K, hookType: K,
name: string, name: string,
fn: SequelizeHooks[K] fn: SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>[K]
): C; ): HooksCtor<H>;
public static addHook<C extends typeof Hooks, K extends keyof SequelizeHooks>( public static addHook<
H extends Hooks,
K extends keyof SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>
>(
this: HooksStatic<H>,
hookType: K, hookType: K,
fn: SequelizeHooks[K] fn: SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>[K]
): C; ): HooksCtor<H>;
/** /**
* Remove hook from the model * Remove hook from the model
*/ */
public static removeHook<C extends typeof Hooks, K extends keyof SequelizeHooks>(hookType: K, name: string): C; public static removeHook<H extends Hooks>(
this: HooksStatic<H>,
hookType: keyof SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>,
name: string,
): HooksCtor<H>;
/** /**
* Check whether the mode has any hooks of this type * Check whether the mode has any hooks of this type
*/ */
public static hasHook<K extends keyof SequelizeHooks>(hookType: K): boolean; public static hasHook<H extends Hooks>(
public static hasHooks<K extends keyof SequelizeHooks>(hookType: K): boolean; this: HooksStatic<H>,
hookType: keyof SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>,
): boolean;
public static hasHooks<H extends Hooks>(
this: HooksStatic<H>,
hookType: keyof SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>,
): boolean;
/** /**
* Add a hook to the model * Add a hook to the model
...@@ -101,16 +150,28 @@ export class Hooks { ...@@ -101,16 +150,28 @@ export class Hooks {
* @param name Provide a name for the hook function. It can be used to remove the hook later or to order * @param name Provide a name for the hook function. It can be used to remove the hook later or to order
* hooks based on some sort of priority system in the future. * hooks based on some sort of priority system in the future.
*/ */
public addHook<K extends keyof SequelizeHooks>(hookType: K, name: string, fn: SequelizeHooks[K]): this; public addHook<K extends keyof SequelizeHooks<M, TModelAttributes, TCreationAttributes>>(
public addHook<K extends keyof SequelizeHooks>(hookType: K, fn: SequelizeHooks[K]): this; hookType: K,
name: string,
fn: SequelizeHooks<Model, TModelAttributes, TCreationAttributes>[K]
): this;
public addHook<K extends keyof SequelizeHooks<M, TModelAttributes, TCreationAttributes>>(
hookType: K, fn: SequelizeHooks<M, TModelAttributes, TCreationAttributes>[K]): this;
/** /**
* Remove hook from the model * Remove hook from the model
*/ */
public removeHook<K extends keyof SequelizeHooks>(hookType: K, name: string): this; public removeHook<K extends keyof SequelizeHooks<M, TModelAttributes, TCreationAttributes>>(
hookType: K,
name: string
): this;
/** /**
* Check whether the mode has any hooks of this type * Check whether the mode has any hooks of this type
*/ */
public hasHook<K extends keyof SequelizeHooks>(hookType: K): boolean; public hasHook<K extends keyof SequelizeHooks<M, TModelAttributes, TCreationAttributes>>(hookType: K): boolean;
public hasHooks<K extends keyof SequelizeHooks>(hookType: K): boolean; public hasHooks<K extends keyof SequelizeHooks<M, TModelAttributes, TCreationAttributes>>(hookType: K): boolean;
} }
export type HooksCtor<H extends Hooks> = typeof Hooks & { new(): H };
export type HooksStatic<H extends Hooks> = { new(): H };
import { DataType } from './data-types'; import { DataType } from './data-types';
import { Logging, Model, ModelAttributeColumnOptions, ModelAttributes, Transactionable, WhereOptions, Filterable, Poolable } from './model'; import {
Logging,
Model,
ModelAttributeColumnOptions,
ModelAttributes,
Transactionable,
WhereOptions,
Filterable,
Poolable,
ModelCtor, ModelStatic
} from './model';
import QueryTypes = require('./query-types'); import QueryTypes = require('./query-types');
import { Sequelize, RetryOptions } from './sequelize'; import { Sequelize, RetryOptions } from './sequelize';
import { Transaction } from './transaction'; import { Transaction } from './transaction';
...@@ -71,15 +81,15 @@ export interface QueryOptions extends Logging, Transactionable, Poolable { ...@@ -71,15 +81,15 @@ export interface QueryOptions extends Logging, Transactionable, Poolable {
fieldMap?: FieldMap; fieldMap?: FieldMap;
} }
export interface QueryOptionsWithWhere extends QueryOptions, Filterable { export interface QueryOptionsWithWhere extends QueryOptions, Filterable<any> {
} }
export interface QueryOptionsWithModel extends QueryOptions { export interface QueryOptionsWithModel<M extends Model> extends QueryOptions {
/** /**
* A sequelize model used to build the returned model instances (used to be called callee) * A sequelize model used to build the returned model instances (used to be called callee)
*/ */
model: typeof Model; model: ModelStatic<M>;
} }
export interface QueryOptionsWithType<T extends QueryTypes> extends QueryOptions { export interface QueryOptionsWithType<T extends QueryTypes> extends QueryOptions {
...@@ -189,7 +199,7 @@ export interface IndexesOptions { ...@@ -189,7 +199,7 @@ export interface IndexesOptions {
/** /**
* Optional where parameter for index. Can be used to limit the index to certain rows. * Optional where parameter for index. Can be used to limit the index to certain rows.
*/ */
where?: WhereOptions; where?: WhereOptions<any>;
/** /**
* Prefix to append to the index name. * Prefix to append to the index name.
...@@ -215,7 +225,7 @@ export interface AddDefaultConstraintOptions extends BaseConstraintOptions { ...@@ -215,7 +225,7 @@ export interface AddDefaultConstraintOptions extends BaseConstraintOptions {
export interface AddCheckConstraintOptions extends BaseConstraintOptions { export interface AddCheckConstraintOptions extends BaseConstraintOptions {
type: 'check'; type: 'check';
where?: WhereOptions; where?: WhereOptions<any>;
} }
export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions {
...@@ -321,9 +331,9 @@ export class QueryInterface { ...@@ -321,9 +331,9 @@ export class QueryInterface {
* @param attributes Hash of attributes, key is attribute name, value is data type * @param attributes Hash of attributes, key is attribute name, value is data type
* @param options Table options. * @param options Table options.
*/ */
public createTable( public createTable<M extends Model>(
tableName: string | { schema?: string; tableName?: string }, tableName: string | { schema?: string; tableName?: string },
attributes: ModelAttributes, attributes: ModelAttributes<M, M['_creationAttributes']>,
options?: QueryInterfaceCreateTableOptions options?: QueryInterfaceCreateTableOptions
): Promise<void>; ): Promise<void>;
...@@ -490,11 +500,11 @@ export class QueryInterface { ...@@ -490,11 +500,11 @@ export class QueryInterface {
/** /**
* Updates a row * Updates a row
*/ */
public update( public update<M extends Model>(
instance: Model, instance: M,
tableName: TableName, tableName: TableName,
values: object, values: object,
identifier: WhereOptions, identifier: WhereOptions<M['_attributes']>,
options?: QueryOptions options?: QueryOptions
): Promise<object>; ): Promise<object>;
...@@ -504,7 +514,7 @@ export class QueryInterface { ...@@ -504,7 +514,7 @@ export class QueryInterface {
public bulkUpdate( public bulkUpdate(
tableName: TableName, tableName: TableName,
values: object, values: object,
identifier: WhereOptions, identifier: WhereOptions<any>,
options?: QueryOptions, options?: QueryOptions,
attributes?: string[] | string attributes?: string[] | string
): Promise<object>; ): Promise<object>;
...@@ -512,14 +522,19 @@ export class QueryInterface { ...@@ -512,14 +522,19 @@ export class QueryInterface {
/** /**
* Deletes a row * Deletes a row
*/ */
public delete(instance: Model | null, tableName: TableName, identifier: WhereOptions, options?: QueryOptions): Promise<object>; public delete(
instance: Model | null,
tableName: TableName,
identifier: WhereOptions<any>,
options?: QueryOptions
): Promise<object>;
/** /**
* Deletes multiple rows at once * Deletes multiple rows at once
*/ */
public bulkDelete( public bulkDelete(
tableName: TableName, tableName: TableName,
identifier: WhereOptions, identifier: WhereOptions<any>,
options?: QueryOptions, options?: QueryOptions,
model?: typeof Model model?: typeof Model
): Promise<object>; ): Promise<object>;
...@@ -532,11 +547,11 @@ export class QueryInterface { ...@@ -532,11 +547,11 @@ export class QueryInterface {
/** /**
* Increments a row value * Increments a row value
*/ */
public increment( public increment<M extends Model>(
instance: Model, instance: Model,
tableName: TableName, tableName: TableName,
values: object, values: object,
identifier: WhereOptions, identifier: WhereOptions<M['_attributes']>,
options?: QueryOptions options?: QueryOptions
): Promise<object>; ): Promise<object>;
......
import { DataType } from './data-types'; import { DataType } from './data-types';
import { Model, WhereOptions } from './model'; import { Model, ModelCtor, WhereOptions } from './model';
export type Primitive = 'string' | 'number' | 'boolean'; export type Primitive = 'string' | 'number' | 'boolean';
...@@ -24,16 +24,21 @@ export function formatNamedParameters(sql: string, parameters: { ...@@ -24,16 +24,21 @@ export function formatNamedParameters(sql: string, parameters: {
}, dialect: string): string; }, dialect: string): string;
export function cloneDeep<T>(obj: T, fn?: (el: unknown) => unknown): T; export function cloneDeep<T>(obj: T, fn?: (el: unknown) => unknown): T;
export interface OptionsForMapping { export interface OptionsForMapping<TAttributes> {
attributes?: string[]; attributes?: string[];
where?: WhereOptions; where?: WhereOptions<TAttributes>;
} }
/** Expand and normalize finder options */ /** Expand and normalize finder options */
export function mapFinderOptions<T extends OptionsForMapping>(options: T, model: typeof Model): T; export function mapFinderOptions<M extends Model, T extends OptionsForMapping<M['_attributes']>>(
options: T,
model: ModelCtor<M>
): T;
/* Used to map field names in attributes and where conditions */ /* Used to map field names in attributes and where conditions */
export function mapOptionFieldNames<T extends OptionsForMapping>(options: T, model: typeof Model): T; export function mapOptionFieldNames<M extends Model, T extends OptionsForMapping<M['_attributes']>>(
options: T, model: ModelCtor<M>
): T;
export function mapWhereFieldNames(attributes: object, model: typeof Model): object; export function mapWhereFieldNames(attributes: object, model: typeof Model): object;
/** Used to map field names in values */ /** Used to map field names in values */
......
import { DataTypes, Model } from 'sequelize'; import { BuildOptions, DataTypes, Model } from 'sequelize';
import { sequelize } from './connection'; import { sequelize } from './connection';
// I really wouldn't recommend this, but if you want you can still use define() and interfaces // I really wouldn't recommend this, but if you want you can still use define() and interfaces
interface User extends Model { interface UserAttributes {
id: number; id: number;
username: string; username: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
type UserModel = { interface UserCreationAttributes extends Partial<UserAttributes> {}
new (): User
customStaticMethod(): unknown
} & typeof Model;
const User = sequelize.define('User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UserModel; interface UserModel extends Model<UserAttributes, UserCreationAttributes>, UserAttributes {}
async function test() { const User = sequelize.define<UserModel>(
User.customStaticMethod(); 'User', { firstName: DataTypes.STRING }, { tableName: 'users' });
const user: User = new User(); async function test() {
const user: UserModel = new User() as UserModel;
const user2: User = (await User.findOne()) as User; const user2: UserModel | null = await User.findOne();
if (!user2) return;
user2.firstName = 'John'; user2.firstName = 'John';
await user2.save(); await user2.save();
} }
// The below doesn't define Attribute types, but should still work
interface UntypedUserModel extends Model, UserAttributes {}
type UntypedUserModelStatic = typeof Model & {
new (values?: keyof any, options?: BuildOptions): UntypedUserModel;
customStaticMethod(): unknown;
}
const UntypedUser = sequelize.define<UntypedUserModel>(
'User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UntypedUserModelStatic;
UntypedUser.customStaticMethod = () => {};
async function testUntyped() {
UntypedUser.customStaticMethod();
const user: UntypedUserModel = new UntypedUser() as UntypedUserModel;
const user2: UntypedUserModel | null = await UntypedUser.findOne();
if (!user2) return;
user2.firstName = 'John';
await user2.save();
}
// This file is used as example. // This file is used as example.
import {BuildOptions, DataTypes, Model, Sequelize} from 'sequelize'; import { BuildOptions, DataTypes, Model, Sequelize } from 'sequelize';
import { import {
Association, Association,
HasManyAddAssociationMixin, HasManyAddAssociationMixin,
......
...@@ -5,7 +5,7 @@ import { OptimisticLockError } from '../lib/errors'; ...@@ -5,7 +5,7 @@ import { OptimisticLockError } from '../lib/errors';
async function test() { async function test() {
try { try {
await User.create({ username: 'john_doe' }); await User.create({ username: 'john_doe', firstName: 'John' });
} catch (e) { } catch (e) {
if (e instanceof UniqueConstraintError) { if (e instanceof UniqueConstraintError) {
throw new Error((e as UniqueConstraintError).sql); throw new Error((e as UniqueConstraintError).sql);
......
import { User } from "./models/User";
User.findOne({ where: { firstName: 'John' }});
// The below line should be an error if uncommented, thanks to the new
// TAttributes-based typechecking
// User.findOne({ where: { blah: 'blah2' }});
...@@ -7,11 +7,26 @@ import { ...@@ -7,11 +7,26 @@ import {
FindOptions, FindOptions,
Model, Model,
ModelCtor, ModelCtor,
Op Op,
Optional
} from 'sequelize'; } from 'sequelize';
import { sequelize } from '../connection'; import { sequelize } from '../connection';
export class User extends Model { export interface UserAttributes {
id: number;
username: string;
firstName: string;
lastName: string;
groupId: number;
}
/**
* In this case, we make every single field optional. In real cases, only
* fields that have default/autoincrement values should be made optional.
*/
export interface UserCreationAttributes extends Optional<UserAttributes, 'id' | 'username' | 'lastName' | 'groupId'> {}
export class User extends Model<UserAttributes, UserCreationAttributes> implements UserAttributes {
public static associations: { public static associations: {
group: BelongsTo<User, UserGroup>; group: BelongsTo<User, UserGroup>;
}; };
...@@ -20,11 +35,11 @@ export class User extends Model { ...@@ -20,11 +35,11 @@ export class User extends Model {
public username!: string; public username!: string;
public firstName!: string; public firstName!: string;
public lastName!: string; public lastName!: string;
public groupId!: number;
public createdAt!: Date; public createdAt!: Date;
public updatedAt!: Date; public updatedAt!: Date;
// mixins for association (optional) // mixins for association (optional)
public groupId!: number;
public group?: UserGroup; public group?: UserGroup;
public getGroup!: BelongsToGetAssociationMixin<UserGroup>; public getGroup!: BelongsToGetAssociationMixin<UserGroup>;
public setGroup!: BelongsToSetAssociationMixin<UserGroup, number>; public setGroup!: BelongsToSetAssociationMixin<UserGroup, number>;
...@@ -36,6 +51,7 @@ User.init( ...@@ -36,6 +51,7 @@ User.init(
firstName: DataTypes.STRING, firstName: DataTypes.STRING,
lastName: DataTypes.STRING, lastName: DataTypes.STRING,
username: DataTypes.STRING, username: DataTypes.STRING,
groupId: DataTypes.NUMBER,
}, },
{ {
version: true, version: true,
...@@ -86,7 +102,7 @@ User.afterFind((users, options) => { ...@@ -86,7 +102,7 @@ User.afterFind((users, options) => {
}); });
// TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly // TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly
User.addHook('beforeFind', 'test', (options: FindOptions) => { User.addHook('beforeFind', 'test', (options: FindOptions<UserAttributes>) => {
return undefined; return undefined;
}); });
......
...@@ -14,6 +14,8 @@ import { ...@@ -14,6 +14,8 @@ import {
} from 'sequelize'; } from 'sequelize';
import { sequelize } from '../connection'; import { sequelize } from '../connection';
// This class doesn't extend the generic Model<TAttributes>, but should still
// function just fine, with a bit less safe type-checking
export class UserGroup extends Model { export class UserGroup extends Model {
public static associations: { public static associations: {
users: HasMany<UserGroup, User> users: HasMany<UserGroup, User>
......
import { Config, Sequelize, Model, QueryTypes } from 'sequelize'; import { Config, Sequelize, Model, QueryTypes, ModelCtor } from 'sequelize';
import { Fn } from '../lib/utils'; import { Fn } from '../lib/utils';
Sequelize.useCLS({ Sequelize.useCLS({
...@@ -6,7 +6,7 @@ Sequelize.useCLS({ ...@@ -6,7 +6,7 @@ Sequelize.useCLS({
export const sequelize = new Sequelize({ export const sequelize = new Sequelize({
hooks: { hooks: {
afterConnect: (connection, config: Config) => { afterConnect: (connection: unknown, config: Config) => {
// noop // noop
} }
}, },
...@@ -56,7 +56,7 @@ const rnd: Fn = sequelize.random(); ...@@ -56,7 +56,7 @@ const rnd: Fn = sequelize.random();
class Model1 extends Model{} class Model1 extends Model{}
class Model2 extends Model{} class Model2 extends Model{}
const myModel: typeof Model1 = sequelize.models.asd; const myModel: ModelCtor<Model1> = sequelize.models.asd;
myModel.hasOne(Model2) myModel.hasOne(Model2)
myModel.findAll(); myModel.findAll();
......
...@@ -8,7 +8,7 @@ async function trans() { ...@@ -8,7 +8,7 @@ async function trans() {
transaction.afterCommit(() => console.log('transaction complete')); transaction.afterCommit(() => console.log('transaction complete'));
User.create( User.create(
{ {
data: 123, firstName: 'John',
}, },
{ {
transaction, transaction,
......
...@@ -25,7 +25,7 @@ where = { ...@@ -25,7 +25,7 @@ where = {
date: new Date() date: new Date()
}; };
// Optional values // Optional values
let whereWithOptionals: { needed: number; optional?: number } = { needed: 2 }; let whereWithOptionals: { needed: number; optional?: number } = { needed: 2 };
where = whereWithOptionals; where = whereWithOptionals;
...@@ -39,10 +39,16 @@ where = whereWithOptionals; ...@@ -39,10 +39,16 @@ where = whereWithOptionals;
const and: AndOperator = { const and: AndOperator = {
[Op.and]: { a: 5 }, // AND (a = 5) [Op.and]: { a: 5 }, // AND (a = 5)
}; };
const typedAnd: AndOperator<{ a: number }> = {
[Op.and]: { a: 5 }, // AND (a = 5)
};
const or: OrOperator = { const or: OrOperator = {
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
}; };
const typedOr: OrOperator<{ a: number }> = {
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
};
let operators: WhereOperators = { let operators: WhereOperators = {
[Op.gt]: 6, // > 6 [Op.gt]: 6, // > 6
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!