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

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:
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
......@@ -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;
......@@ -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;
......@@ -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,
......
......@@ -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>>;
......@@ -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.
*/
......@@ -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;
}
......@@ -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;
}
......@@ -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;
}
......@@ -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;
}
/**
......@@ -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.
*/
......
......@@ -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.
*/
......@@ -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.
*/
......@@ -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.
......
......@@ -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.
*/
......@@ -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.
......@@ -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.
......@@ -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.
......@@ -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.
......@@ -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.
......@@ -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.
......@@ -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.
*/
......
......@@ -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.
*/
......@@ -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.
*/
......@@ -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.
......
......@@ -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
* interfaces.
*/
export interface ModelHooks<M extends Model = Model> {
export interface ModelHooks<M extends Model = Model, TAttributes = any> {
beforeValidate(instance: M, options: ValidationOptions): HookReturn;
afterValidate(instance: M, options: ValidationOptions): HookReturn;
beforeCreate(attributes: M, options: CreateOptions): HookReturn;
afterCreate(attributes: M, options: CreateOptions): HookReturn;
beforeCreate(attributes: M, options: CreateOptions<TAttributes>): HookReturn;
afterCreate(attributes: M, options: CreateOptions<TAttributes>): HookReturn;
beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn;
afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn;
beforeRestore(instance: M, options: InstanceRestoreOptions): HookReturn;
afterRestore(instance: M, options: InstanceRestoreOptions): HookReturn;
beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn;
afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn;
beforeSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn;
afterSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn;
beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn;
afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn;
beforeBulkDestroy(options: DestroyOptions): HookReturn;
afterBulkDestroy(options: DestroyOptions): HookReturn;
beforeBulkRestore(options: RestoreOptions): HookReturn;
afterBulkRestore(options: RestoreOptions): HookReturn;
beforeBulkUpdate(options: UpdateOptions): HookReturn;
afterBulkUpdate(options: UpdateOptions): HookReturn;
beforeFind(options: FindOptions): HookReturn;
beforeCount(options: CountOptions): HookReturn;
beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn;
beforeFindAfterOptions(options: FindOptions): HookReturn;
afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn;
beforeUpdate(instance: M, options: InstanceUpdateOptions<TAttributes>): HookReturn;
afterUpdate(instance: M, options: InstanceUpdateOptions<TAttributes>): HookReturn;
beforeSave(
instance: M,
options: InstanceUpdateOptions<TAttributes> | CreateOptions<TAttributes>
): HookReturn;
afterSave(
instance: M,
options: InstanceUpdateOptions<TAttributes> | CreateOptions<TAttributes>
): HookReturn;
beforeBulkCreate(instances: M[], options: BulkCreateOptions<TAttributes>): HookReturn;
afterBulkCreate(instances: M[], options: BulkCreateOptions<TAttributes>): HookReturn;
beforeBulkDestroy(options: DestroyOptions<TAttributes>): HookReturn;
afterBulkDestroy(options: DestroyOptions<TAttributes>): HookReturn;
beforeBulkRestore(options: RestoreOptions<TAttributes>): HookReturn;
afterBulkRestore(options: RestoreOptions<TAttributes>): 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;
afterSync(options: SyncOptions): HookReturn;
beforeBulkSync(options: SyncOptions): HookReturn;
afterBulkSync(options: SyncOptions): HookReturn;
}
export interface SequelizeHooks extends ModelHooks {
beforeDefine(attributes: ModelAttributes, options: ModelOptions<Model>): void;
export interface SequelizeHooks<
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;
beforeInit(config: Config, options: Options): void;
afterInit(sequelize: Sequelize): void;
......@@ -67,33 +77,72 @@ export interface SequelizeHooks extends ModelHooks {
/**
* 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
*
* @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.
*/
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,
name: string,
fn: SequelizeHooks[K]
): C;
public static addHook<C extends typeof Hooks, K extends keyof SequelizeHooks>(
fn: SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>[K]
): HooksCtor<H>;
public static addHook<
H extends Hooks,
K extends keyof SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>
>(
this: HooksStatic<H>,
hookType: K,
fn: SequelizeHooks[K]
): C;
fn: SequelizeHooks<H['_model'], H['_attributes'], H['_creationAttributes']>[K]
): HooksCtor<H>;
/**
* 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
*/
public static hasHook<K extends keyof SequelizeHooks>(hookType: K): boolean;
public static hasHooks<K extends keyof SequelizeHooks>(hookType: K): boolean;
public static hasHook<H extends Hooks>(
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
......@@ -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
* 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>(hookType: K, fn: SequelizeHooks[K]): this;
public addHook<K extends keyof SequelizeHooks<M, TModelAttributes, TCreationAttributes>>(
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
*/
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
*/
public hasHook<K extends keyof SequelizeHooks>(hookType: K): boolean;
public hasHooks<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<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 { 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 { Sequelize, RetryOptions } from './sequelize';
import { Transaction } from './transaction';
......@@ -71,15 +81,15 @@ export interface QueryOptions extends Logging, Transactionable, Poolable {
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)
*/
model: typeof Model;
model: ModelStatic<M>;
}
export interface QueryOptionsWithType<T extends QueryTypes> extends QueryOptions {
......@@ -189,7 +199,7 @@ export interface IndexesOptions {
/**
* 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.
......@@ -215,7 +225,7 @@ export interface AddDefaultConstraintOptions extends BaseConstraintOptions {
export interface AddCheckConstraintOptions extends BaseConstraintOptions {
type: 'check';
where?: WhereOptions;
where?: WhereOptions<any>;
}
export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions {
......@@ -321,9 +331,9 @@ export class QueryInterface {
* @param attributes Hash of attributes, key is attribute name, value is data type
* @param options Table options.
*/
public createTable(
public createTable<M extends Model>(
tableName: string | { schema?: string; tableName?: string },
attributes: ModelAttributes,
attributes: ModelAttributes<M, M['_creationAttributes']>,
options?: QueryInterfaceCreateTableOptions
): Promise<void>;
......@@ -490,11 +500,11 @@ export class QueryInterface {
/**
* Updates a row
*/
public update(
instance: Model,
public update<M extends Model>(
instance: M,
tableName: TableName,
values: object,
identifier: WhereOptions,
identifier: WhereOptions<M['_attributes']>,
options?: QueryOptions
): Promise<object>;
......@@ -504,7 +514,7 @@ export class QueryInterface {
public bulkUpdate(
tableName: TableName,
values: object,
identifier: WhereOptions,
identifier: WhereOptions<any>,
options?: QueryOptions,
attributes?: string[] | string
): Promise<object>;
......@@ -512,14 +522,19 @@ export class QueryInterface {
/**
* 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
*/
public bulkDelete(
tableName: TableName,
identifier: WhereOptions,
identifier: WhereOptions<any>,
options?: QueryOptions,
model?: typeof Model
): Promise<object>;
......@@ -532,11 +547,11 @@ export class QueryInterface {
/**
* Increments a row value
*/
public increment(
public increment<M extends Model>(
instance: Model,
tableName: TableName,
values: object,
identifier: WhereOptions,
identifier: WhereOptions<M['_attributes']>,
options?: QueryOptions
): Promise<object>;
......
import { DataType } from './data-types';
import { Model, WhereOptions } from './model';
import { Model, ModelCtor, WhereOptions } from './model';
export type Primitive = 'string' | 'number' | 'boolean';
......@@ -24,16 +24,21 @@ export function formatNamedParameters(sql: string, parameters: {
}, dialect: string): string;
export function cloneDeep<T>(obj: T, fn?: (el: unknown) => unknown): T;
export interface OptionsForMapping {
export interface OptionsForMapping<TAttributes> {
attributes?: string[];
where?: WhereOptions;
where?: WhereOptions<TAttributes>;
}
/** 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 */
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;
/** Used to map field names in values */
......
import { DataTypes, Model } from 'sequelize';
import { BuildOptions, DataTypes, Model } from 'sequelize';
import { sequelize } from './connection';
// 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;
username: string;
firstName: string;
......@@ -12,19 +12,43 @@ interface User extends Model {
updatedAt: Date;
}
type UserModel = {
new (): User
customStaticMethod(): unknown
} & typeof Model;
interface UserCreationAttributes extends Partial<UserAttributes> {}
const User = sequelize.define('User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UserModel;
interface UserModel extends Model<UserAttributes, UserCreationAttributes>, UserAttributes {}
const User = sequelize.define<UserModel>(
'User', { firstName: DataTypes.STRING }, { tableName: 'users' });
async function test() {
User.customStaticMethod();
const user: UserModel = new User() as UserModel;
const user2: UserModel | null = await User.findOne();
if (!user2) return;
user2.firstName = 'John';
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: User = new User();
const user: UntypedUserModel = new UntypedUser() as UntypedUserModel;
const user2: User = (await User.findOne()) as User;
const user2: UntypedUserModel | null = await UntypedUser.findOne();
if (!user2) return;
user2.firstName = 'John';
......
// This file is used as example.
import {BuildOptions, DataTypes, Model, Sequelize} from 'sequelize';
import { BuildOptions, DataTypes, Model, Sequelize } from 'sequelize';
import {
Association,
HasManyAddAssociationMixin,
......
......@@ -5,7 +5,7 @@ import { OptimisticLockError } from '../lib/errors';
async function test() {
try {
await User.create({ username: 'john_doe' });
await User.create({ username: 'john_doe', firstName: 'John' });
} catch (e) {
if (e instanceof UniqueConstraintError) {
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 {
FindOptions,
Model,
ModelCtor,
Op
Op,
Optional
} from 'sequelize';
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: {
group: BelongsTo<User, UserGroup>;
};
......@@ -20,11 +35,11 @@ export class User extends Model {
public username!: string;
public firstName!: string;
public lastName!: string;
public groupId!: number;
public createdAt!: Date;
public updatedAt!: Date;
// mixins for association (optional)
public groupId!: number;
public group?: UserGroup;
public getGroup!: BelongsToGetAssociationMixin<UserGroup>;
public setGroup!: BelongsToSetAssociationMixin<UserGroup, number>;
......@@ -36,6 +51,7 @@ User.init(
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
username: DataTypes.STRING,
groupId: DataTypes.NUMBER,
},
{
version: true,
......@@ -86,7 +102,7 @@ User.afterFind((users, options) => {
});
// 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;
});
......
......@@ -14,6 +14,8 @@ import {
} from 'sequelize';
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 {
public static associations: {
users: HasMany<UserGroup, User>
......
import { Config, Sequelize, Model, QueryTypes } from 'sequelize';
import { Config, Sequelize, Model, QueryTypes, ModelCtor } from 'sequelize';
import { Fn } from '../lib/utils';
Sequelize.useCLS({
......@@ -6,7 +6,7 @@ Sequelize.useCLS({
export const sequelize = new Sequelize({
hooks: {
afterConnect: (connection, config: Config) => {
afterConnect: (connection: unknown, config: Config) => {
// noop
}
},
......@@ -56,7 +56,7 @@ const rnd: Fn = sequelize.random();
class Model1 extends Model{}
class Model2 extends Model{}
const myModel: typeof Model1 = sequelize.models.asd;
const myModel: ModelCtor<Model1> = sequelize.models.asd;
myModel.hasOne(Model2)
myModel.findAll();
......
......@@ -8,7 +8,7 @@ async function trans() {
transaction.afterCommit(() => console.log('transaction complete'));
User.create(
{
data: 123,
firstName: 'John',
},
{
transaction,
......
......@@ -39,10 +39,16 @@ where = whereWithOptionals;
const and: AndOperator = {
[Op.and]: { a: 5 }, // AND (a = 5)
};
const typedAnd: AndOperator<{ a: number }> = {
[Op.and]: { a: 5 }, // AND (a = 5)
};
const or: OrOperator = {
[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 = {
[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!