diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index 684e9a48c286..cde929b3dee2 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -5,7 +5,8 @@ Sequelize provides its own TypeScript definitions. Please note that only **TypeScript >= 4.1** is supported. Our TypeScript support does not follow SemVer. We will support TypeScript releases for at least one year, after which they may be dropped in a SemVer MINOR release. -As Sequelize heavily relies on runtime property assignments, TypeScript won't be very useful out of the box. A decent amount of manual type declarations are needed to make models workable. +As Sequelize heavily relies on runtime property assignments, TypeScript won't be very useful out of the box. +A decent amount of manual type declarations are needed to make models workable. ## Installation @@ -16,131 +17,177 @@ In order to avoid installation bloat for non TS users, you must install the foll ## Usage -Example of a minimal TypeScript project with strict type-checking for attributes. - **Important**: You must use `declare` on your class properties typings to ensure TypeScript does not emit those class properties. See [Caveat with Public Class Fields](./model-basics.html#caveat-with-public-class-fields) -**NOTE:** Keep the following code in sync with `/types/test/typescriptDocs/ModelInit.ts` to ensure it typechecks correctly. +Sequelize Models accept two generic types to define what the model's Attributes & Creation Attributes are like: ```typescript -/** - * Keep this file in sync with the code in the "Usage" section in typescript.md - */ -import { - Association, DataTypes, HasManyAddAssociationMixin, HasManyCountAssociationsMixin, - HasManyCreateAssociationMixin, HasManyGetAssociationsMixin, HasManyHasAssociationMixin, Model, - ModelDefined, Optional, Sequelize -} from "sequelize"; +import { Model, Optional } from 'sequelize'; -const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); +// We don't recommend doing this. Read on for the new way of declaring Model typings. -// These are all the attributes in the User model -interface UserAttributes { - id: number; - name: string; - preferredName: string | null; +type UserAttributes = { + id: number, + name: string, + // other attributes... +}; + +// we're telling the Model that 'id' is optional +// when creating an instance of the model (such as using Model.create()). +type UserCreationAttributes = Optional; + +class User extends Model { + declare id: number; + declare string: number; + // other attributes... } +``` -// Some attributes are optional in `User.build` and `User.create` calls -interface UserCreationAttributes extends Optional {} +This solution is verbose. Sequelize >=6.14.0 provides new utility types that will drastically reduce the amount +of boilerplate necessary: `InferAttributes`, and `InferCreationAttributes`. They will extract Attribute typings +directly from the Model: + +```typescript +import { Model, InferAttributes, InferCreationAttributes, CreationOptional } from 'sequelize'; + +// order of InferAttributes & InferCreationAttributes is important. +class User extends Model, InferCreationAttributes> { + // 'CreationOptional' is a special type that marks the field as optional + // when creating an instance of the model (such as using Model.create()). + declare id: CreationOptional; + declare string: number; + // other attributes... +} +``` + +Important things to know about `InferAttributes` & `InferCreationAttributes` work: They will select all declared properties of the class except: + +- Static fields and methods. +- Methods (anything whose type is a function). +- Those whose type uses the branded type `NonAttribute`. +- Those excluded by using AttributesOf like this: `InferAttributes`. +- Those declared by the Model superclass (but not intermediary classes!). + If one of your attributes shares the same name as one of the properties of `Model`, change its name. + Doing this is likely to cause issues anyway. +- Getter & setters are not automatically excluded. Set their return / parameter type to `NonAttribute`, + or add them to `omit` to exclude them. + +`InferCreationAttributes` works the same way as `AttributesOf` with one exception: Properties typed using the `CreationOptional` type +will be marked as optional. + +You only need to use `CreationOptional` & `NonAttribute` on class instance fields or getters. + +Example of a minimal TypeScript project with strict type-checking for attributes: -class User extends Model - implements UserAttributes { - declare id: number; // Note that the `null assertion` `!` is required in strict mode. +[//]: # (NOTE for maintainers: Keep the following code in sync with `/types/test/typescriptDocs/ModelInit.ts` to ensure it typechecks correctly.) + +```typescript +import { + Association, DataTypes, HasManyAddAssociationMixin, HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, HasManyGetAssociationsMixin, HasManyHasAssociationMixin, + HasManySetAssociationsMixin, HasManyAddAssociationsMixin, HasManyHasAssociationsMixin, + HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, Model, ModelDefined, Optional, + Sequelize, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute +} from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// 'projects' is excluded as it's not an attribute, it's an association. +class User extends Model, InferCreationAttributes> { + // id can be undefined during creation when using `autoIncrement` + declare id: CreationOptional; declare name: string; declare preferredName: string | null; // for nullable fields // timestamps! - declare readonly createdAt: Date; - declare readonly updatedAt: Date; + // createdAt can be undefined during creation + declare createdAt: CreationOptional; + // updatedAt can be undefined during creation + declare updatedAt: CreationOptional; // Since TS cannot determine model association at compile time // we have to declare them here purely virtually // these will not exist until `Model.init` was called. declare getProjects: HasManyGetAssociationsMixin; // Note the null assertions! declare addProject: HasManyAddAssociationMixin; + declare addProjects: HasManyAddAssociationsMixin; + declare setProjects: HasManySetAssociationsMixin; + declare removeProject: HasManyRemoveAssociationMixin; + declare removeProjects: HasManyRemoveAssociationsMixin; declare hasProject: HasManyHasAssociationMixin; + declare hasProjects: HasManyHasAssociationsMixin; declare countProjects: HasManyCountAssociationsMixin; - declare createProject: HasManyCreateAssociationMixin; + declare createProject: HasManyCreateAssociationMixin; // You can also pre-declare possible inclusions, these will only be populated if you // actively include a relation. - declare readonly projects?: Project[]; // Note this is optional since it's only populated when explicitly requested in code + declare projects?: NonAttribute; // Note this is optional since it's only populated when explicitly requested in code + + // getters that are not attributes should be tagged using NonAttribute + // to remove them from the model's Attribute Typings. + get fullName(): NonAttribute { + return this.name; + } declare static associations: { projects: Association; }; } -interface ProjectAttributes { - id: number; - ownerId: number; - name: string; - description?: string; -} - -interface ProjectCreationAttributes extends Optional {} - -class Project extends Model - implements ProjectAttributes { - declare id: number; +class Project extends Model< + InferAttributes, + InferCreationAttributes +> { + // id can be undefined during creation when using `autoIncrement` + declare id: CreationOptional; declare ownerId: number; declare name: string; - declare readonly createdAt: Date; - declare readonly updatedAt: Date; -} + // `owner` is an eagerly-loaded association. + // We tag it as `NonAttribute` + declare owner?: NonAttribute; -interface AddressAttributes { - userId: number; - address: string; + // createdAt can be undefined during creation + declare createdAt: CreationOptional; + // updatedAt can be undefined during creation + declare updatedAt: CreationOptional; } -// You can write `extends Model` instead, -// but that will do the exact same thing as below -class Address extends Model implements AddressAttributes { +class Address extends Model< + InferAttributes
, + InferCreationAttributes
+> { declare userId: number; declare address: string; - declare readonly createdAt: Date; - declare readonly updatedAt: Date; + // createdAt can be undefined during creation + declare createdAt: CreationOptional; + // updatedAt can be undefined during creation + declare updatedAt: CreationOptional; } -// You can also define modules in a functional way -interface NoteAttributes { - id: number; - title: string; - content: string; -} - -// You can also set multiple attributes optional at once -interface NoteCreationAttributes - extends Optional {} - Project.init( { id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, - primaryKey: true, + primaryKey: true }, ownerId: { type: DataTypes.INTEGER.UNSIGNED, - allowNull: false, + allowNull: false }, name: { type: new DataTypes.STRING(128), - allowNull: false, - }, - description: { - type: new DataTypes.STRING(128), - allowNull: true, + allowNull: false }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, }, { sequelize, - tableName: "projects", + tableName: 'projects' } ); @@ -149,90 +196,103 @@ User.init( id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, - primaryKey: true, + primaryKey: true }, name: { type: new DataTypes.STRING(128), - allowNull: false, + allowNull: false }, preferredName: { type: new DataTypes.STRING(128), - allowNull: true, + allowNull: true }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, }, { - tableName: "users", - sequelize, // passing the `sequelize` instance is required + tableName: 'users', + sequelize // passing the `sequelize` instance is required } ); Address.init( { userId: { - type: DataTypes.INTEGER.UNSIGNED, + type: DataTypes.INTEGER.UNSIGNED }, address: { type: new DataTypes.STRING(128), - allowNull: false, + allowNull: false }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, }, { - tableName: "address", - sequelize, // passing the `sequelize` instance is required + tableName: 'address', + sequelize // passing the `sequelize` instance is required } ); +// You can also define modules in a functional way +interface NoteAttributes { + id: number; + title: string; + content: string; +} + +// You can also set multiple attributes optional at once +type NoteCreationAttributes = Optional; + // And with a functional approach defining a module looks like this const Note: ModelDefined< NoteAttributes, NoteCreationAttributes > = sequelize.define( - "Note", + 'Note', { id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, - primaryKey: true, + primaryKey: true }, title: { type: new DataTypes.STRING(64), - defaultValue: "Unnamed Note", + defaultValue: 'Unnamed Note' }, content: { type: new DataTypes.STRING(4096), - allowNull: false, - }, + allowNull: false + } }, { - tableName: "notes", + tableName: 'notes' } ); // Here we associate which actually populates out pre-declared `association` static and other methods. User.hasMany(Project, { - sourceKey: "id", - foreignKey: "ownerId", - as: "projects", // this determines the name in `associations`! + sourceKey: 'id', + foreignKey: 'ownerId', + as: 'projects' // this determines the name in `associations`! }); -Address.belongsTo(User, { targetKey: "id" }); -User.hasOne(Address, { sourceKey: "id" }); +Address.belongsTo(User, { targetKey: 'id' }); +User.hasOne(Address, { sourceKey: 'id' }); async function doStuffWithUser() { const newUser = await User.create({ - name: "Johnny", - preferredName: "John", + name: 'Johnny', + preferredName: 'John', }); console.log(newUser.id, newUser.name, newUser.preferredName); const project = await newUser.createProject({ - name: "first!", - ownerId: 123, + name: 'first!' }); const ourUser = await User.findByPk(1, { include: [User.associations.projects], - rejectOnEmpty: true, // Specifying true here removes `null` from the return type! + rejectOnEmpty: true // Specifying true here removes `null` from the return type! }); // Note the `!` null assertion since TS can't know if we included @@ -250,8 +310,7 @@ async function doStuffWithUser() { The typings for Sequelize v5 allowed you to define models without specifying types for the attributes. This is still possible for backwards compatibility and for cases where you feel strict typing for attributes isn't worth it. -**NOTE:** Keep the following code in sync with `typescriptDocs/ModelInitNoAttributes.ts` to ensure -it typechecks correctly. +[//]: # (NOTE for maintainers: Keep the following code in sync with `typescriptDocs/ModelInitNoAttributes.ts` to ensure it typechecks correctly.) ```ts import { Sequelize, Model, DataTypes } from "sequelize"; @@ -259,9 +318,9 @@ import { Sequelize, Model, DataTypes } from "sequelize"; const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); class User extends Model { - public id!: number; // Note that the `null assertion` `!` is required in strict mode. - public name!: string; - public preferredName!: string | null; // for nullable fields + declare id: number; + declare name: string; + declare preferredName: string | null; } User.init( @@ -303,8 +362,7 @@ async function doStuffWithUserModel() { 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. -**NOTE:** Keep the following code in sync with `typescriptDocs/Define.ts` to ensure -it typechecks correctly. +[//]: # (NOTE for maintainers: Keep the following code in sync with `typescriptDocs/Define.ts` to ensure it typechecks correctly.) ```ts import { Sequelize, Model, DataTypes, Optional } from "sequelize"; @@ -345,8 +403,7 @@ async function doStuff() { 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. -**NOTE:** Keep the following code in sync with `typescriptDocs/DefineNoAttributes.ts` to ensure -it typechecks correctly. +[//]: # (NOTE for maintainers: Keep the following code in sync with `typescriptDocs/DefineNoAttributes.ts` to ensure it typechecks correctly.) ```ts import { Sequelize, Model, DataTypes } from "sequelize"; @@ -376,3 +433,97 @@ async function doStuff() { console.log(instance.id); } ``` + +## Utility Types + +### Requesting a Model Class + +`ModelStatic` is designed to be used to type a Model *class*. + +Here is an example of a utility method that requests a Model Class, and returns the list of primary keys defined in that class: + +```typescript +import { ModelStatic, ModelAttributeColumnOptions, Model, InferAttributes, InferCreationAttributes, CreationOptional } from 'sequelize'; + +/** + * Returns the list of attributes that are part of the model's primary key. + */ +export function getPrimaryKeyAttributes(model: ModelStatic): ModelAttributeColumnOptions[] { + const attributes: ModelAttributeColumnOptions[] = []; + + for (const attribute of Object.values(model.rawAttributes)) { + if (attribute.primaryKey) { + attributes.push(attribute); + } + } + + return attributes; +} + +class User extends Model, InferCreationAttributes> { + id: CreationOptional; +} + +User.init({ + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true + }, +}, { sequelize }); + +const primaryAttributes = getPrimaryKeyAttributes(User); +``` + +### Getting a Model's attributes + +If you need to access the list of attributes of a given model, `Attributes` and `CreationAttributes` +are what you need to use. + +They will return the Attributes (and Creation Attributes) of the Model passed as a parameter. + +Don't confuse them with `InferAttributes` and `InferCreationAttributes`. These two utility types should only every be used +in the definition of a Model to automatically create the list of attributes from the model's public class fields. They only work +with class-based model definitions (When using `Model.init`). + +`Attributes` and `CreationAttributes` will return the list of attributes of any model, no matter how they were created (be it `Model.init` or `Sequelize#define`). + +Here is an example of a utility function that requests a Model Class, and the name of an attribute ; and returns the corresponding attribute metadata. + +```typescript +import { + ModelStatic, + ModelAttributeColumnOptions, + Model, + InferAttributes, + InferCreationAttributes, + CreationOptional, + Attributes +} from 'sequelize'; + +export function getAttributeMetadata(model: ModelStatic, attributeName: keyof Attributes): ModelAttributeColumnOptions { + const attribute = model.rawAttributes[attributeName]; + if (attribute == null) { + throw new Error(`Attribute ${attributeName} does not exist on model ${model.name}`); + } + + return attribute; +} + +class User extends Model, InferCreationAttributes> { + id: CreationOptional; +} + +User.init({ + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true + }, +}, { sequelize }); + +const idAttributeMeta = getAttributeMetadata(User, 'id'); // works! + +// @ts-expect-error +const nameAttributeMeta = getAttributeMetadata(User, 'name'); // fails because 'name' is not an attribute of User +``` diff --git a/src/associations/belongs-to-many.d.ts b/src/associations/belongs-to-many.d.ts index c63e97ac6bce..af3e3075cc86 100644 --- a/src/associations/belongs-to-many.d.ts +++ b/src/associations/belongs-to-many.d.ts @@ -8,7 +8,8 @@ import { InstanceDestroyOptions, InstanceUpdateOptions, Model, - ModelStatic, + ModelCtor, + ModelType, Transactionable, } from '../model'; import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, MultiAssociationAccessors } from './base'; @@ -21,7 +22,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: ModelStatic | string; + model: ModelType | string; /** * If true the generated join table will be paranoid @@ -52,14 +53,14 @@ export interface JoinTableAttributes { } /** - * Options provided when associating models with {@link Model.belongsToMany} relationship + * Options provided when associating models with belongsToMany relationship */ 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: ModelStatic | string | ThroughOptions; + through: ModelType | string | ThroughOptions; /** * The name of the foreign key in the join table (representing the target model) or an object representing @@ -97,7 +98,7 @@ export class BelongsToMany ext public sourceKey: string; public targetKey: string; public accessors: MultiAssociationAccessors; - constructor(source: ModelStatic, target: ModelStatic, options: BelongsToManyOptions); + constructor(source: ModelCtor, target: ModelCtor, options: BelongsToManyOptions); } /** @@ -116,7 +117,7 @@ export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions = ( @@ -156,7 +158,7 @@ export interface BelongsToManySetAssociationsMixinOptions } /** - * The setAssociations mixin applied to models with {@link Model.belongsToMany}. + * The setAssociations mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -177,6 +179,7 @@ export interface BelongsToManySetAssociationsMixinOptions * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManySetAssociationsMixin = ( @@ -197,7 +200,7 @@ export interface BelongsToManyAddAssociationsMixinOptions } /** - * The addAssociations mixin applied to models with {@link Model.belongsToMany}. + * The addAssociations mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -218,6 +221,7 @@ export interface BelongsToManyAddAssociationsMixinOptions * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyAddAssociationsMixin = ( @@ -238,7 +242,7 @@ export interface BelongsToManyAddAssociationMixinOptions } /** - * The addAssociation mixin applied to models with {@link Model.belongsToMany}. + * The addAssociation mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -259,6 +263,7 @@ export interface BelongsToManyAddAssociationMixinOptions * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyAddAssociationMixin = ( @@ -274,7 +279,7 @@ export interface BelongsToManyCreateAssociationMixinOptions extends CreateOption through?: JoinTableAttributes; } /** - * The createAssociation mixin applied to models with {@link Model.belongsToMany}. + * The createAssociation mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -295,6 +300,7 @@ export interface BelongsToManyCreateAssociationMixinOptions extends CreateOption * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyCreateAssociationMixin = ( @@ -309,7 +315,7 @@ export type BelongsToManyCreateAssociationMixin = ( export interface BelongsToManyRemoveAssociationMixinOptions extends InstanceDestroyOptions {} /** - * The removeAssociation mixin applied to models with {@link Model.belongsToMany}. + * The removeAssociation mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -330,6 +336,7 @@ export interface BelongsToManyRemoveAssociationMixinOptions extends InstanceDest * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyRemoveAssociationMixin = ( @@ -344,7 +351,7 @@ export type BelongsToManyRemoveAssociationMixin = ( export interface BelongsToManyRemoveAssociationsMixinOptions extends InstanceDestroyOptions, InstanceDestroyOptions {} /** - * The removeAssociations mixin applied to models with {@link Model.belongsToMany}. + * The removeAssociations mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -365,6 +372,7 @@ export interface BelongsToManyRemoveAssociationsMixinOptions extends InstanceDes * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyRemoveAssociationsMixin = ( @@ -379,7 +387,7 @@ export type BelongsToManyRemoveAssociationsMixin = ( export interface BelongsToManyHasAssociationMixinOptions extends BelongsToManyGetAssociationsMixinOptions {} /** - * The hasAssociation mixin applied to models with {@link Model.belongsToMany}. + * The hasAssociation mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -400,6 +408,7 @@ export interface BelongsToManyHasAssociationMixinOptions extends BelongsToManyGe * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyHasAssociationMixin = ( @@ -414,7 +423,7 @@ export type BelongsToManyHasAssociationMixin = ( export interface BelongsToManyHasAssociationsMixinOptions extends BelongsToManyGetAssociationsMixinOptions {} /** - * The removeAssociations mixin applied to models with {@link Model.belongsToMany}. + * The removeAssociations mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -435,6 +444,7 @@ export interface BelongsToManyHasAssociationsMixinOptions extends BelongsToManyG * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyHasAssociationsMixin = ( @@ -454,7 +464,7 @@ export interface BelongsToManyCountAssociationsMixinOptions extends Transactiona } /** - * The countAssociations mixin applied to models with {@link Model.belongsToMany}. + * The countAssociations mixin applied to models with belongsToMany. * An example of usage is as follows: * * ```js @@ -475,6 +485,7 @@ export interface BelongsToManyCountAssociationsMixinOptions extends Transactiona * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ export type BelongsToManyCountAssociationsMixin = ( diff --git a/src/associations/belongs-to.d.ts b/src/associations/belongs-to.d.ts index 31abaeb37967..19d50859290e 100644 --- a/src/associations/belongs-to.d.ts +++ b/src/associations/belongs-to.d.ts @@ -1,10 +1,10 @@ import { DataType } from '../data-types'; -import { CreateOptions, CreationAttributes, FindOptions, Model, ModelStatic, SaveOptions } from '../model'; +import { CreateOptions, CreationAttributes, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; -// type ModelStatic = InstanceType; +// type ModelCtor = InstanceType; /** - * Options provided when associating models with {@link Model.belongsTo} relationship + * Options provided when associating models with belongsTo relationship * * @see Association class belongsTo method */ @@ -23,7 +23,7 @@ export interface BelongsToOptions extends AssociationOptions { export class BelongsTo extends Association { public accessors: SingleAssociationAccessors; - constructor(source: ModelStatic, target: ModelStatic, options: BelongsToOptions); + constructor(source: ModelCtor, target: ModelCtor, options: BelongsToOptions); } /** @@ -38,7 +38,7 @@ export interface BelongsToGetAssociationMixinOptions extends FindOptions { } /** - * The getAssociation mixin applied to models with {@link Model.belongsTo}. + * The getAssociation mixin applied to models with belongsTo. * An example of usage is as follows: * * ```js @@ -52,6 +52,7 @@ export interface BelongsToGetAssociationMixinOptions extends FindOptions { * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html * @see Instance */ export type BelongsToGetAssociationMixin = (options?: BelongsToGetAssociationMixinOptions) => Promise; @@ -68,7 +69,7 @@ export interface BelongsToSetAssociationMixinOptions extends SaveOptions { } /** - * The setAssociation mixin applied to models with {@link Model.belongsTo}. + * The setAssociation mixin applied to models with belongsTo. * An example of usage is as follows: * * ```js @@ -82,6 +83,7 @@ export interface BelongsToSetAssociationMixinOptions extends SaveOptions { * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html * @see Instance */ export type BelongsToSetAssociationMixin = ( @@ -97,7 +99,7 @@ export interface BelongsToCreateAssociationMixinOptions extends CreateOptions, BelongsToSetAssociationMixinOptions {} /** - * The createAssociation mixin applied to models with {@link Model.belongsTo}. + * The createAssociation mixin applied to models with belongsTo. * An example of usage is as follows: * * ```js @@ -111,9 +113,12 @@ export interface BelongsToCreateAssociationMixinOptions * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html * @see Instance */ export type BelongsToCreateAssociationMixin = ( values?: CreationAttributes, options?: BelongsToCreateAssociationMixinOptions ) => Promise; + +export default BelongsTo; diff --git a/src/associations/has-many.d.ts b/src/associations/has-many.d.ts index a2c34e3c7350..ee83127b845a 100644 --- a/src/associations/has-many.d.ts +++ b/src/associations/has-many.d.ts @@ -6,13 +6,13 @@ import { FindOptions, InstanceUpdateOptions, Model, - ModelStatic, + ModelCtor, Transactionable, } from '../model'; import { Association, ManyToManyOptions, MultiAssociationAccessors } from './base'; /** - * Options provided when associating models with {@link Model.hasMany} relationship + * Options provided when associating models with hasMany relationship */ export interface HasManyOptions extends ManyToManyOptions { @@ -30,7 +30,7 @@ export interface HasManyOptions extends ManyToManyOptions { export class HasMany extends Association { public accessors: MultiAssociationAccessors; - constructor(source: ModelStatic, target: ModelStatic, options: HasManyOptions); + constructor(source: ModelCtor, target: ModelCtor, options: HasManyOptions); } /** @@ -45,7 +45,7 @@ export interface HasManyGetAssociationsMixinOptions extends FindOptions { } /** - * The getAssociations mixin applied to models with {@link Model.hasMany}. + * The getAssociations mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -66,6 +66,7 @@ export interface HasManyGetAssociationsMixinOptions extends FindOptions { * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyGetAssociationsMixin = (options?: HasManyGetAssociationsMixinOptions) => Promise; @@ -77,7 +78,7 @@ export type HasManyGetAssociationsMixin = (options?: HasManyGetAssociati export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {} /** - * The setAssociations mixin applied to models with {@link Model.hasMany}. + * The setAssociations mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -98,6 +99,7 @@ export interface HasManySetAssociationsMixinOptions extends FindOptions, In * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManySetAssociationsMixin = ( @@ -112,7 +114,7 @@ export type HasManySetAssociationsMixin = ( export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {} /** - * The addAssociations mixin applied to models with {@link Model.hasMany}. + * The addAssociations mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -133,6 +135,7 @@ export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOption * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyAddAssociationsMixin = ( @@ -147,7 +150,7 @@ export type HasManyAddAssociationsMixin = ( export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {} /** - * The addAssociation mixin applied to models with {@link Model.hasMany}. + * The addAssociation mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -168,6 +171,7 @@ export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyAddAssociationMixin = ( @@ -182,7 +186,7 @@ export type HasManyAddAssociationMixin = ( export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} /** - * The createAssociation mixin applied to models with {@link Model.hasMany}. + * The createAssociation mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -203,6 +207,7 @@ export interface HasManyCreateAssociationMixinOptions extends CreateOptions * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyCreateAssociationMixin< @@ -221,7 +226,7 @@ export type HasManyCreateAssociationMixin< export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {} /** - * The removeAssociation mixin applied to models with {@link Model.hasMany}. + * The removeAssociation mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -242,6 +247,7 @@ export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOpti * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyRemoveAssociationMixin = ( @@ -256,7 +262,7 @@ export type HasManyRemoveAssociationMixin = ( export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {} /** - * The removeAssociations mixin applied to models with {@link Model.hasMany}. + * The removeAssociations mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -277,6 +283,7 @@ export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOpt * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyRemoveAssociationsMixin = ( @@ -291,7 +298,7 @@ export type HasManyRemoveAssociationsMixin = ( export interface HasManyHasAssociationMixinOptions extends HasManyGetAssociationsMixinOptions {} /** - * The hasAssociation mixin applied to models with {@link Model.hasMany}. + * The hasAssociation mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -312,6 +319,7 @@ export interface HasManyHasAssociationMixinOptions extends HasManyGetAssociation * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyHasAssociationMixin = ( @@ -347,8 +355,8 @@ export interface HasManyHasAssociationsMixinOptions extends HasManyGetAssociatio * } * ``` * - * This method returns true if all provided targets are associated to the source model, and false if - * any of the provided targets are not. + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html + * @see Instance */ export type HasManyHasAssociationsMixin = ( targets: (TModel | TModelPrimaryKey)[], @@ -367,7 +375,7 @@ export interface HasManyCountAssociationsMixinOptions extends Transactionable, F } /** - * The countAssociations mixin applied to models with {@link Model.hasMany}. + * The countAssociations mixin applied to models with hasMany. * An example of usage is as follows: * * ```js @@ -388,6 +396,7 @@ export interface HasManyCountAssociationsMixinOptions extends Transactionable, F * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ export type HasManyCountAssociationsMixin = (options?: HasManyCountAssociationsMixinOptions) => Promise; diff --git a/src/associations/has-one.d.ts b/src/associations/has-one.d.ts index 048e6fe99271..692b9e1efcbf 100644 --- a/src/associations/has-one.d.ts +++ b/src/associations/has-one.d.ts @@ -1,9 +1,9 @@ import { DataType } from '../data-types'; -import { CreateOptions, CreationAttributes, FindOptions, Model, ModelStatic, SaveOptions } from '../model'; +import { CreateOptions, CreationAttributes, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; /** - * Options provided when associating models with {@link Model.hasOne} relationship + * Options provided when associating models with hasOne relationship */ export interface HasOneOptions extends AssociationOptions { @@ -21,7 +21,7 @@ export interface HasOneOptions extends AssociationOptions { export class HasOne extends Association { public accessors: SingleAssociationAccessors; - constructor(source: ModelStatic, target: ModelStatic, options: HasOneOptions); + constructor(source: ModelCtor, target: ModelCtor, options: HasOneOptions); } /** @@ -36,7 +36,7 @@ export interface HasOneGetAssociationMixinOptions extends FindOptions { } /** - * The getAssociation mixin applied to models with {@link Model.hasOne}. + * The getAssociation mixin applied to models with hasOne. * An example of usage is as follows: * * ```js @@ -50,6 +50,7 @@ export interface HasOneGetAssociationMixinOptions extends FindOptions { * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html * @see Instance */ export type HasOneGetAssociationMixin = (options?: HasOneGetAssociationMixinOptions) => Promise; @@ -66,7 +67,7 @@ export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMi } /** - * The setAssociation mixin applied to models with {@link Model.hasOne}. + * The setAssociation mixin applied to models with hasOne. * An example of usage is as follows: * * ```js @@ -80,6 +81,7 @@ export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMi * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html * @see Instance */ export type HasOneSetAssociationMixin = ( @@ -94,7 +96,7 @@ export type HasOneSetAssociationMixin = ( export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {} /** - * The createAssociation mixin applied to models with {@link Model.hasOne}. + * The createAssociation mixin applied to models with hasOne. * An example of usage is as follows: * * ```js @@ -108,6 +110,7 @@ export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociatio * } * ``` * + * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html * @see Instance */ export type HasOneCreateAssociationMixin = ( diff --git a/src/dialects/abstract/query-interface.d.ts b/src/dialects/abstract/query-interface.d.ts index 9d87047c1ab4..e35ec5b8cf82 100644 --- a/src/dialects/abstract/query-interface.d.ts +++ b/src/dialects/abstract/query-interface.d.ts @@ -1,66 +1,126 @@ -import { DataType } from '../../data-types'; +import { DataType } from './data-types'; import { Logging, Model, ModelAttributeColumnOptions, ModelAttributes, + Transactionable, WhereOptions, Filterable, + Poolable, + ModelCtor, ModelStatic, + ModelType, CreationAttributes, Attributes, - BuiltModelAttributeColumOptions, -} from '../../model'; -import { Sequelize, QueryRawOptions, QueryRawOptionsWithModel } from '../../sequelize'; -import { Transaction } from '../../transaction'; -import { SetRequired } from '../../utils/set-required'; -import { Fn, Literal } from '../../utils'; -import { Deferrable } from '../../deferrable'; -import { AbstractQueryGenerator } from './query-generator.js'; +} from './model'; +import QueryTypes = require('./query-types'); +import { Sequelize, RetryOptions } from './sequelize'; +import { Transaction } from './transaction'; +import { SetRequired } from './../type-helpers/set-required'; +import { Fn, Literal } from './utils'; +import { Deferrable } from './deferrable'; -interface Replaceable { +type BindOrReplacements = { [key: string]: unknown } | unknown[]; +type FieldMap = { [key: string]: string }; + +/** + * Interface for query options + */ +export interface QueryOptions extends Logging, Transactionable, Poolable { /** - * Only named replacements are allowed in query interface methods. + * If true, sequelize will not try to format the results of the query, or build an instance of a model from + * the result */ - replacements?: { [key: string]: unknown }, -} + raw?: boolean; -interface QiOptionsWithReplacements extends QueryRawOptions, Replaceable {} + /** + * The type of query you are executing. The query type affects how results are formatted before they are + * passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts. + */ + type?: string; -export interface QiInsertOptions extends QueryRawOptions, Replaceable { - returning?: boolean | string[], -} + /** + * If true, transforms objects with `.` separated property names into nested objects using + * [dottie.js](https://github.com/mickhansen/dottie.js). For example { 'user.username': 'john' } becomes + * { user: { username: 'john' }}. When `nest` is true, the query type is assumed to be `'SELECT'`, + * unless otherwise specified + * + * @default false + */ + nest?: boolean; -export interface QiSelectOptions extends QueryRawOptions, Replaceable, Filterable { + /** + * Sets the query type to `SELECT` and return a single row + */ + plain?: boolean; -} + /** + * Either an object of named parameter replacements in the format `:param` or an array of unnamed + * replacements to replace `?` in your SQL. + */ + replacements?: BindOrReplacements; -export interface QiUpdateOptions extends QueryRawOptions, Replaceable { - returning?: boolean | string[], -} + /** + * Either an object of named parameter bindings in the format `$param` or an array of unnamed + * values to bind to `$1`, `$2`, etc in your SQL. + */ + bind?: BindOrReplacements; -export interface QiDeleteOptions extends QueryRawOptions, Replaceable { - limit?: number | Literal | null | undefined; + /** + * A sequelize instance used to build the return instance + */ + instance?: Model; + + /** + * Map returned fields to model's fields if `options.model` or `options.instance` is present. + * Mapping will occur before building the model instance. + */ + mapToModel?: boolean; + + retry?: RetryOptions; + + /** + * Map returned fields to arbitrary names for SELECT query type if `options.fieldMaps` is present. + */ + fieldMap?: FieldMap; } -export interface QiArithmeticOptions extends QueryRawOptions, Replaceable { - returning?: boolean | string[], +export interface QueryOptionsWithWhere extends QueryOptions, Filterable { + } -export interface QiUpsertOptions extends QueryRawOptionsWithModel, Replaceable { +export interface QueryOptionsWithModel extends QueryOptions { + /** + * A sequelize model used to build the returned model instances (used to be called callee) + */ + model: ModelStatic; +} +export interface QueryOptionsWithType extends QueryOptions { + /** + * The type of query you are executing. The query type affects how results are formatted before they are + * passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts. + */ + type: T; } -export interface CreateFunctionOptions extends QueryRawOptions { +export interface QueryOptionsWithForce extends QueryOptions { force?: boolean; } +/** +* Most of the methods accept options and use only the logger property of the options. That's why the most used +* interface type for options in a method is separated here as another interface. +*/ +export interface QueryInterfaceOptions extends Logging, Transactionable {} + export interface CollateCharsetOptions { collate?: string; charset?: string; } -export interface QueryInterfaceCreateTableOptions extends QueryRawOptions, CollateCharsetOptions { +export interface QueryInterfaceCreateTableOptions extends QueryInterfaceOptions, CollateCharsetOptions { engine?: string; /** * Used for compound unique keys. @@ -73,12 +133,12 @@ export interface QueryInterfaceCreateTableOptions extends QueryRawOptions, Colla }; } -export interface QueryInterfaceDropTableOptions extends QueryRawOptions { +export interface QueryInterfaceDropTableOptions extends QueryInterfaceOptions { cascade?: boolean; force?: boolean; } -export interface QueryInterfaceDropAllTablesOptions extends QueryRawOptions { +export interface QueryInterfaceDropAllTablesOptions extends QueryInterfaceOptions { skip?: string[]; } @@ -94,34 +154,7 @@ export type TableName = string | TableNameWithSchema; export type IndexType = 'UNIQUE' | 'FULLTEXT' | 'SPATIAL'; export type IndexMethod = 'BTREE' | 'HASH' | 'GIST' | 'SPGIST' | 'GIN' | 'BRIN' | string; -export interface IndexField { - /** - * The name of the column - */ - name: string; - - /** - * Create a prefix index of length chars - */ - length?: number; - - /** - * The direction the column should be sorted in - */ - order?: 'ASC' | 'DESC'; - - /** - * The collation (sort order) for the column - */ - collate?: string; - - /** - * Index operator type. Postgres only - */ - operator?: string; -} - -export interface IndexOptions { +export interface IndexesOptions { /** * The name of the index. Defaults to model name + _ + fields concatenated */ @@ -143,21 +176,23 @@ export interface IndexOptions { unique?: boolean; /** - * PostgreSQL will build the index without taking any write locks. Postgres only. + * PostgreSQL will build the index without taking any write locks. Postgres only * * @default false */ concurrently?: boolean; /** - * The fields to index. + * An array of the fields to index. Each field can either be a string containing the name of the field, + * a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `name` + * (field name), `length` (create a prefix index of length chars), `order` (the direction the column + * should be sorted in), `collate` (the collation (sort order) for the column), `operator` (likes IndexesOptions['operator']) */ - fields?: Array; + fields?: (string | { name: string; length?: number; order?: 'ASC' | 'DESC'; collate?: string; operator?: string } | Fn | Literal)[]; /** - * The method to create the index by (`USING` statement in SQL). - * BTREE and HASH are supported by mysql and postgres. - * Postgres additionally supports GIST, SPGIST, BRIN and GIN. + * The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and + * postgres, and postgres additionally supports GIST, SPGIST, BRIN and GIN. */ using?: IndexMethod; @@ -177,7 +212,7 @@ export interface IndexOptions { prefix?: string; } -export interface QueryInterfaceIndexOptions extends IndexOptions, Omit {} +export interface QueryInterfaceIndexOptions extends IndexesOptions, QueryInterfaceOptions {} export interface BaseConstraintOptions { name?: string; @@ -222,7 +257,7 @@ export type AddConstraintOptions = | AddPrimaryKeyConstraintOptions | AddForeignKeyConstraintOptions; -export interface CreateDatabaseOptions extends CollateCharsetOptions, QueryRawOptions { +export interface CreateDatabaseOptions extends CollateCharsetOptions, QueryOptions { encoding?: string; } @@ -257,7 +292,7 @@ export class QueryInterface { * * We don't have a definition for the QueryGenerator, because I doubt it is commonly in use separately. */ - public queryGenerator: AbstractQueryGenerator; + public queryGenerator: unknown; /** * Returns the current sequelize instance. @@ -271,14 +306,14 @@ export class QueryInterface { * * @param schema The schema to query. Applies only to Postgres. */ - public createSchema(schema?: string, options?: QueryRawOptions): Promise; + public createSchema(schema?: string, options?: QueryInterfaceOptions): Promise; /** * Drops the specified schema (table). * * @param schema The schema to query. Applies only to Postgres. */ - public dropSchema(schema?: string, options?: QueryRawOptions): Promise; + public dropSchema(schema?: string, options?: QueryInterfaceOptions): Promise; /** * Drops all tables. @@ -290,12 +325,12 @@ export class QueryInterface { * * @param options */ - public showAllSchemas(options?: QueryRawOptions): Promise; + public showAllSchemas(options?: QueryOptions): Promise; /** * Return database version */ - public databaseVersion(options?: QueryRawOptions): Promise; + public databaseVersion(options?: QueryInterfaceOptions): Promise; /** * Creates a table with specified attributes. @@ -330,17 +365,17 @@ export class QueryInterface { * * @param options */ - public dropAllEnums(options?: QueryRawOptions): Promise; + public dropAllEnums(options?: QueryOptions): Promise; /** * Renames a table */ - public renameTable(before: TableName, after: TableName, options?: QueryRawOptions): Promise; + public renameTable(before: TableName, after: TableName, options?: QueryInterfaceOptions): Promise; /** * Returns all tables */ - public showAllTables(options?: QueryRawOptions): Promise; + public showAllTables(options?: QueryOptions): Promise; /** * Describe a table @@ -357,7 +392,7 @@ export class QueryInterface { table: TableName, key: string, attribute: ModelAttributeColumnOptions | DataType, - options?: QiOptionsWithReplacements + options?: QueryInterfaceOptions ): Promise; /** @@ -366,7 +401,7 @@ export class QueryInterface { public removeColumn( table: TableName, attribute: string, - options?: QiOptionsWithReplacements + options?: QueryInterfaceOptions ): Promise; /** @@ -376,7 +411,7 @@ export class QueryInterface { tableName: TableName, attributeName: string, dataTypeOrOptions?: DataType | ModelAttributeColumnOptions, - options?: QiOptionsWithReplacements + options?: QueryInterfaceOptions ): Promise; /** @@ -386,7 +421,7 @@ export class QueryInterface { tableName: TableName, attrNameBefore: string, attrNameAfter: string, - options?: QiOptionsWithReplacements + options?: QueryInterfaceOptions ): Promise; /** @@ -415,18 +450,18 @@ export class QueryInterface { */ public addConstraint( tableName: TableName, - options?: AddConstraintOptions & QueryRawOptions + options?: AddConstraintOptions & QueryInterfaceOptions ): Promise; /** * Removes constraints from a table */ - public removeConstraint(tableName: TableName, constraintName: string, options?: QueryRawOptions): Promise; + public removeConstraint(tableName: TableName, constraintName: string, options?: QueryInterfaceOptions): Promise; /** * Shows the index of a table */ - public showIndex(tableName: string | object, options?: QueryRawOptions): Promise; + public showIndex(tableName: string | object, options?: QueryOptions): Promise; /** * Put a name to an index @@ -436,27 +471,28 @@ export class QueryInterface { /** * Returns all foreign key constraints of requested tables */ - public getForeignKeysForTables(tableNames: string[], options?: QueryRawOptions): Promise; + public getForeignKeysForTables(tableNames: string[], options?: QueryInterfaceOptions): Promise; /** * Get foreign key references details for the table */ - public getForeignKeyReferencesForTable(tableName: TableName, options?: QueryRawOptions): Promise; + public getForeignKeyReferencesForTable(tableName: TableName, options?: QueryInterfaceOptions): Promise; /** * Inserts a new record */ - public insert(instance: Model | null, tableName: string, values: object, options?: QiInsertOptions): Promise; + public insert(instance: Model | null, tableName: string, values: object, options?: QueryOptions): Promise; /** * Inserts or Updates a record in the database */ - public upsert( + public upsert( tableName: TableName, insertValues: object, updateValues: object, where: object, - options?: QiUpsertOptions, + model: ModelType, + options?: QueryOptions ): Promise; /** @@ -465,7 +501,7 @@ export class QueryInterface { public bulkInsert( tableName: TableName, records: object[], - options?: QiOptionsWithReplacements, + options?: QueryOptions, attributes?: Record ): Promise; @@ -476,8 +512,8 @@ export class QueryInterface { instance: M, tableName: TableName, values: object, - where: WhereOptions>, - options?: QiUpdateOptions + identifier: WhereOptions>, + options?: QueryOptions ): Promise; /** @@ -486,9 +522,9 @@ export class QueryInterface { public bulkUpdate( tableName: TableName, values: object, - where: WhereOptions, - options?: QiOptionsWithReplacements, - columnDefinitions?: { [columnName: string]: BuiltModelAttributeColumOptions }, + identifier: WhereOptions, + options?: QueryOptions, + attributes?: string[] | string ): Promise; /** @@ -498,7 +534,7 @@ export class QueryInterface { instance: Model | null, tableName: TableName, identifier: WhereOptions, - options?: QiDeleteOptions, + options?: QueryOptions ): Promise; /** @@ -507,37 +543,24 @@ export class QueryInterface { public bulkDelete( tableName: TableName, identifier: WhereOptions, - options?: QiOptionsWithReplacements, - model?: ModelStatic + options?: QueryOptions, + model?: ModelType ): Promise; /** * Returns selected rows */ - public select(model: ModelStatic | null, tableName: TableName, options?: QiSelectOptions): Promise; + public select(model: ModelType | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise; /** * Increments a row value */ public increment( - model: ModelStatic, + instance: Model, tableName: TableName, - incrementAmountsByField: object, - extraAttributesToBeUpdated?: object, - where?: WhereOptions>, - options?: QiArithmeticOptions, - ): Promise; - - /** - * Decrements a row value - */ - public decrement( - model: ModelStatic, - tableName: TableName, - incrementAmountsByField: object, - extraAttributesToBeUpdated?: object, - where?: WhereOptions>, - options?: QiArithmeticOptions, + values: object, + identifier: WhereOptions>, + options?: QueryOptions ): Promise; /** @@ -545,9 +568,9 @@ export class QueryInterface { */ public rawSelect( tableName: TableName, - options: QiSelectOptions, - attributeSelector: string, - model?: ModelStatic + options: QueryOptionsWithWhere, + attributeSelector: string | string[], + model?: ModelType ): Promise; /** @@ -564,13 +587,13 @@ export class QueryInterface { functionName: string, functionParams: FunctionParam[], optionsArray: string[], - options?: QiOptionsWithReplacements + options?: QueryInterfaceOptions ): Promise; /** * Postgres only. Drops the specified trigger. */ - public dropTrigger(tableName: TableName, triggerName: string, options?: QiOptionsWithReplacements): Promise; + public dropTrigger(tableName: TableName, triggerName: string, options?: QueryInterfaceOptions): Promise; /** * Postgres only. Renames a trigger @@ -579,7 +602,7 @@ export class QueryInterface { tableName: TableName, oldTriggerName: string, newTriggerName: string, - options?: QiOptionsWithReplacements + options?: QueryInterfaceOptions ): Promise; /** @@ -592,13 +615,13 @@ export class QueryInterface { language: string, body: string, optionsArray?: string[], - options?: CreateFunctionOptions + options?: QueryOptionsWithForce ): Promise; /** * Postgres only. Drops a function */ - public dropFunction(functionName: string, params: FunctionParam[], options?: QiOptionsWithReplacements): Promise; + public dropFunction(functionName: string, params: FunctionParam[], options?: QueryInterfaceOptions): Promise; /** * Postgres only. Rename a function @@ -607,9 +630,14 @@ export class QueryInterface { oldFunctionName: string, params: FunctionParam[], newFunctionName: string, - options?: QiOptionsWithReplacements + options?: QueryInterfaceOptions ): Promise; + /** + * Escape a table name + */ + public quoteTable(identifier: TableName): string; + /** * Escape an identifier (e.g. a table or attribute name). If force is true, the identifier will be quoted * even if the `quoteIdentifiers` option is false. @@ -621,35 +649,40 @@ export class QueryInterface { */ public quoteIdentifiers(identifiers: string): string; + /** + * Escape a value (e.g. a string, number or date) + */ + public escape(value?: string | number | Date): string; + /** * Set option for autocommit of a transaction */ - public setAutocommit(transaction: Transaction, value: boolean, options?: QueryRawOptions): Promise; + public setAutocommit(transaction: Transaction, value: boolean, options?: QueryOptions): Promise; /** * Set the isolation level of a transaction */ - public setIsolationLevel(transaction: Transaction, value: string, options?: QueryRawOptions): Promise; + public setIsolationLevel(transaction: Transaction, value: string, options?: QueryOptions): Promise; /** * Begin a new transaction */ - public startTransaction(transaction: Transaction, options?: QueryRawOptions): Promise; + public startTransaction(transaction: Transaction, options?: QueryOptions): Promise; /** * Defer constraints */ - public deferConstraints(transaction: Transaction, options?: QueryRawOptions): Promise; + public deferConstraints(transaction: Transaction, options?: QueryOptions): Promise; /** * Commit an already started transaction */ - public commitTransaction(transaction: Transaction, options?: QueryRawOptions): Promise; + public commitTransaction(transaction: Transaction, options?: QueryOptions): Promise; /** * Rollback ( revert ) a transaction that has'nt been commited */ - public rollbackTransaction(transaction: Transaction, options?: QueryRawOptions): Promise; + public rollbackTransaction(transaction: Transaction, options?: QueryOptions): Promise; /** * Creates a database @@ -659,5 +692,5 @@ export class QueryInterface { /** * Creates a database */ - public dropDatabase(name: string, options?: QueryRawOptions): Promise; + public dropDatabase(name: string, options?: QueryOptions): Promise; } diff --git a/src/hooks.d.ts b/src/hooks.d.ts index 5b2120222fd2..b9620f172a64 100644 --- a/src/hooks.d.ts +++ b/src/hooks.d.ts @@ -1,6 +1,6 @@ +import { Attributes, CreationAttributes, ModelType } from '../index'; import { ValidationOptions } from './instance-validator'; -import { - Model, +import Model, { BulkCreateOptions, CountOptions, CreateOptions, @@ -9,11 +9,11 @@ import { InstanceRestoreOptions, InstanceUpdateOptions, ModelAttributes, - ModelOptions, RestoreOptions, UpdateOptions, UpsertOptions, - Attributes, CreationAttributes, ModelStatic + ModelOptions, RestoreOptions, UpdateOptions, UpsertOptions } from './model'; -import { AbstractQuery } from './dialects/abstract/query'; -import { Config, Options, Sequelize, SyncOptions, QueryOptions } from './sequelize'; +import { AbstractQuery } from './query'; +import { QueryOptions } from './query-interface'; +import { Config, Options, Sequelize, SyncOptions } from './sequelize'; import { DeepWriteable } from './utils'; export type HookReturn = Promise | void; @@ -34,7 +34,7 @@ export interface ModelHooks { beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; beforeUpsert(attributes: M, options: UpsertOptions): HookReturn; - afterUpsert(attributes: [ M, boolean | null ], options: UpsertOptions): HookReturn; + afterUpsert(attributes: [ M, boolean | null ], options: UpsertOptions): HookReturn; beforeSave( instance: M, options: InstanceUpdateOptions | CreateOptions @@ -71,7 +71,7 @@ export interface SequelizeHooks< TCreationAttributes = TAttributes > extends ModelHooks { beforeDefine(attributes: ModelAttributes, options: ModelOptions): void; - afterDefine(model: ModelStatic): void; + afterDefine(model: ModelType): void; beforeInit(config: Config, options: Options): void; afterInit(sequelize: Sequelize): void; beforeConnect(config: DeepWriteable): HookReturn; diff --git a/src/sequelize.d.ts b/src/sequelize.d.ts index 0924295085c4..12e98b40adb4 100644 --- a/src/sequelize.d.ts +++ b/src/sequelize.d.ts @@ -1,6 +1,8 @@ +import * as DataTypes from './data-types'; import { HookReturn, Hooks, SequelizeHooks } from './hooks'; import { ValidationOptions } from './instance-validator'; import { + AndOperator, BulkCreateOptions, CreateOptions, DestroyOptions, @@ -12,23 +14,22 @@ import { ModelAttributeColumnOptions, ModelAttributes, ModelOptions, + OrOperator, UpdateOptions, + WhereAttributeHash, WhereOperators, + ModelCtor, Hookable, - ModelStatic, + ModelType, CreationAttributes, Attributes, - ColumnReference, - Transactionable, - Poolable, - WhereAttributeHashValue, } from './model'; import { ModelManager } from './model-manager'; -import { QueryTypes, Transaction, TransactionOptions, TRANSACTION_TYPES, ISOLATION_LEVELS, PartlyRequired, Op, DataTypes } from '.'; +import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType, ColumnsDescription } from './query-interface'; +import QueryTypes = require('./query-types'); +import { Transaction, TransactionOptions } from './transaction'; import { Cast, Col, DeepWriteable, Fn, Json, Literal, Where } from './utils'; -import type { AbstractDialect } from './dialects/abstract'; -import { QueryInterface, ColumnsDescription } from './dialects/abstract/query-interface'; -import { ConnectionManager } from './dialects/abstract/connection-manager'; +import { ConnectionManager } from './connection-manager'; /** * Additional options for table altering during sync @@ -70,14 +71,13 @@ export interface SyncOptions extends Logging, Hookable { * An optional parameter to specify the schema search_path (Postgres only) */ searchPath?: string; + } export interface DefaultSetOptions { } /** - * Connection Pool options. - * - * Used in {@link Options.pool} + * Connection Pool options */ export interface PoolOptions { /** @@ -160,7 +160,7 @@ export interface Config { readonly protocol: 'tcp'; readonly native: boolean; readonly ssl: boolean; - readonly replication: ReplicationOptions | false; + readonly replication: boolean; readonly dialectModulePath: null | string; readonly keepDefaultTimezone?: boolean; readonly dialectOptions?: { @@ -169,7 +169,7 @@ export interface Config { }; } -export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql' | 'db2' | 'snowflake' | 'ibmi'; +export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql'; export interface RetryOptions { match?: (RegExp | string | Function)[]; @@ -177,7 +177,7 @@ export interface RetryOptions { } /** - * Options for the constructor of the {@link Sequelize} main class. + * Options for the constructor of Sequelize main class */ export interface Options extends Logging { /** @@ -239,7 +239,7 @@ export interface Options extends Logging { /** * The port of the relational database. */ - port?: number | string; + port?: number; /** * A flag that defines if is used SSL. @@ -307,7 +307,7 @@ export interface Options extends Logging { * * @default false */ - replication?: ReplicationOptions | false; + replication?: ReplicationOptions; /** * Connection pool options @@ -328,14 +328,14 @@ export interface Options extends Logging { * * @default 'REPEATABLE_READ' */ - isolationLevel?: ISOLATION_LEVELS; + isolationLevel?: string; /** * Set the default transaction type. See Sequelize.Transaction.TYPES for possible options. Sqlite only. * * @default 'DEFERRED' */ - transactionType?: TRANSACTION_TYPES; + transactionType?: Transaction.TYPES; /** * Run built in type validators on insert and update, e.g. validate that arguments passed to integer @@ -347,7 +347,7 @@ export interface Options extends Logging { /** * Sets available operator aliases. - * See (https://sequelize.org/docs/v7/core-concepts/model-querying-basics/#operators) for more information. + * See (https://sequelize.org/master/manual/querying.html#operators) for more information. * WARNING: Setting this to boolean value was deprecated and is no-op. * * @default all aliases @@ -404,112 +404,12 @@ export interface Options extends Logging { export interface QueryOptionsTransactionRequired { } -type BindOrReplacements = { [key: string]: unknown } | unknown[]; -type FieldMap = { [key: string]: string }; - -/** - * Options for {@link Sequelize#queryRaw}. - */ -export interface QueryRawOptions extends Logging, Transactionable, Poolable { - /** - * If true, sequelize will not try to format the results of the query, or build an instance of a model from - * the result - */ - raw?: boolean; - - /** - * The type of query you are executing. The query type affects how results are formatted before they are - * passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts. - */ - type?: string; - - /** - * If true, transforms objects with `.` separated property names into nested objects using - * [dottie.js](https://github.com/mickhansen/dottie.js). For example { 'user.username': 'john' } becomes - * { user: { username: 'john' }}. When `nest` is true, the query type is assumed to be `'SELECT'`, - * unless otherwise specified - * - * @default false - */ - nest?: boolean; - - /** - * Sets the query type to `SELECT` and return a single row - */ - plain?: boolean; - - /** - * Either an object of named parameter bindings in the format `$param` or an array of unnamed - * values to bind to `$1`, `$2`, etc in your SQL. - */ - bind?: BindOrReplacements; - - /** - * A sequelize instance used to build the return instance - */ - instance?: Model; - - /** - * Map returned fields to model's fields if `options.model` or `options.instance` is present. - * Mapping will occur before building the model instance. - */ - mapToModel?: boolean; - - retry?: RetryOptions; - - /** - * Map returned fields to arbitrary names for SELECT query type if `options.fieldMaps` is present. - */ - fieldMap?: FieldMap; -} - -export interface QueryRawOptionsWithType extends QueryRawOptions { - /** - * The type of query you are executing. The query type affects how results are formatted before they are - * passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts. - */ - type: T; -} - -export interface QueryRawOptionsWithModel extends QueryRawOptions { - /** - * A sequelize model used to build the returned model instances (used to be called callee) - */ - model: ModelStatic; -} - -/** - * Options for {@link Sequelize#query}. - */ -export interface QueryOptions extends QueryRawOptions { - /** - * Either an object of named parameter replacements in the format `:param` or an array of unnamed - * replacements to replace `?` in your SQL. - */ - replacements?: BindOrReplacements; -} - -export interface QueryOptionsWithType extends QueryOptions { - /** - * The type of query you are executing. The query type affects how results are formatted before they are - * passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts. - */ - type: T; -} - -export interface QueryOptionsWithModel extends QueryOptions { - /** - * A sequelize model used to build the returned model instances (used to be called callee) - */ - model: ModelStatic; -} - /** * This is the main class, the entry point to sequelize. To use it, you just need to * import sequelize: * * ```js - * const { Sequelize } = require('@sequelize/core'); + * const Sequelize = require('sequelize'); * ``` * * In addition to sequelize, the connection library for the dialect you want to use @@ -532,7 +432,6 @@ export class Sequelize extends Hooks { * username: self.sequelize.fn('upper', self.sequelize.col('username')) * }) * ``` - * * @param fn The function you want to call * @param args All further arguments will be passed as arguments to the function */ @@ -586,8 +485,8 @@ export class Sequelize extends Hooks { * * @param conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot * notation or a string using postgres json syntax. - * @param value An optional value to compare against. - * Produces a string of the form "<json path> = '<value>'"`. + * @param value An optional value to compare against. Produces a string of the form " = + * ''". */ public static json: typeof json; public json: typeof json; @@ -613,9 +512,6 @@ export class Sequelize extends Hooks { public static where: typeof where; public where: typeof where; - public static Op: typeof Op; - public static DataTypes: typeof DataTypes; - /** * A hook that is run before validation * @@ -870,8 +766,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with factory */ - public static afterDefine(name: string, fn: (model: ModelStatic) => void): void; - public static afterDefine(fn: (model: ModelStatic) => void): void; + public static afterDefine(name: string, fn: (model: ModelType) => void): void; + public static afterDefine(fn: (model: ModelType) => void): void; /** * A hook that is run before Sequelize() call @@ -893,7 +789,6 @@ export class Sequelize extends Hooks { /** * A hook that is run before sequelize.sync call - * * @param fn A callback function that is called with options passed to sequelize.sync */ public static beforeBulkSync(dname: string, fn: (options: SyncOptions) => HookReturn): void; @@ -901,7 +796,6 @@ export class Sequelize extends Hooks { /** * A hook that is run after sequelize.sync call - * * @param fn A callback function that is called with options passed to sequelize.sync */ public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; @@ -909,7 +803,6 @@ export class Sequelize extends Hooks { /** * A hook that is run before Model.sync call - * * @param fn A callback function that is called with options passed to Model.sync */ public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; @@ -917,7 +810,6 @@ export class Sequelize extends Hooks { /** * A hook that is run after Model.sync call - * * @param fn A callback function that is called with options passed to Model.sync */ public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; @@ -930,7 +822,7 @@ export class Sequelize extends Hooks { * * @param namespace */ - public static useCLS(namespace: ContinuationLocalStorageNamespace): typeof Sequelize; + public static useCLS(namespace: object): typeof Sequelize; /** * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. @@ -942,26 +834,15 @@ export class Sequelize extends Hooks { */ public readonly config: Config; - public readonly options: PartlyRequired; - - public readonly dialect: AbstractDialect; - public readonly modelManager: ModelManager; public readonly connectionManager: ConnectionManager; - /** - * For internal use only. - * - * @type {ContinuationLocalStorageNamespace | undefined} - */ - public static readonly _cls: ContinuationLocalStorageNamespace | undefined; - /** * Dictionary of all models linked with this instance. */ - public models: { - [key: string]: ModelStatic; + public readonly models: { + [key: string]: ModelCtor; }; /** @@ -999,7 +880,6 @@ export class Sequelize extends Hooks { /** * Instantiate sequelize with an URI - * * @param uri A full database URI * @param options See above for possible options */ @@ -1185,8 +1065,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with factory */ - public afterDefine(name: string, fn: (model: ModelStatic) => void): void; - public afterDefine(fn: (model: ModelStatic) => void): void; + public afterDefine(name: string, fn: (model: ModelType) => void): void; + public afterDefine(fn: (model: ModelType) => void): void; /** * A hook that is run before Sequelize() call @@ -1208,7 +1088,6 @@ export class Sequelize extends Hooks { /** * A hook that is run before sequelize.sync call - * * @param fn A callback function that is called with options passed to sequelize.sync */ public beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; @@ -1216,7 +1095,6 @@ export class Sequelize extends Hooks { /** * A hook that is run after sequelize.sync call - * * @param fn A callback function that is called with options passed to sequelize.sync */ public afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; @@ -1224,7 +1102,6 @@ export class Sequelize extends Hooks { /** * A hook that is run before Model.sync call - * * @param fn A callback function that is called with options passed to Model.sync */ public beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; @@ -1232,7 +1109,6 @@ export class Sequelize extends Hooks { /** * A hook that is run after Model.sync call - * * @param fn A callback function that is called with options passed to Model.sync */ public afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; @@ -1265,7 +1141,7 @@ export class Sequelize extends Hooks { * class MyModel extends Model {} * MyModel.init({ * columnA: { - * type: DataTypes.BOOLEAN, + * type: Sequelize.BOOLEAN, * validate: { * is: ["[a-z]",'i'], // will only allow letters * max: 23, // only allow values <= 23 @@ -1277,7 +1153,7 @@ export class Sequelize extends Hooks { * field: 'column_a' * // Other attributes here * }, - * columnB: DataTypes.STRING, + * columnB: Sequelize.STRING, * columnC: 'MY VERY OWN COLUMN TYPE' * }, { sequelize }) * @@ -1290,16 +1166,16 @@ export class Sequelize extends Hooks { * getters. * * For a list of possible data types, see - * https://sequelize.org/docs/v7/other-topics/other-data-types + * https://sequelize.org/master/en/latest/docs/models-definition/#data-types * * For more about getters and setters, see - * https://sequelize.org/docs/v7/core-concepts/getters-setters-virtuals/ + * https://sequelize.org/master/en/latest/docs/models-definition/#getters-setters * * For more about instance and class methods, see - * https://sequelize.org/docs/v7/core-concepts/model-basics/#taking-advantage-of-models-being-classes + * https://sequelize.org/master/en/latest/docs/models-definition/#expansion-of-models * * For more about validation, see - * https://sequelize.org/docs/v7/core-concepts/validations-and-constraints/ + * https://sequelize.org/master/en/latest/docs/models-definition/#validations * * @param modelName The name of the model. The model will be stored in `sequelize.models` under this name * @param attributes An object, where each attribute is a column of the table. Each column can be either a @@ -1309,16 +1185,16 @@ export class Sequelize extends Hooks { */ public define>( modelName: string, - attributes?: ModelAttributes, + attributes: ModelAttributes, options?: ModelOptions - ): ModelStatic; + ): ModelCtor; /** * Fetch a Model which is already defined * * @param modelName The name of a model defined with Sequelize.define */ - public model(modelName: string): ModelStatic; + public model(modelName: string): ModelCtor; /** * Checks whether a model with the given name is defined @@ -1366,33 +1242,6 @@ export class Sequelize extends Hooks { public query(sql: string | { query: string; values: unknown[] }, options: (QueryOptions | QueryOptionsWithType) & { plain: true }): Promise<{ [key: string]: unknown } | null>; public query(sql: string | { query: string; values: unknown[] }, options?: QueryOptions | QueryOptionsWithType): Promise<[unknown[], unknown]>; - /** - * Works like {@link Sequelize#query}, but does not inline replacements. Only bind parameters are supported. - * - * @param sql The SQL to execute - * @param options The options for the query. See {@link QueryRawOptions} for details. - */ - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise<[undefined, number]>; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise<[number, number]>; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise; - public queryRaw( - sql: string | { query: string; values: unknown[] }, - options: QueryRawOptionsWithModel & { plain: true } - ): Promise; - public queryRaw( - sql: string | { query: string; values: unknown[] }, - options: QueryRawOptionsWithModel - ): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType & { plain: true }): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: QueryRawOptionsWithType): Promise; - public queryRaw(sql: string | { query: string; values: unknown[] }, options: (QueryRawOptions | QueryRawOptionsWithType) & { plain: true }): Promise<{ [key: string]: unknown } | null>; - public queryRaw(sql: string | { query: string; values: unknown[] }, options?: QueryRawOptions | QueryRawOptionsWithType): Promise<[unknown[], unknown]>; - /** * Get the fn for random based on the dialect */ @@ -1426,7 +1275,7 @@ export class Sequelize extends Hooks { * @param schema Name of the schema * @param options Options supplied */ - public createSchema(schema: string, options?: Logging): Promise; + public createSchema(schema: string, options: Logging): Promise; /** * Show all defined schemas @@ -1437,7 +1286,7 @@ export class Sequelize extends Hooks { * * @param options Options supplied */ - public showAllSchemas(options?: Logging): Promise; + public showAllSchemas(options: Logging): Promise; /** * Drop a single schema @@ -1449,7 +1298,7 @@ export class Sequelize extends Hooks { * @param schema Name of the schema * @param options Options supplied */ - public dropSchema(schema: string, options?: Logging): Promise; + public dropSchema(schema: string, options: Logging): Promise; /** * Drop all schemas @@ -1460,7 +1309,7 @@ export class Sequelize extends Hooks { * * @param options Options supplied */ - public dropAllSchemas(options?: Logging): Promise; + public dropAllSchemas(options: Logging): Promise; /** * Sync all defined models to the DB. @@ -1531,7 +1380,7 @@ export class Sequelize extends Hooks { * ```js * const cls = require('cls-hooked'); * const namespace = cls.createNamespace('....'); - * const Sequelize = require('@sequelize/core'); + * const Sequelize = require('sequelize'); * Sequelize.useCLS(namespace); * ``` * Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace @@ -1556,11 +1405,6 @@ export class Sequelize extends Hooks { * Returns the database version */ public databaseVersion(): Promise; - - /** - * Returns the installed version of Sequelize - */ - static get version(): string; } // Utilities @@ -1577,14 +1421,10 @@ export class Sequelize extends Hooks { * username: self.sequelize.fn('upper', self.sequelize.col('username')) * }) * ``` - * * @param fn The function you want to call * @param args All further arguments will be passed as arguments to the function */ -export function fn( - fn: string, - ...args: Fn['args'] -): Fn; +export function fn(fn: string, ...args: unknown[]): Fn; /** * Creates a object representing a column in the DB. This is often useful in conjunction with @@ -1614,67 +1454,47 @@ export function literal(val: string): Literal; * * @param args Each argument will be joined by AND */ -export function and>(...args: T): { [Op.and]: T }; +export function and(...args: (WhereOperators | WhereAttributeHash | Where)[]): AndOperator; /** * An OR query * * @param args Each argument will be joined by OR */ -export function or>(...args: T): { [Op.or]: T }; +export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): OrOperator; /** * Creates an object representing nested where conditions for postgres's json data-type. * * @param conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot * notation or a string using postgres json syntax. - * @param value An optional value to compare against. - * Produces a string of the form "<json path> = '<value>'". + * @param value An optional value to compare against. Produces a string of the form " = + * ''". */ export function json(conditionsOrPath: string | object, value?: string | number | boolean): Json; -export type WhereLeftOperand = Fn | ColumnReference | Literal | Cast | ModelAttributeColumnOptions; +export type AttributeType = Fn | Col | Literal | ModelAttributeColumnOptions | string; +export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string | symbol | null; /** - * A way of specifying "attr = condition". - * Can be used as a replacement for the POJO syntax (e.g. `where: { name: 'Lily' }`) when you need to compare a column that the POJO syntax cannot represent. - * - * @param leftOperand The left side of the comparison. - * - A value taken from YourModel.rawAttributes, to reference an attribute. - * The attribute must be defined in your model definition. - * - A Literal (using {@link Sequelize#literal}) - * - A SQL Function (using {@link Sequelize#fn}) - * - A Column name (using {@link Sequelize#col}) - * Note that simple strings to reference an attribute are not supported. You can use the POJO syntax instead. - * @param operator The comparison operator to use. If unspecified, defaults to {@link Op.eq}. - * @param rightOperand The right side of the comparison. Its value depends on the used operator. - * See {@link WhereOperators} for information about what value is valid for each operator. + * A way of specifying attr = condition. * - * @example - * // Using an attribute as the left operand. - * // Equal to: WHERE first_name = 'Lily' - * where(User.rawAttributes.firstName, Op.eq, 'Lily'); + * The attr can either be an object taken from `Model.rawAttributes` (for example `Model.rawAttributes.id` + * or + * `Model.rawAttributes.name`). The attribute should be defined in your model definition. The attribute can + * also be an object from one of the sequelize utility functions (`sequelize.fn`, `sequelize.col` etc.) * - * @example - * // Using a column name as the left operand. - * // Equal to: WHERE first_name = 'Lily' - * where(col('first_name'), Op.eq, 'Lily'); + * For string attributes, use the regular `{ where: { attr: something }}` syntax. If you don't want your + * string to be escaped, use `sequelize.literal`. * - * @example - * // Using a SQL function on the left operand. - * // Equal to: WHERE LOWER(first_name) = 'lily' - * where(fn('LOWER', col('first_name')), Op.eq, 'lily'); - * - * @example - * // Using raw SQL as the left operand. - * // Equal to: WHERE 'Lily' = 'Lily' - * where(literal(`'Lily'`), Op.eq, 'Lily'); + * @param attr The attribute, which can be either an attribute object from `Model.rawAttributes` or a + * sequelize object, for example an instance of `sequelize.fn`. For simple string attributes, use the + * POJO syntax + * @param comparator Comparator + * @param logic The condition. Can be both a simply type, or a further condition (`.or`, `.and`, `.literal` + * etc.) */ -export function where(leftOperand: WhereLeftOperand | Where, operator: Op, rightOperand: WhereOperators[Op]): Where; -export function where(leftOperand: any, operator: string, rightOperand: any): Where; -export function where(leftOperand: WhereLeftOperand, rightOperand: WhereAttributeHashValue): Where; - -type ContinuationLocalStorageNamespace = { - get(key: string): unknown; - set(key: string, value: unknown): void; -}; +export function where(attr: AttributeType, comparator: string | symbol, logic: LogicType): Where; +export function where(attr: AttributeType, logic: LogicType): Where; + +export default Sequelize; diff --git a/test/types/infer-attributes.ts b/test/types/infer-attributes.ts index 0a1d95dc5375..1892acb5b6aa 100644 --- a/test/types/infer-attributes.ts +++ b/test/types/infer-attributes.ts @@ -1,110 +1,106 @@ import { expectTypeOf } from 'expect-type'; -import { - Attributes, - CreationAttributes, - CreationOptional, - DataTypes, - ForeignKey, - InferAttributes, - InferCreationAttributes, - Model, - NonAttribute, - Sequelize, -} from '@sequelize/core'; - -class Project extends Model> { - declare id: number; -} - -class User extends Model, - InferCreationAttributes> { - declare optionalAttribute: CreationOptional; - declare mandatoryAttribute: string; - - declare optionalArrayAttribute: CreationOptional; - declare mandatoryArrayAttribute: string[]; - - // note: using CreationOptional here is unnecessary, but we still ensure that it works. - declare nullableOptionalAttribute: CreationOptional; - - declare nonAttribute: NonAttribute; - declare nonAttributeArray: NonAttribute; - declare nonAttributeNestedArray: NonAttribute; - - declare omittedAttribute: number; - declare omittedAttributeArray: number[]; - - declare joinedEntity?: NonAttribute; - declare projectId: CreationOptional>; - - instanceMethod() { - } - - static staticMethod() { - } +import { InferAttributes, InferCreationAttributes, CreationOptional, Model, NonAttribute, Attributes, CreationAttributes } from 'sequelize'; + +class User extends Model< + InferAttributes, + InferCreationAttributes +> { + declare id: CreationOptional; + declare name: string; + declare anArray: CreationOptional; + + // omitted using `omit` option + declare groups: Group[]; + // omitted using `NonAttribute` + declare projects: NonAttribute; + + instanceMethod() {} + static staticMethod() {} } -User.init({ - mandatoryArrayAttribute: DataTypes.ARRAY(DataTypes.STRING), - mandatoryAttribute: DataTypes.STRING, - // projectId is omitted but still works, because it is branded with 'ForeignKey' - nullableOptionalAttribute: DataTypes.STRING, - optionalArrayAttribute: DataTypes.ARRAY(DataTypes.STRING), - optionalAttribute: DataTypes.INTEGER, -}, { sequelize: new Sequelize() }); - type UserAttributes = Attributes; type UserCreationAttributes = CreationAttributes; expectTypeOf().not.toBeAny(); -expectTypeOf().not.toBeAny(); - -expectTypeOf().not.toBeNullable(); -expectTypeOf().toBeNullable(); - -expectTypeOf().not.toBeNullable(); -expectTypeOf().not.toBeNullable(); -expectTypeOf().not.toBeNullable(); -expectTypeOf().toBeNullable(); - -type NonUndefined = T extends undefined ? never : T; +{ + class Test extends Model> { + declare id: NonAttribute; + } -expectTypeOf().not.toEqualTypeOf>(); + const win: Attributes = {}; +} -expectTypeOf().not.toBeNullable(); -expectTypeOf().not.toBeNullable(); +{ + const win: UserAttributes = { + id: 1, + name: '', + anArray: [''], + }; + + const fail1: UserAttributes = { + id: 1, + name: '', + // @ts-expect-error - 'extra' should not be present + extra: '' + }; + + // @ts-expect-error - 'name' should be present + const fail2: UserAttributes = { + id: 1, + }; +} -expectTypeOf().not.toHaveProperty('nonAttribute'); -expectTypeOf().not.toHaveProperty('nonAttribute'); +{ + const win: UserCreationAttributes = { + id: undefined, + name: '', + anArray: undefined, + }; + + const fail1: UserCreationAttributes = { + id: 1, + name: '', + // @ts-expect-error 'extra' does not exist + extra: '' + }; + + const fail2: UserCreationAttributes = { + id: 1, + // @ts-expect-error name cannot be undefined + name: undefined, + anArray: undefined, + }; +} -expectTypeOf().not.toHaveProperty('nonAttributeArray'); -expectTypeOf().not.toHaveProperty('nonAttributeArray'); +type GroupAttributes = InferAttributes; -expectTypeOf().not.toHaveProperty('nonAttributeNestedArray'); -expectTypeOf().not.toHaveProperty('nonAttributeNestedArray'); +class Group extends Model { + declare id: number; +} -expectTypeOf().not.toHaveProperty('omittedAttribute'); -expectTypeOf().not.toHaveProperty('omittedAttribute'); +{ + // @ts-expect-error - id should not be missing + const fail1: GroupAttributes = {}; -expectTypeOf().not.toHaveProperty('omittedAttributeArray'); -expectTypeOf().not.toHaveProperty('omittedAttributeArray'); + // @ts-expect-error - id should not be missing + const fail2: InferAttributes = {}; -expectTypeOf().not.toHaveProperty('instanceMethod'); -expectTypeOf().not.toHaveProperty('instanceMethod'); + // @ts-expect-error - id should not be missing + const fail3: InferAttributes = {}; +} -expectTypeOf().not.toHaveProperty('staticMethod'); -expectTypeOf().not.toHaveProperty('staticMethod'); +class Project extends Model> { + declare id: number; +} // brands: { - const user = User.build({ mandatoryArrayAttribute: [], mandatoryAttribute: '' }); - // ensure branding does not break arrays. - const brandedArray: NonAttribute = user.nonAttributeArray; - const anArray: string[] = user.nonAttributeArray; - const item: boolean = user.nonAttributeArray[0].endsWith(''); + const brandedArray: NonAttribute = ['']; + const anArray: string[] = brandedArray; + const item: string = brandedArray[0]; } { @@ -121,23 +117,7 @@ expectTypeOf().not.toHaveProperty('staticMethod'); } { - const user = User.build({ mandatoryArrayAttribute: [], mandatoryAttribute: '' }); - const project: Project = user.joinedEntity!; - - // ensure branding does not break objects - const id = project.id; -} - -{ - // ensure branding does not break null - const brandedString: NonAttribute = null; -} - -{ - class User2 extends Model, InferCreationAttributes> { - declare nullableAttribute: string | null; - } - - // this should work, all null attributes are optional in Model.create - User2.create({}); + // ensure branding does not break instances + const brandedUser: NonAttribute = new User(); + const aUser: User = brandedUser; } diff --git a/test/types/models/User.ts b/test/types/models/User.ts index 9081a00d7fbd..e2029fb062f7 100644 --- a/test/types/models/User.ts +++ b/test/types/models/User.ts @@ -11,7 +11,7 @@ import { Model, ModelStatic, Op -} from '@sequelize/core'; +} from 'sequelize'; import { sequelize } from '../connection'; type NonUserAttributes = 'group'; @@ -115,9 +115,9 @@ User.addHook('afterDestroy', async (instance, options) => { }); // Model#addScope -User.addScope('withoutLastName', { +User.addScope('withoutFirstName', { where: { - lastName: { + firstName: { [Op.is]: null } } diff --git a/test/types/typescriptDocs/ModelInitNoAttributes.ts b/test/types/typescriptDocs/ModelInitNoAttributes.ts index ab4c8feda486..ff8a5c22a22d 100644 --- a/test/types/typescriptDocs/ModelInitNoAttributes.ts +++ b/test/types/typescriptDocs/ModelInitNoAttributes.ts @@ -1,11 +1,17 @@ -import { Sequelize, Model, DataTypes } from '@sequelize/core'; +/** + * Keep this file in sync with the code in the "Usage without strict types for + * attributes" section in /docs/manual/other-topics/typescript.md + * + * Don't include this comment in the md file. + */ +import { Sequelize, Model, DataTypes } from 'sequelize'; const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); class User extends Model { - declare id: number; - declare name: string; - declare preferredName: string | null; + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields } User.init( diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts new file mode 100644 index 000000000000..b5f5982dbf92 --- /dev/null +++ b/types/lib/model.d.ts @@ -0,0 +1,3124 @@ +import { IndexHints } from '..'; +import { Association, BelongsTo, BelongsToMany, BelongsToManyOptions, BelongsToOptions, HasMany, HasManyOptions, HasOne, HasOneOptions } from './associations/index'; +import { DataType } from './data-types'; +import { Deferrable } from './deferrable'; +import { HookReturn, Hooks, ModelHooks } from './hooks'; +import { ValidationOptions } from './instance-validator'; +import { IndexesOptions, QueryOptions, TableName } from './query-interface'; +import { Sequelize, SyncOptions } from './sequelize'; +import { Col, Fn, Literal, Where, MakeUndefinedOptional, AnyFunction } from './utils'; +import { LOCK, Transaction, Op } from '..'; +import { SetRequired } from '../type-helpers/set-required' + +export interface Logging { + /** + * A function that gets executed while running the query to log the sql. + */ + logging?: boolean | ((sql: string, timing?: number) => void); + + /** + * Pass query execution time in milliseconds as second argument to logging function (options.logging). + */ + benchmark?: boolean; +} + +export interface Poolable { + /** + * Force the query to use the write pool, regardless of the query type. + * + * @default false + */ + useMaster?: boolean; +} + +export interface Transactionable { + /** + * Transaction to run query under + */ + transaction?: Transaction | null; +} + +export interface SearchPathable { + /** + * An optional parameter to specify the schema search_path (Postgres only) + */ + searchPath?: string; +} + +export interface Filterable { + /** + * Attribute has to be matched for rows to be selected for the given action. + */ + where?: WhereOptions; +} + +export interface Projectable { + /** + * A list of the attributes that you want to select. To rename an attribute, you can pass an array, with + * two elements - the first is the name of the attribute in the DB (or some kind of expression such as + * `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to + * have in the returned instance + */ + attributes?: FindAttributeOptions; +} + +export interface Paranoid { + /** + * If true, only non-deleted records will be returned. If false, both deleted and non-deleted records will + * be returned. Only applies if `options.paranoid` is true for the model. + */ + paranoid?: boolean; +} + +export type GroupOption = string | Fn | Col | (string | Fn | Col)[]; + +/** + * Options to pass to Model on drop + */ +export interface DropOptions extends Logging { + /** + * Also drop all objects depending on this table, such as views. Only works in postgres + */ + cascade?: boolean; +} + +/** + * Schema Options provided for applying a schema to a model + */ +export interface SchemaOptions extends Logging { + /** + * The character(s) that separates the schema name from the table name + */ + schemaDelimiter?: string; +} + +/** + * Scope Options for Model.scope + */ +export interface ScopeOptions { + /** + * The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. + * To apply simple scopes and scope functions with no arguments, pass them as strings. For scope function, + * pass an object, with a `method` property. The value can either be a string, if the method does not take + * any arguments, or an array, where the first element is the name of the method, and consecutive elements + * are arguments to that method. Pass null to remove all scopes, including the default. + */ + method: string | readonly [string, ...unknown[]]; +} + +/** + * The type accepted by every `where` option + */ +export type WhereOptions = + | WhereAttributeHash + | AndOperator + | OrOperator + | Literal + | Fn + | Where; + +/** + * Example: `[Op.any]: [2,3]` becomes `ANY ARRAY[2, 3]::INTEGER` + * + * _PG only_ + */ +export interface AnyOperator { + [Op.any]: readonly (string | number)[]; +} + +/** TODO: Undocumented? */ +export interface AllOperator { + [Op.all]: readonly (string | number | Date | Literal)[]; +} + +export type Rangable = readonly [number, number] | readonly [Date, Date] | readonly [string, string] | Literal; + +/** + * Operators that can be used in WhereOptions + * + * See https://sequelize.org/master/en/v3/docs/querying/#operators + */ +export interface WhereOperators { + /** + * Example: `[Op.any]: [2,3]` becomes `ANY ARRAY[2, 3]::INTEGER` + * + * _PG only_ + */ + + /** Example: `[Op.eq]: 6,` becomes `= 6` */ + [Op.eq]?: null | boolean | string | number | Literal | WhereOperators | Col; + + [Op.any]?: readonly (string | number | Literal)[] | Literal; + + /** Example: `[Op.gte]: 6,` becomes `>= 6` */ + [Op.gte]?: number | string | Date | Literal | Col; + + /** Example: `[Op.lt]: 10,` becomes `< 10` */ + [Op.lt]?: number | string | Date | Literal | Col; + + /** Example: `[Op.lte]: 10,` becomes `<= 10` */ + [Op.lte]?: number | string | Date | Literal | Col; + + /** Example: `[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` becomes `@@ to_tsquery('fat & rat')` */ + [Op.match]?: Fn; + + /** Example: `[Op.ne]: 20,` becomes `!= 20` */ + [Op.ne]?: null | string | number | Literal | WhereOperators; + + /** Example: `[Op.not]: true,` becomes `IS NOT TRUE` */ + [Op.not]?: null | boolean | string | number | Literal | WhereOperators; + + /** Example: `[Op.is]: null,` becomes `IS NULL` */ + [Op.is]?: null; + + /** Example: `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` */ + [Op.between]?: Rangable; + + /** Example: `[Op.in]: [1, 2],` becomes `IN [1, 2]` */ + [Op.in]?: readonly (string | number | Literal)[] | Literal; + + /** Example: `[Op.notIn]: [1, 2],` becomes `NOT IN [1, 2]` */ + [Op.notIn]?: readonly (string | number | Literal)[] | Literal; + + /** + * Examples: + * - `[Op.like]: '%hat',` becomes `LIKE '%hat'` + * - `[Op.like]: { [Op.any]: ['cat', 'hat']}` becomes `LIKE ANY ARRAY['cat', 'hat']` + */ + [Op.like]?: string | Literal | AnyOperator | AllOperator; + + /** + * Examples: + * - `[Op.notLike]: '%hat'` becomes `NOT LIKE '%hat'` + * - `[Op.notLike]: { [Op.any]: ['cat', 'hat']}` becomes `NOT LIKE ANY ARRAY['cat', 'hat']` + */ + [Op.notLike]?: string | Literal | AnyOperator | AllOperator; + + /** + * case insensitive PG only + * + * Examples: + * - `[Op.iLike]: '%hat'` becomes `ILIKE '%hat'` + * - `[Op.iLike]: { [Op.any]: ['cat', 'hat']}` becomes `ILIKE ANY ARRAY['cat', 'hat']` + */ + [Op.iLike]?: string | Literal | AnyOperator | AllOperator; + + /** + * PG array overlap operator + * + * Example: `[Op.overlap]: [1, 2]` becomes `&& [1, 2]` + */ + [Op.overlap]?: Rangable; + + /** + * PG array contains operator + * + * Example: `[Op.contains]: [1, 2]` becomes `@> [1, 2]` + */ + [Op.contains]?: readonly (string | number)[] | Rangable; + + /** + * PG array contained by operator + * + * Example: `[Op.contained]: [1, 2]` becomes `<@ [1, 2]` + */ + [Op.contained]?: readonly (string | number)[] | Rangable; + + /** Example: `[Op.gt]: 6,` becomes `> 6` */ + [Op.gt]?: number | string | Date | Literal | Col; + + /** + * PG only + * + * Examples: + * - `[Op.notILike]: '%hat'` becomes `NOT ILIKE '%hat'` + * - `[Op.notLike]: ['cat', 'hat']` becomes `LIKE ANY ARRAY['cat', 'hat']` + */ + [Op.notILike]?: string | Literal | AnyOperator | AllOperator; + + /** Example: `[Op.notBetween]: [11, 15],` becomes `NOT BETWEEN 11 AND 15` */ + [Op.notBetween]?: Rangable; + + /** + * Strings starts with value. + */ + [Op.startsWith]?: string; + + /** + * String ends with value. + */ + [Op.endsWith]?: string; + /** + * String contains value. + */ + [Op.substring]?: string; + + /** + * MySQL/PG only + * + * Matches regular expression, case sensitive + * + * Example: `[Op.regexp]: '^[h|a|t]'` becomes `REGEXP/~ '^[h|a|t]'` + */ + [Op.regexp]?: string; + + /** + * MySQL/PG only + * + * Does not match regular expression, case sensitive + * + * Example: `[Op.notRegexp]: '^[h|a|t]'` becomes `NOT REGEXP/!~ '^[h|a|t]'` + */ + [Op.notRegexp]?: string; + + /** + * PG only + * + * Matches regular expression, case insensitive + * + * Example: `[Op.iRegexp]: '^[h|a|t]'` becomes `~* '^[h|a|t]'` + */ + [Op.iRegexp]?: string; + + /** + * PG only + * + * Does not match regular expression, case insensitive + * + * Example: `[Op.notIRegexp]: '^[h|a|t]'` becomes `!~* '^[h|a|t]'` + */ + [Op.notIRegexp]?: string; + + /** + * PG only + * + * Forces the operator to be strictly left eg. `<< [a, b)` + */ + [Op.strictLeft]?: Rangable; + + /** + * PG only + * + * Forces the operator to be strictly right eg. `>> [a, b)` + */ + [Op.strictRight]?: Rangable; + + /** + * PG only + * + * Forces the operator to not extend the left eg. `&> [1, 2)` + */ + [Op.noExtendLeft]?: Rangable; + + /** + * PG only + * + * Forces the operator to not extend the left eg. `&< [1, 2)` + */ + [Op.noExtendRight]?: Rangable; + +} + +/** Example: `[Op.or]: [{a: 5}, {a: 6}]` becomes `(a = 5 OR a = 6)` */ +export interface OrOperator { + [Op.or]: WhereOptions | readonly WhereOptions[] | WhereValue | readonly WhereValue[]; +} + +/** Example: `[Op.and]: {a: 5}` becomes `AND (a = 5)` */ +export interface AndOperator { + [Op.and]: WhereOptions | readonly WhereOptions[] | WhereValue | readonly WhereValue[]; +} + +/** + * Where Geometry Options + */ +export interface WhereGeometryOptions { + type: string; + coordinates: readonly (number[] | number)[]; +} + +/** + * Used for the right hand side of WhereAttributeHash. + * WhereAttributeHash is in there for JSON columns. + */ +export type WhereValue = + | string + | number + | bigint + | boolean + | Date + | Buffer + | null + | WhereOperators + | WhereAttributeHash // for JSON columns + | Col // reference another column + | Fn + | OrOperator + | AndOperator + | WhereGeometryOptions + | readonly (string | number | Buffer | WhereAttributeHash)[]; // implicit [Op.or] + +/** + * A hash of attributes to describe your search. + */ +export type WhereAttributeHash = { + /** + * Possible key values: + * - A simple attribute name + * - A nested key for JSON columns + * + * { + * "meta.audio.length": { + * [Op.gt]: 20 + * } + * } + */ + [field in keyof TAttributes]?: WhereValue | WhereOptions; +} +/** + * Through options for Include Options + */ +export interface IncludeThroughOptions extends Filterable, Projectable { + /** + * The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / + * `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural + */ + as?: string; + + /** + * If true, only non-deleted records will be returned from the join table. + * If false, both deleted and non-deleted records will be returned. + * Only applies if through model is paranoid. + */ + paranoid?: boolean; +} + +/** + * Options for eager-loading associated models, also allowing for all associations to be loaded at once + */ +export type Includeable = ModelType | Association | IncludeOptions | { all: true, nested?: true } | string; + +/** + * Complex include options + */ +export interface IncludeOptions extends Filterable, Projectable, Paranoid { + /** + * Mark the include as duplicating, will prevent a subquery from being used. + */ + duplicating?: boolean; + /** + * The model you want to eagerly load + */ + model?: ModelType; + + /** + * The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / + * `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural + */ + as?: string; + + /** + * The association you want to eagerly load. (This can be used instead of providing a model/as pair) + */ + association?: Association | string; + + /** + * Custom `on` clause, overrides default. + */ + on?: WhereOptions; + + /** + * Note that this converts the eager load to an inner join, + * unless you explicitly set `required: false` + */ + where?: WhereOptions; + + /** + * If true, converts to an inner join, which means that the parent model will only be loaded if it has any + * matching children. True if `include.where` is set, false otherwise. + */ + required?: boolean; + + /** + * If true, converts to a right join if dialect support it. Ignored if `include.required` is true. + */ + right?: boolean; + + /** + * Limit include. Only available when setting `separate` to true. + */ + limit?: number; + + /** + * Run include in separate queries. + */ + separate?: boolean; + + /** + * Through Options + */ + through?: IncludeThroughOptions; + + /** + * Load further nested related models + */ + include?: Includeable[]; + + /** + * Order include. Only available when setting `separate` to true. + */ + order?: Order; + + /** + * Use sub queries. This should only be used if you know for sure the query does not result in a cartesian product. + */ + subQuery?: boolean; +} + +type OrderItemAssociation = Association | ModelStatic | { model: ModelStatic; as: string } | string +type OrderItemColumn = string | Col | Fn | Literal +export type OrderItem = + | string + | Fn + | Col + | Literal + | [OrderItemColumn, string] + | [OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] +export type Order = Fn | Col | Literal | OrderItem[]; + +/** + * Please note if this is used the aliased property will not be available on the model instance + * as a property but only via `instance.get('alias')`. + */ +export type ProjectionAlias = readonly [string | Literal | Fn | Col, string]; + +export type FindAttributeOptions = + | (string | ProjectionAlias)[] + | { + exclude: string[]; + include?: (string | ProjectionAlias)[]; + } + | { + exclude?: string[]; + include: (string | ProjectionAlias)[]; + }; + +export interface IndexHint { + type: IndexHints; + values: string[]; +} + +export interface IndexHintable { + /** + * MySQL only. + */ + indexHints?: IndexHint[]; +} + +type Omit = Pick> + +/** + * Options that are passed to any model creating a SELECT query + * + * A hash of options to describe the scope of the search + */ +export interface FindOptions + extends QueryOptions, Filterable, Projectable, Paranoid, IndexHintable +{ + /** + * A list of associations to eagerly load using a left join (a single association is also supported). Supported is either + * `{ include: Model1 }`, `{ include: [ Model1, Model2, ...]}`, `{ include: [{ model: Model1, as: 'Alias' }]}` or + * `{ include: [{ all: true }]}`. + * If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in + * the as attribute when eager loading Y). + */ + include?: Includeable | Includeable[]; + + /** + * Specifies an ordering. If a string is provided, it will be escaped. Using an array, you can provide + * several columns / functions to order by. Each element can be further wrapped in a two-element array. The + * first element is the column / function to order by, the second is the direction. For example: + * `order: [['name', 'DESC']]`. In this way the column will be escaped, but the direction will not. + */ + order?: Order; + + /** + * GROUP BY in sql + */ + group?: GroupOption; + + /** + * Limit the results + */ + limit?: number; + + /** + * Skip the results; + */ + offset?: number; + + /** + * Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. + * Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model + * locks with joins. See [transaction.LOCK for an example](transaction#lock) + */ + lock?: + | LOCK + | { level: LOCK; of: ModelStatic } + | boolean; + /** + * Skip locked rows. Only supported in Postgres. + */ + skipLocked?: boolean; + + /** + * Return raw result. See sequelize.query for more information. + */ + raw?: boolean; + + /** + * Select group rows after groups and aggregates are computed. + */ + having?: WhereOptions; + + /** + * Use sub queries (internal) + */ + subQuery?: boolean; +} + +export interface NonNullFindOptions extends FindOptions { + /** + * Throw if nothing was found. + */ + rejectOnEmpty: boolean | Error; +} + +/** + * Options for Model.count method + */ +export interface CountOptions + extends Logging, Transactionable, Filterable, Projectable, Paranoid, Poolable +{ + /** + * Include options. See `find` for details + */ + include?: Includeable | Includeable[]; + + /** + * Apply COUNT(DISTINCT(col)) + */ + distinct?: boolean; + + /** + * GROUP BY in sql + * Used in conjunction with `attributes`. + * @see Projectable + */ + group?: GroupOption; + + /** + * The column to aggregate on. + */ + col?: string; +} + +/** + * Options for Model.count when GROUP BY is used + */ +export type CountWithOptions = SetRequired, 'group'> + +export interface FindAndCountOptions extends CountOptions, FindOptions { } + +interface GroupedCountResultItem { + [key: string]: unknown // projected attributes + count: number // the count for each group +} + +/** + * Options for Model.build method + */ +export interface BuildOptions { + /** + * If set to true, values will ignore field and virtual setters. + */ + raw?: boolean; + + /** + * Is this record new + */ + isNewRecord?: boolean; + + /** + * An array of include options. A single option is also supported - Used to build prefetched/included model instances. See `set` + * + * TODO: See set + */ + include?: Includeable | Includeable[]; +} + +export interface Silent { + /** + * If true, the updatedAt timestamp will not be updated. + * + * @default false + */ + silent?: boolean; +} + +/** + * Options for Model.create method + */ +export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable, Hookable { + /** + * If set, only columns matching those in fields will be saved + */ + fields?: (keyof TAttributes)[]; + + /** + * dialect specific ON CONFLICT DO NOTHING / INSERT IGNORE + */ + ignoreDuplicates?: boolean; + + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | (keyof TAttributes)[]; + + /** + * If false, validations won't be run. + * + * @default true + */ + validate?: boolean; + +} + +export interface Hookable { + + /** + * If `false` the applicable hooks will not be called. + * The default value depends on the context. + */ + hooks?: boolean + +} + +/** + * Options for Model.findOrCreate method + */ +export interface FindOrCreateOptions + extends FindOptions, CreateOptions +{ + /** + * Default values to use if building a new instance + */ + defaults?: TCreationAttributes; +} + +/** + * Options for Model.findOrBuild method + */ +export interface FindOrBuildOptions + extends FindOptions, BuildOptions +{ + /** + * Default values to use if building a new instance + */ + defaults?: TCreationAttributes; +} + +/** + * Options for Model.upsert method + */ +export interface UpsertOptions extends Logging, Transactionable, SearchPathable, Hookable { + /** + * The fields to insert / update. Defaults to all fields + */ + fields?: (keyof TAttributes)[]; + + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | (keyof TAttributes)[]; + + /** + * Run validations before the row is inserted + */ + validate?: boolean; + /** + * Optional override for the conflict fields in the ON CONFLICT part of the query. + * Only supported in Postgres >= 9.5 and SQLite >= 3.24.0 + */ + conflictFields?: (keyof TAttributes)[]; +} + +/** + * Options for Model.bulkCreate method + */ +export interface BulkCreateOptions extends Logging, Transactionable, Hookable, SearchPathable { + /** + * Fields to insert (defaults to all fields) + */ + fields?: (keyof TAttributes)[]; + + /** + * Should each row be subject to validation before it is inserted. The whole insert will fail if one row + * fails validation + */ + validate?: boolean; + + /** + * Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if + * options.hooks is true. + */ + individualHooks?: boolean; + + /** + * Ignore duplicate values for primary keys? + * + * @default false + */ + ignoreDuplicates?: boolean; + + /** + * Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, + * MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). + */ + updateOnDuplicate?: (keyof TAttributes)[]; + + /** + * Include options. See `find` for details + */ + include?: Includeable | Includeable[]; + + /** + * Return all columns or only the specified columns for the affected rows (only for postgres) + */ + returning?: boolean | (keyof TAttributes)[]; +} + +/** + * The options passed to Model.destroy in addition to truncate + */ +export interface TruncateOptions extends Logging, Transactionable, Filterable, Hookable { + /** + * Only used in conjuction with TRUNCATE. Truncates all tables that have foreign-key references to the + * named table, or to any tables added to the group due to CASCADE. + * + * @default false; + */ + cascade?: boolean; + + /** + * If set to true, destroy will SELECT all records matching the where parameter and will execute before / + * after destroy hooks on each row + */ + individualHooks?: boolean; + + /** + * How many rows to delete + */ + limit?: number; + + /** + * Delete instead of setting deletedAt to current timestamp (only applicable if `paranoid` is enabled) + */ + force?: boolean; + + /** + * Only used in conjunction with `truncate`. + * Automatically restart sequences owned by columns of the truncated table + */ + restartIdentity?: boolean; +} + +/** + * Options used for Model.destroy + */ +export interface DestroyOptions extends TruncateOptions { + /** + * If set to true, dialects that support it will use TRUNCATE instead of DELETE FROM. If a table is + * truncated the where and limit options are ignored + */ + truncate?: boolean; +} + +/** + * Options for Model.restore + */ +export interface RestoreOptions extends Logging, Transactionable, Filterable, Hookable { + + /** + * If set to true, restore will find all records within the where parameter and will execute before / after + * bulkRestore hooks on each row + */ + individualHooks?: boolean; + + /** + * How many rows to undelete + */ + limit?: number; +} + +/** + * Options used for Model.update + */ +export interface UpdateOptions extends Logging, Transactionable, Paranoid, Hookable { + /** + * Options to describe the scope of the search. + */ + where: WhereOptions; + + /** + * Fields to update (defaults to all fields) + */ + fields?: (keyof TAttributes)[]; + + /** + * Should each row be subject to validation before it is inserted. The whole insert will fail if one row + * fails validation. + * + * @default true + */ + validate?: boolean; + + /** + * Whether or not to update the side effects of any virtual setters. + * + * @default true + */ + sideEffects?: boolean; + + /** + * Run before / after update hooks?. If true, this will execute a SELECT followed by individual UPDATEs. + * A select is needed, because the row data needs to be passed to the hooks + * + * @default false + */ + individualHooks?: boolean; + + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | (keyof TAttributes)[]; + + /** + * How many rows to update (only for mysql and mariadb) + */ + limit?: number; + + /** + * If true, the updatedAt timestamp will not be updated. + */ + silent?: boolean; +} + +/** + * Options used for Model.aggregate + */ +export interface AggregateOptions + extends QueryOptions, Filterable, Paranoid +{ + /** + * The type of the result. If `field` is a field in this Model, the default will be the type of that field, + * otherwise defaults to float. + */ + dataType?: string | T; + + /** + * Applies DISTINCT to the field being aggregated over + */ + distinct?: boolean; +} + +// instance + +/** + * Options used for Instance.increment method + */ +export interface IncrementDecrementOptions + extends Logging, Transactionable, Silent, SearchPathable, Filterable { } + +/** + * Options used for Instance.increment method + */ +export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptions { + /** + * The number to increment by + * + * @default 1 + */ + by?: number; +} + +/** + * Options used for Instance.restore method + */ +export interface InstanceRestoreOptions extends Logging, Transactionable { } + +/** + * Options used for Instance.destroy method + */ +export interface InstanceDestroyOptions extends Logging, Transactionable, Hookable { + /** + * If set to true, paranoid models will actually be deleted + */ + force?: boolean; +} + +/** + * Options used for Instance.update method + */ +export interface InstanceUpdateOptions extends + SaveOptions, SetOptions, Filterable { } + +/** + * Options used for Instance.set method + */ +export interface SetOptions { + /** + * If set to true, field and virtual setters will be ignored + */ + raw?: boolean; + + /** + * Clear all previously set data values + */ + reset?: boolean; +} + +/** + * Options used for Instance.save method + */ +export interface SaveOptions extends Logging, Transactionable, Silent, Hookable { + /** + * An optional array of strings, representing database columns. If fields is provided, only those columns + * will be validated and saved. + */ + fields?: (keyof TAttributes)[]; + + /** + * If false, validations won't be run. + * + * @default true + */ + validate?: boolean; + + /** + * A flag that defines if null values should be passed as values or not. + * + * @default false + */ + omitNull?: boolean; +} + +/** + * Model validations, allow you to specify format/content/inheritance validations for each attribute of the + * model. + * + * Validations are automatically run on create, update and save. You can also call validate() to manually + * validate an instance. + * + * The validations are implemented by validator.js. + */ +export interface ModelValidateOptions { + /** + * - `{ is: ['^[a-z]+$','i'] }` will only allow letters + * - `{ is: /^[a-z]+$/i }` also only allows letters + */ + is?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; + + /** + * - `{ not: ['[a-z]','i'] }` will not allow letters + */ + not?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; + + /** + * checks for email format (foo@bar.com) + */ + isEmail?: boolean | { msg: string }; + + /** + * checks for url format (http://foo.com) + */ + isUrl?: boolean | { msg: string }; + + /** + * checks for IPv4 (129.89.23.1) or IPv6 format + */ + isIP?: boolean | { msg: string }; + + /** + * checks for IPv4 (129.89.23.1) + */ + isIPv4?: boolean | { msg: string }; + + /** + * checks for IPv6 format + */ + isIPv6?: boolean | { msg: string }; + + /** + * will only allow letters + */ + isAlpha?: boolean | { msg: string }; + + /** + * will only allow alphanumeric characters, so "_abc" will fail + */ + isAlphanumeric?: boolean | { msg: string }; + + /** + * will only allow numbers + */ + isNumeric?: boolean | { msg: string }; + + /** + * checks for valid integers + */ + isInt?: boolean | { msg: string }; + + /** + * checks for valid floating point numbers + */ + isFloat?: boolean | { msg: string }; + + /** + * checks for any numbers + */ + isDecimal?: boolean | { msg: string }; + + /** + * checks for lowercase + */ + isLowercase?: boolean | { msg: string }; + + /** + * checks for uppercase + */ + isUppercase?: boolean | { msg: string }; + + /** + * won't allow null + */ + notNull?: boolean | { msg: string }; + + /** + * only allows null + */ + isNull?: boolean | { msg: string }; + + /** + * don't allow empty strings + */ + notEmpty?: boolean | { msg: string }; + + /** + * only allow a specific value + */ + equals?: string | { msg: string }; + + /** + * force specific substrings + */ + contains?: string | { msg: string }; + + /** + * check the value is not one of these + */ + notIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; + + /** + * check the value is one of these + */ + isIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; + + /** + * don't allow specific substrings + */ + notContains?: readonly string[] | string | { msg: string; args: readonly string[] | string }; + + /** + * only allow values with length between 2 and 10 + */ + len?: readonly [number, number] | { msg: string; args: readonly [number, number] }; + + /** + * only allow uuids + */ + isUUID?: number | { msg: string; args: number }; + + /** + * only allow date strings + */ + isDate?: boolean | { msg: string; args: boolean }; + + /** + * only allow date strings after a specific date + */ + isAfter?: string | { msg: string; args: string }; + + /** + * only allow date strings before a specific date + */ + isBefore?: string | { msg: string; args: string }; + + /** + * only allow values + */ + max?: number | { msg: string; args: readonly [number] }; + + /** + * only allow values >= 23 + */ + min?: number | { msg: string; args: readonly [number] }; + + /** + * only allow arrays + */ + isArray?: boolean | { msg: string; args: boolean }; + + /** + * check for valid credit card numbers + */ + isCreditCard?: boolean | { msg: string; args: boolean }; + + // TODO: Enforce 'rest' indexes to have type `(value: unknown) => boolean` + // Blocked by: https://github.com/microsoft/TypeScript/issues/7765 + /** + * Custom validations are also possible + */ + [name: string]: unknown; +} + +/** + * Interface for indexes property in InitOptions + */ +export type ModelIndexesOptions = IndexesOptions + +/** + * Interface for name property in InitOptions + */ +export interface ModelNameOptions { + /** + * Singular model name + */ + singular?: string; + + /** + * Plural model name + */ + plural?: string; +} + +/** + * Interface for getterMethods in InitOptions + */ +export interface ModelGetterOptions { + [name: string]: (this: M) => unknown; +} + +/** + * Interface for setterMethods in InitOptions + */ +export interface ModelSetterOptions { + [name: string]: (this: M, val: any) => void; +} + +/** + * Interface for Define Scope Options + */ +export interface ModelScopeOptions { + /** + * Name of the scope and it's query + */ + [scopeName: string]: FindOptions | ((...args: readonly any[]) => FindOptions); +} + +/** + * General column options + */ +export interface ColumnOptions { + /** + * If false, the column will have a NOT NULL constraint, and a not null validation will be run before an + * instance is saved. + * @default true + */ + allowNull?: boolean; + + /** + * If set, sequelize will map the attribute name to a different name in the database + */ + field?: string; + + /** + * A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`) + */ + defaultValue?: unknown; +} + +/** + * References options for the column's attributes + */ +export interface ModelAttributeColumnReferencesOptions { + /** + * If this column references another table, provide it here as a Model, or a string + */ + model?: TableName | ModelType; + + /** + * The column of the foreign table that this column references + */ + key?: string; + + /** + * When to check for the foreign key constraing + * + * PostgreSQL only + */ + deferrable?: Deferrable; +} + +/** + * Column options for the model schema attributes + */ +export interface ModelAttributeColumnOptions extends ColumnOptions { + /** + * A string or a data type + */ + type: DataType; + + /** + * If true, the column will get a unique constraint. If a string is provided, the column will be part of a + * composite unique index. If multiple columns have the same string, they will be part of the same unique + * index + */ + unique?: boolean | string | { name: string; msg: string }; + + /** + * Primary key flag + */ + primaryKey?: boolean; + + /** + * Is this field an auto increment field + */ + autoIncrement?: boolean; + + /** + * If this field is a Postgres auto increment field, use Postgres `GENERATED BY DEFAULT AS IDENTITY` instead of `SERIAL`. Postgres 10+ only. + */ + autoIncrementIdentity?: boolean; + + /** + * Comment for the database + */ + comment?: string; + + /** + * An object with reference configurations or the column name as string + */ + references?: string | ModelAttributeColumnReferencesOptions; + + /** + * What should happen when the referenced key is updated. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or + * NO ACTION + */ + onUpdate?: string; + + /** + * What should happen when the referenced key is deleted. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or + * NO ACTION + */ + onDelete?: string; + + + /** + * An object of validations to execute for this column every time the model is saved. Can be either the + * name of a validation provided by validator.js, a validation function provided by extending validator.js + * (see the + * `DAOValidator` property for more details), or a custom validation function. Custom validation functions + * are called with the value of the field, and can possibly take a second callback argument, to signal that + * they are asynchronous. If the validator is sync, it should throw in the case of a failed validation, it + * it is async, the callback should be called with the error text. + */ + validate?: ModelValidateOptions; + + /** + * Usage in object notation + * + * ```js + * class MyModel extends Model {} + * MyModel.init({ + * states: { + * type: Sequelize.ENUM, + * values: ['active', 'pending', 'deleted'] + * } + * }, { sequelize }) + * ``` + */ + values?: readonly string[]; + + /** + * Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying + * values. + */ + get?(this: M): unknown; + + /** + * Provide a custom setter for this column. Use `this.setDataValue(String, Value)` to manipulate the + * underlying values. + */ + set?(this: M, val: unknown): void; +} + +/** + * Interface for Attributes provided for all columns in a model + */ +export type ModelAttributes = { + /** + * The description of a database column + */ + [name in keyof TAttributes]: DataType | ModelAttributeColumnOptions; +} + +/** + * Possible types for primary keys + */ +export type Identifier = number | string | Buffer; + +/** + * Options for model definition + */ +export interface ModelOptions { + /** + * Define the default search scope to use for this model. Scopes have the same form as the options passed to + * find / findAll. + */ + defaultScope?: FindOptions>; + + /** + * More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about + * how scopes are defined, and what you can do with them + */ + scopes?: ModelScopeOptions>; + + /** + * Don't persits null values. This means that all columns with null values will not be saved. + */ + omitNull?: boolean; + + /** + * Adds createdAt and updatedAt timestamps to the model. Default true. + */ + timestamps?: boolean; + + /** + * Calling destroy will not delete the model, but instead set a deletedAt timestamp if this is true. Needs + * timestamps=true to work. Default false. + */ + paranoid?: boolean; + + /** + * Converts all camelCased columns to underscored if true. Default false. + */ + underscored?: boolean; + + /** + * Indicates if the model's table has a trigger associated with it. Default false. + */ + hasTrigger?: boolean; + + /** + * If freezeTableName is true, sequelize will not try to alter the DAO name to get the table name. + * Otherwise, the dao name will be pluralized. Default false. + */ + freezeTableName?: boolean; + + /** + * An object with two attributes, `singular` and `plural`, which are used when this model is associated to + * others. + */ + name?: ModelNameOptions; + + /** + * Set name of the model. By default its same as Class name. + */ + modelName?: string; + + /** + * Indexes for the provided database table + */ + indexes?: readonly ModelIndexesOptions[]; + + /** + * Override the name of the createdAt column if a string is provided, or disable it if false. Timestamps + * must be true. Not affected by underscored setting. + */ + createdAt?: string | boolean; + + /** + * Override the name of the deletedAt column if a string is provided, or disable it if false. Timestamps + * must be true. Not affected by underscored setting. + */ + deletedAt?: string | boolean; + + /** + * Override the name of the updatedAt column if a string is provided, or disable it if false. Timestamps + * must be true. Not affected by underscored setting. + */ + updatedAt?: string | boolean; + + /** + * @default pluralized model name, unless freezeTableName is true, in which case it uses model name + * verbatim + */ + tableName?: string; + + schema?: string; + + /** + * You can also change the database engine, e.g. to MyISAM. InnoDB is the default. + */ + engine?: string; + + charset?: string; + + /** + * Finaly you can specify a comment for the table in MySQL and PG + */ + comment?: string; + + collate?: string; + + /** + * Set the initial AUTO_INCREMENT value for the table in MySQL. + */ + initialAutoIncrement?: string; + + /** + * An object of hook function that are called before and after certain lifecycle events. + * See Hooks for more information about hook + * functions and their signatures. Each property can either be a function, or an array of functions. + */ + hooks?: Partial>>; + + /** + * An object of model wide validations. Validations have access to all model values via `this`. If the + * validator function takes an argument, it is asumed to be async, and is called with a callback that + * accepts an optional error. + */ + validate?: ModelValidateOptions; + + /** + * Allows defining additional setters that will be available on model instances. + */ + setterMethods?: ModelSetterOptions; + + /** + * Allows defining additional getters that will be available on model instances. + */ + getterMethods?: ModelGetterOptions; + + /** + * Enable optimistic locking. + * When enabled, sequelize will add a version count attribute to the model and throw an + * OptimisticLockingError error when stale instances are saved. + * - If string: Uses the named attribute. + * - If boolean: Uses `version`. + * @default false + */ + version?: boolean | string; +} + +/** + * Options passed to [[Model.init]] + */ +export interface InitOptions extends ModelOptions { + /** + * The sequelize connection. Required ATM. + */ + sequelize: Sequelize; +} + +/** + * AddScope Options for Model.addScope + */ +export interface AddScopeOptions { + /** + * If a scope of the same name already exists, should it be overwritten? + */ + override: boolean; +} + +export abstract class Model + extends Hooks, TModelAttributes, TCreationAttributes> +{ + /** + * 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! + * + * Before using these, I'd tried typing out the functions without them, but + * Typescript fails to infer `TAttributes` in signatures like the below. + * + * ```ts + * public static findOne, TAttributes>( + * this: { new(): M }, + * options: NonNullFindOptions + * ): Promise; + * ``` + * + * @deprecated This property will become a Symbol in v7 to prevent collisions. + * Use Attributes instead of this property to be forward-compatible. + */ + _attributes: TModelAttributes; // TODO [>6]: make this a non-exported symbol (same as the one in hooks.d.ts) + + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + * + * @deprecated This property will become a Symbol in v7 to prevent collisions. + * Use CreationAttributes instead of this property to be forward-compatible. + */ + _creationAttributes: TCreationAttributes; // TODO [>6]: make this a non-exported symbol (same as the one in hooks.d.ts) + + /** The name of the database table */ + public static readonly tableName: string; + + /** + * The name of the primary key attribute + */ + public static readonly primaryKeyAttribute: string; + + /** + * The name of the primary key attributes + */ + public static readonly primaryKeyAttributes: readonly string[]; + + /** + * An object hash from alias to association object + */ + public static readonly associations: { + [key: string]: Association; + }; + + /** + * The options that the model was initialized with + */ + public static readonly options: InitOptions; + + /** + * The attributes of the model + */ + public static readonly rawAttributes: { [attribute: string]: ModelAttributeColumnOptions }; + + /** + * Returns the attributes of the model + */ + public static getAttributes(): { [attribute: string]: ModelAttributeColumnOptions }; + + /** + * Reference to the sequelize instance the model was initialized with + */ + public static readonly sequelize?: Sequelize; + + /** + * Initialize a model, representing a table in the DB, with attributes and options. + * + * The table columns are define by the hash that is given as the second argument. Each attribute of the hash represents a column. A short table definition might look like this: + * + * ```js + * Project.init({ + * columnA: { + * type: Sequelize.BOOLEAN, + * validate: { + * is: ['[a-z]','i'], // will only allow letters + * max: 23, // only allow values <= 23 + * isIn: { + * args: [['en', 'zh']], + * msg: "Must be English or Chinese" + * } + * }, + * field: 'column_a' + * // Other attributes here + * }, + * columnB: Sequelize.STRING, + * columnC: 'MY VERY OWN COLUMN TYPE' + * }, {sequelize}) + * + * sequelize.models.modelName // The model will now be available in models under the class name + * ``` + * + * As shown above, column definitions can be either strings, a reference to one of the datatypes that are predefined on the Sequelize constructor, or an object that allows you to specify both the type of the column, and other attributes such as default values, foreign key constraints and custom setters and getters. + * + * For a list of possible data types, see https://sequelize.org/master/en/latest/docs/models-definition/#data-types + * + * For more about getters and setters, see https://sequelize.org/master/en/latest/docs/models-definition/#getters-setters + * + * For more about instance and class methods, see https://sequelize.org/master/en/latest/docs/models-definition/#expansion-of-models + * + * For more about validation, see https://sequelize.org/master/en/latest/docs/models-definition/#validations + * + * @param attributes + * An object, where each attribute is a column of the table. Each column can be either a DataType, a + * string or a type-description object, with the properties described below: + * @param options These options are merged with the default define options provided to the Sequelize constructor + * @return Return the initialized model + */ + public static init, M extends InstanceType>( + this: MS, + attributes: ModelAttributes>, options: InitOptions + ): MS; + + /** + * Remove attribute from model definition + * + * @param attribute + */ + public static removeAttribute(attribute: string): void; + + /** + * Sync this Model to the DB, that is create the table. Upon success, the callback will be called with the + * model instance (this) + */ + public static sync(options?: SyncOptions): Promise; + + /** + * Drop the table represented by this Model + * + * @param options + */ + public static drop(options?: DropOptions): Promise; + + /** + * Apply a schema to this model. For postgres, this will actually place the schema in front of the table + * name + * - `"schema"."tableName"`, while the schema will be prepended to the table name for mysql and + * sqlite - `'schema.tablename'`. + * + * @param schema The name of the schema + * @param options + */ + public static schema( + this: ModelStatic, + schema: string, + options?: SchemaOptions + ): ModelCtor; + + /** + * Get the tablename of the model, taking schema into account. The method will return The name as a string + * if the model has no schema, or an object with `tableName`, `schema` and `delimiter` properties. + * + * @param options The hash of options from any query. You can use one model to access tables with matching + * schemas by overriding `getTableName` and using custom key/values to alter the name of the table. + * (eg. + * subscribers_1, subscribers_2) + */ + public static getTableName(): string | { + tableName: string; + schema: string; + delimiter: string; + }; + + /** + * Apply a scope created in `define` to the model. First let's look at how to create scopes: + * ```js + * class MyModel extends Model {} + * MyModel.init(attributes, { + * defaultScope: { + * where: { + * username: 'dan' + * }, + * limit: 12 + * }, + * scopes: { + * isALie: { + * where: { + * stuff: 'cake' + * } + * }, + * complexFunction(email, accessLevel) { + * return { + * where: { + * email: { + * [Op.like]: email + * }, + * accesss_level { + * [Op.gte]: accessLevel + * } + * } + * } + * } + * }, + * sequelize, + * }) + * ``` + * Now, since you defined a default scope, every time you do Model.find, the default scope is appended to + * your query. Here's a couple of examples: + * ```js + * Model.findAll() // WHERE username = 'dan' + * Model.findAll({ where: { age: { gt: 12 } } }) // WHERE age > 12 AND username = 'dan' + * ``` + * + * To invoke scope functions you can do: + * ```js + * Model.scope({ method: ['complexFunction' 'dan@sequelize.com', 42]}).findAll() + * // WHERE email like 'dan@sequelize.com%' AND access_level >= 42 + * ``` + * + * @return Model A reference to the model, with the scope(s) applied. Calling scope again on the returned + * model will clear the previous scope. + */ + public static scope( + this: ModelStatic, + options?: string | ScopeOptions | readonly (string | ScopeOptions)[] | WhereAttributeHash + ): ModelCtor; + + /** + * Add a new scope to the model + * + * This is especially useful for adding scopes with includes, when the model you want to + * include is not available at the time this model is defined. By default this will throw an + * error if a scope with that name already exists. Pass `override: true` in the options + * object to silence this error. + */ + public static addScope( + this: ModelStatic, + name: string, + scope: FindOptions>, + options?: AddScopeOptions + ): void; + public static addScope( + this: ModelStatic, + name: string, + scope: (...args: readonly any[]) => FindOptions>, + options?: AddScopeOptions + ): void; + + /** + * Search for multiple instances. + * + * __Simple search using AND and =__ + * ```js + * Model.findAll({ + * where: { + * attr1: 42, + * attr2: 'cake' + * } + * }) + * ``` + * ```sql + * WHERE attr1 = 42 AND attr2 = 'cake' + * ``` + * + * __Using greater than, less than etc.__ + * ```js + * + * Model.findAll({ + * where: { + * attr1: { + * gt: 50 + * }, + * attr2: { + * lte: 45 + * }, + * attr3: { + * in: [1,2,3] + * }, + * attr4: { + * ne: 5 + * } + * } + * }) + * ``` + * ```sql + * WHERE attr1 > 50 AND attr2 <= 45 AND attr3 IN (1,2,3) AND attr4 != 5 + * ``` + * Possible options are: `[Op.ne], [Op.in], [Op.not], [Op.notIn], [Op.gte], [Op.gt], [Op.lte], [Op.lt], [Op.like], [Op.ilike]/[Op.iLike], [Op.notLike], + * [Op.notILike], '..'/[Op.between], '!..'/[Op.notBetween], '&&'/[Op.overlap], '@>'/[Op.contains], '<@'/[Op.contained]` + * + * __Queries using OR__ + * ```js + * Model.findAll({ + * where: Sequelize.and( + * { name: 'a project' }, + * Sequelize.or( + * { id: [1,2,3] }, + * { id: { gt: 10 } } + * ) + * ) + * }) + * ``` + * ```sql + * WHERE name = 'a project' AND (id` IN (1,2,3) OR id > 10) + * ``` + * + * The success listener is called with an array of instances if the query succeeds. + * + * @see {Sequelize#query} + */ + public static findAll( + this: ModelStatic, + options?: FindOptions>): Promise; + + /** + * Search for a single instance by its primary key. This applies LIMIT 1, so the listener will + * always be called with a single instance. + */ + public static findByPk( + this: ModelStatic, + identifier: Identifier, + options: Omit>, 'where'> + ): Promise; + public static findByPk( + this: ModelStatic, + identifier?: Identifier, + options?: Omit>, 'where'> + ): Promise; + + /** + * Search for a single instance. Returns the first instance found, or null if none can be found. + */ + public static findOne( + this: ModelStatic, + options: NonNullFindOptions> + ): Promise; + public static findOne( + this: ModelStatic, + options?: FindOptions> + ): Promise; + + /** + * Run an aggregation method on the specified field + * + * @param field The field to aggregate over. Can be a field name or * + * @param aggregateFunction The function to use for aggregation, e.g. sum, max etc. + * @param options Query options. See sequelize.query for full options + * @return Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in + * which case the complete data result is returned. + */ + public static aggregate( + this: ModelStatic, + field: keyof Attributes | '*', + aggregateFunction: string, + options?: AggregateOptions> + ): Promise; + + /** + * Count number of records if group by is used + * @return Returns count for each group and the projected attributes. + */ + public static count( + this: ModelStatic, + options: CountWithOptions> + ): Promise; + + /** + * Count the number of records matching the provided where clause. + * + * If you provide an `include` option, the number of matching associations will be counted instead. + * @return Returns count for each group and the projected attributes. + */ + public static count( + this: ModelStatic, + options?: Omit>, 'group'> + ): Promise; + + /** + * Find all the rows matching your query, within a specified offset / limit, and get the total number of + * rows matching your query. This is very useful for paging + * + * ```js + * Model.findAndCountAll({ + * where: ..., + * limit: 12, + * offset: 12 + * }).then(result => { + * ... + * }) + * ``` + * In the above example, `result.rows` will contain rows 13 through 24, while `result.count` will return + * the + * total number of rows that matched your query. + * + * When you add includes, only those which are required (either because they have a where clause, or + * because + * `required` is explicitly set to true on the include) will be added to the count part. + * + * Suppose you want to find all users who have a profile attached: + * ```js + * User.findAndCountAll({ + * include: [ + * { model: Profile, required: true} + * ], + * limit: 3 + * }); + * ``` + * Because the include for `Profile` has `required` set it will result in an inner join, and only the users + * who have a profile will be counted. If we remove `required` from the include, both users with and + * without + * profiles will be counted + * + * This function also support grouping, when `group` is provided, the count will be an array of objects + * containing the count for each group and the projected attributes. + * ```js + * User.findAndCountAll({ + * group: 'type' + * }); + * ``` + */ + public static findAndCountAll( + this: ModelStatic, + options?: Omit>, 'group'> + ): Promise<{ rows: M[]; count: number }>; + public static findAndCountAll( + this: ModelStatic, + options: SetRequired>, 'group'> + ): Promise<{ rows: M[]; count: GroupedCountResultItem[] }>; + + /** + * Find the maximum value of field + */ + public static max( + this: ModelStatic, + field: keyof Attributes, + options?: AggregateOptions> + ): Promise; + + /** + * Find the minimum value of field + */ + public static min( + this: ModelStatic, + field: keyof Attributes, + options?: AggregateOptions> + ): Promise; + + /** + * Find the sum of field + */ + public static sum( + this: ModelStatic, + field: keyof Attributes, + options?: AggregateOptions> + ): Promise; + + /** + * Builds a new model instance. Values is an object of key value pairs, must be defined but can be empty. + */ + public static build( + this: ModelStatic, + record?: CreationAttributes, + options?: BuildOptions + ): M; + + /** + * Undocumented bulkBuild + */ + public static bulkBuild( + this: ModelStatic, + records: ReadonlyArray>, + options?: BuildOptions + ): M[]; + + /** + * Builds a new model instance and calls save on it. + */ + public static create< + M extends Model, + O extends CreateOptions> = CreateOptions> + >( + this: ModelStatic, + values?: CreationAttributes, + options?: O + ): Promise; + + /** + * Find a row that matches the query, or build (but don't save) the row if none is found. + * The successful result of the promise will be (instance, initialized) - Make sure to use `.then(([...]))` + */ + public static findOrBuild( + this: ModelStatic, + options: FindOrBuildOptions< + Attributes, + CreationAttributes + > + ): Promise<[M, boolean]>; + + /** + * Find a row that matches the query, or build and save the row if none is found + * The successful result of the promise will be (instance, created) - Make sure to use `.then(([...]))` + * + * If no transaction is passed in the `options` object, a new transaction will be created internally, to + * prevent the race condition where a matching row is created by another connection after the find but + * before the insert call. However, it is not always possible to handle this case in SQLite, specifically + * if one transaction inserts and another tries to select before the first one has comitted. In this case, + * an instance of sequelize.TimeoutError will be thrown instead. If a transaction is created, a savepoint + * will be created instead, and any unique constraint violation will be handled internally. + */ + public static findOrCreate( + this: ModelStatic, + options: FindOrCreateOptions, CreationAttributes> + ): Promise<[M, boolean]>; + + /** + * A more performant findOrCreate that will not work under a transaction (at least not in postgres) + * Will execute a find call, if empty then attempt to create, if unique constraint then attempt to find again + */ + public static findCreateFind( + this: ModelStatic, + options: FindOrCreateOptions, CreationAttributes> + ): Promise<[M, boolean]>; + + /** + * Insert or update a single row. An update will be executed if a row which matches the supplied values on + * either the primary key or a unique key is found. Note that the unique index must be defined in your + * sequelize model and not just in the table. Otherwise you may experience a unique constraint violation, + * because sequelize fails to identify the row that should be updated. + * + * **Implementation details:** + * + * * MySQL - Implemented as a single query `INSERT values ON DUPLICATE KEY UPDATE values` + * * PostgreSQL - Implemented as a temporary function with exception handling: INSERT EXCEPTION WHEN + * unique_constraint UPDATE + * * SQLite - Implemented as two queries `INSERT; UPDATE`. This means that the update is executed + * regardless + * of whether the row already existed or not + * + * **Note** that SQLite returns null for created, no matter if the row was created or updated. This is + * because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know + * whether the row was inserted or not. + */ + public static upsert( + this: ModelStatic, + values: CreationAttributes, + options?: UpsertOptions> + ): Promise<[M, boolean | null]>; + + /** + * Create and insert multiple instances in bulk. + * + * The success handler is passed an array of instances, but please notice that these may not completely + * represent the state of the rows in the DB. This is because MySQL and SQLite do not make it easy to + * obtain + * back automatically generated IDs and other default values in a way that can be mapped to multiple + * records. To obtain Instances for the newly created values, you will need to query for them again. + * + * @param records List of objects (key/value pairs) to create instances from + */ + public static bulkCreate( + this: ModelStatic, + records: ReadonlyArray>, + options?: BulkCreateOptions> + ): Promise; + + /** + * Truncate all instances of the model. This is a convenient method for Model.destroy({ truncate: true }). + */ + public static truncate( + this: ModelStatic, + options?: TruncateOptions> + ): Promise; + + /** + * Delete multiple instances, or set their deletedAt timestamp to the current time if `paranoid` is enabled. + * + * @return Promise The number of destroyed rows + */ + public static destroy( + this: ModelStatic, + options?: DestroyOptions> + ): Promise; + + /** + * Restore multiple instances if `paranoid` is enabled. + */ + public static restore( + this: ModelStatic, + options?: RestoreOptions> + ): Promise; + + /** + * Update multiple instances that match the where options. The promise returns an array with one or two + * elements. The first element is always the number of affected rows, while the second element is the actual + * affected rows (only supported in postgres and mssql with `options.returning` true.) + */ + public static update( + this: ModelStatic, + values: { + [key in keyof Attributes]?: Attributes[key] | Fn | Col | Literal; + }, + options: UpdateOptions> + ): Promise<[number, M[]]>; + + /** + * Increments a single field. + */ + public static increment( + this: ModelStatic, + field: keyof Attributes, + options: IncrementDecrementOptionsWithBy> + ): Promise; + + /** + * Increments multiple fields by the same value. + */ + public static increment( + this: ModelStatic, + fields: ReadonlyArray>, + options: IncrementDecrementOptionsWithBy> + ): Promise; + + /** + * Increments multiple fields by different values. + */ + public static increment( + this: ModelStatic, + fields: { [key in keyof Attributes]?: number }, + options: IncrementDecrementOptions> + ): Promise; + + /** + * Decrements a single field. + */ + public static decrement( + this: ModelStatic, + field: keyof Attributes, + options: IncrementDecrementOptionsWithBy> + ): Promise; + + /** + * Decrements multiple fields by the same value. + */ + public static decrement( + this: ModelStatic, + fields: (keyof Attributes)[], + options: IncrementDecrementOptionsWithBy> + ): Promise; + + /** + * Decrements multiple fields by different values. + */ + public static decrement( + this: ModelStatic, + fields: { [key in keyof Attributes]?: number }, + options: IncrementDecrementOptions> + ): Promise; + + /** + * Run a describe query on the table. The result will be return to the listener as a hash of attributes and + * their types. + */ + public static describe(): Promise; + + /** + * Unscope the model + */ + public static unscoped(this: M): M; + + /** + * A hook that is run before validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeValidate( + this: ModelStatic, + name: string, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + public static beforeValidate( + this: ModelStatic, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + + /** + * A hook that is run after validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterValidate( + this: ModelStatic, + name: string, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + public static afterValidate( + this: ModelStatic, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + + /** + * A hook that is run before creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public static beforeCreate( + this: ModelStatic, + name: string, + fn: (instance: M, options: CreateOptions>) => HookReturn + ): void; + public static beforeCreate( + this: ModelStatic, + fn: (instance: M, options: CreateOptions>) => HookReturn + ): void; + + /** + * A hook that is run after creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public static afterCreate( + this: ModelStatic, + name: string, + fn: (instance: M, options: CreateOptions>) => HookReturn + ): void; + public static afterCreate( + this: ModelStatic, + fn: (instance: M, options: CreateOptions>) => HookReturn + ): void; + + /** + * A hook that is run before destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeDestroy( + this: ModelStatic, + name: string, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + public static beforeDestroy( + this: ModelStatic, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + + /** + * A hook that is run after destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterDestroy( + this: ModelStatic, + name: string, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + public static afterDestroy( + this: ModelStatic, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + + /** + * A hook that is run before updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeUpdate( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions>) => HookReturn + ): void; + public static beforeUpdate( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions>) => HookReturn + ): void; + + /** + * A hook that is run after updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterUpdate( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions>) => HookReturn + ): void; + public static afterUpdate( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions>) => HookReturn + ): void; + + /** + * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeSave( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions> | SaveOptions>) => HookReturn + ): void; + public static beforeSave( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions> | SaveOptions>) => HookReturn + ): void; + + /** + * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterSave( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions> | SaveOptions>) => HookReturn + ): void; + public static afterSave( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions> | SaveOptions>) => HookReturn + ): void; + + /** + * A hook that is run before creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public static beforeBulkCreate( + this: ModelStatic, + name: string, + fn: (instances: M[], options: BulkCreateOptions>) => HookReturn + ): void; + public static beforeBulkCreate( + this: ModelStatic, + fn: (instances: M[], options: BulkCreateOptions>) => HookReturn + ): void; + + /** + * A hook that is run after creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public static afterBulkCreate( + this: ModelStatic, + name: string, + fn: (instances: readonly M[], options: BulkCreateOptions>) => HookReturn + ): void; + public static afterBulkCreate( + this: ModelStatic, + fn: (instances: readonly M[], options: BulkCreateOptions>) => HookReturn + ): void; + + /** + * A hook that is run before destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeBulkDestroy( + this: ModelStatic, + name: string, fn: (options: BulkCreateOptions>) => HookReturn): void; + public static beforeBulkDestroy( + this: ModelStatic, + fn: (options: BulkCreateOptions>) => HookReturn + ): void; + + /** + * A hook that is run after destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterBulkDestroy( + this: ModelStatic, + name: string, fn: (options: DestroyOptions>) => HookReturn + ): void; + public static afterBulkDestroy( + this: ModelStatic, + fn: (options: DestroyOptions>) => HookReturn + ): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeBulkUpdate( + this: ModelStatic, + name: string, fn: (options: UpdateOptions>) => HookReturn + ): void; + public static beforeBulkUpdate( + this: ModelStatic, + fn: (options: UpdateOptions>) => HookReturn + ): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterBulkUpdate( + this: ModelStatic, + name: string, fn: (options: UpdateOptions>) => HookReturn + ): void; + public static afterBulkUpdate( + this: ModelStatic, + fn: (options: UpdateOptions>) => HookReturn + ): void; + + /** + * A hook that is run before a find (select) query + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFind( + this: ModelStatic, + name: string, fn: (options: FindOptions>) => HookReturn + ): void; + public static beforeFind( + this: ModelStatic, + fn: (options: FindOptions>) => HookReturn + ): void; + + /** + * A hook that is run before a count query + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeCount( + this: ModelStatic, + name: string, fn: (options: CountOptions>) => HookReturn + ): void; + public static beforeCount( + this: ModelStatic, + fn: (options: CountOptions>) => HookReturn + ): void; + + /** + * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFindAfterExpandIncludeAll( + this: ModelStatic, + name: string, fn: (options: FindOptions>) => HookReturn + ): void; + public static beforeFindAfterExpandIncludeAll( + this: ModelStatic, + fn: (options: FindOptions>) => HookReturn + ): void; + + /** + * A hook that is run before a find (select) query, after all option parsing is complete + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFindAfterOptions( + this: ModelStatic, + name: string, fn: (options: FindOptions>) => HookReturn + ): void; + public static beforeFindAfterOptions( + this: ModelStatic, + fn: (options: FindOptions>) => void + ): HookReturn; + + /** + * A hook that is run after a find (select) query + * + * @param name + * @param fn A callback function that is called with instance(s), options + */ + public static afterFind( + this: ModelStatic, + name: string, + fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions>) => HookReturn + ): void; + public static afterFind( + this: ModelStatic, + fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions>) => HookReturn + ): void; + + /** + * A hook that is run before sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public static beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run before Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static beforeSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static afterSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * Creates an association between this (the source) and the provided target. The foreign key is added + * on the target. + * + * Example: `User.hasOne(Profile)`. This will add userId to the profile table. + * + * @param target The model that will be associated with hasOne relationship + * @param options Options for the association + */ + public static hasOne( + this: ModelStatic, target: ModelStatic, options?: HasOneOptions + ): HasOne; + + /** + * Creates an association between this (the source) and the provided target. The foreign key is added on the + * source. + * + * Example: `Profile.belongsTo(User)`. This will add userId to the profile table. + * + * @param target The model that will be associated with hasOne relationship + * @param options Options for the association + */ + public static belongsTo( + this: ModelStatic, target: ModelStatic, options?: BelongsToOptions + ): BelongsTo; + + /** + * Create an association that is either 1:m or n:m. + * + * ```js + * // Create a 1:m association between user and project + * User.hasMany(Project) + * ``` + * ```js + * // Create a n:m association between user and project + * User.hasMany(Project) + * Project.hasMany(User) + * ``` + * By default, the name of the join table will be source+target, so in this case projectsusers. This can be + * overridden by providing either a string or a Model as `through` in the options. If you use a through + * model with custom attributes, these attributes can be set when adding / setting new associations in two + * ways. Consider users and projects from before with a join table that stores whether the project has been + * started yet: + * ```js + * class UserProjects extends Model {} + * UserProjects.init({ + * started: Sequelize.BOOLEAN + * }, { sequelize }) + * User.hasMany(Project, { through: UserProjects }) + * Project.hasMany(User, { through: UserProjects }) + * ``` + * ```js + * jan.addProject(homework, { started: false }) // The homework project is not started yet + * jan.setProjects([makedinner, doshopping], { started: true}) // Both shopping and dinner have been + * started + * ``` + * + * If you want to set several target instances, but with different attributes you have to set the + * attributes on the instance, using a property with the name of the through model: + * + * ```js + * p1.userprojects { + * started: true + * } + * user.setProjects([p1, p2], {started: false}) // The default value is false, but p1 overrides that. + * ``` + * + * Similarily, when fetching through a join table with custom attributes, these attributes will be + * available as an object with the name of the through model. + * ```js + * user.getProjects().then(projects => { + * const p1 = projects[0] + * p1.userprojects.started // Is this project started yet? + * }) + * ``` + * + * @param target The model that will be associated with hasOne relationship + * @param options Options for the association + */ + public static hasMany( + this: ModelStatic, target: ModelStatic, options?: HasManyOptions + ): HasMany; + + /** + * Create an N:M association with a join table + * + * ```js + * User.belongsToMany(Project) + * Project.belongsToMany(User) + * ``` + * By default, the name of the join table will be source+target, so in this case projectsusers. This can be + * overridden by providing either a string or a Model as `through` in the options. + * + * If you use a through model with custom attributes, these attributes can be set when adding / setting new + * associations in two ways. Consider users and projects from before with a join table that stores whether + * the project has been started yet: + * ```js + * class UserProjects extends Model {} + * UserProjects.init({ + * started: Sequelize.BOOLEAN + * }, { sequelize }); + * User.belongsToMany(Project, { through: UserProjects }) + * Project.belongsToMany(User, { through: UserProjects }) + * ``` + * ```js + * jan.addProject(homework, { started: false }) // The homework project is not started yet + * jan.setProjects([makedinner, doshopping], { started: true}) // Both shopping and dinner has been started + * ``` + * + * If you want to set several target instances, but with different attributes you have to set the + * attributes on the instance, using a property with the name of the through model: + * + * ```js + * p1.userprojects { + * started: true + * } + * user.setProjects([p1, p2], {started: false}) // The default value is false, but p1 overrides that. + * ``` + * + * Similarily, when fetching through a join table with custom attributes, these attributes will be + * available as an object with the name of the through model. + * ```js + * user.getProjects().then(projects => { + * const p1 = projects[0] + * p1.userprojects.started // Is this project started yet? + * }) + * ``` + * + * @param target The model that will be associated with hasOne relationship + * @param options Options for the association + * + */ + public static belongsToMany( + this: ModelStatic, target: ModelStatic, options: BelongsToManyOptions + ): BelongsToMany; + + /** + * Returns true if this instance has not yet been persisted to the database + */ + public isNewRecord: boolean; + + /** + * A reference to the sequelize instance + */ + public sequelize: Sequelize; + + /** + * Builds a new model instance. + * @param values an object of key value pairs + */ + constructor(values?: MakeUndefinedOptional, options?: BuildOptions); + + /** + * Get an object representing the query for this instance, use with `options.where` + */ + public where(): object; + + /** + * Get the value of the underlying data value + */ + public getDataValue(key: K): TModelAttributes[K]; + + /** + * Update the underlying data value + */ + public setDataValue(key: K, value: TModelAttributes[K]): void; + + /** + * If no key is given, returns all values of the instance, also invoking virtual getters. + * + * If key is given and a field or virtual getter is present for the key it will call that getter - else it + * will return the value for key. + * + * @param options.plain If set to true, included instances will be returned as plain objects + */ + public get(options?: { plain?: boolean; clone?: boolean }): TModelAttributes; + public get(key: K, options?: { plain?: boolean; clone?: boolean }): this[K]; + public get(key: string, options?: { plain?: boolean; clone?: boolean }): unknown; + + /** + * Set is used to update values on the instance (the sequelize representation of the instance that is, + * remember that nothing will be persisted before you actually call `save`). In its most basic form `set` + * will update a value stored in the underlying `dataValues` object. However, if a custom setter function + * is defined for the key, that function will be called instead. To bypass the setter, you can pass `raw: + * true` in the options object. + * + * If set is called with an object, it will loop over the object, and call set recursively for each key, + * value pair. If you set raw to true, the underlying dataValues will either be set directly to the object + * passed, or used to extend dataValues, if dataValues already contain values. + * + * When set is called, the previous value of the field is stored and sets a changed flag(see `changed`). + * + * Set can also be used to build instances for associations, if you have values for those. + * When using set with associations you need to make sure the property key matches the alias of the + * association while also making sure that the proper include options have been set (from .build() or + * .findOne()) + * + * If called with a dot.seperated key on a JSON/JSONB attribute it will set the value nested and flag the + * entire object as changed. + * + * @param options.raw If set to true, field and virtual setters will be ignored + * @param options.reset Clear all previously set data values + */ + public set(key: K, value: TModelAttributes[K], options?: SetOptions): this; + public set(keys: Partial, options?: SetOptions): this; + public setAttributes(key: K, value: TModelAttributes[K], options?: SetOptions): this; + public setAttributes(keys: Partial, options?: SetOptions): this; + + /** + * If changed is called with a string it will return a boolean indicating whether the value of that key in + * `dataValues` is different from the value in `_previousDataValues`. + * + * If changed is called without an argument, it will return an array of keys that have changed. + * + * If changed is called with two arguments, it will set the property to `dirty`. + * + * If changed is called without an argument and no keys have changed, it will return `false`. + */ + public changed(key: K): boolean; + public changed(key: K, dirty: boolean): void; + public changed(): false | string[]; + + /** + * Returns the previous value for key from `_previousDataValues`. + */ + public previous(): Partial; + public previous(key: K): TModelAttributes[K] | undefined; + + /** + * Validates this instance, and if the validation passes, persists it to the database. + * + * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). + * + * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. + * + * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. + */ + public save(options?: SaveOptions): Promise; + + /** + * Refresh the current instance in-place, i.e. update the object with current data from the DB and return + * the same object. This is different from doing a `find(Instance.id)`, because that would create and + * return a new instance. With this method, all references to the Instance are updated with the new data + * and no new objects are created. + */ + public reload(options?: FindOptions): Promise; + + /** + * Validate the attribute of this instance according to validation rules set in the model definition. + * + * Emits null if and only if validation successful; otherwise an Error instance containing + * { field name : [error msgs] } entries. + * + * @param options.skip An array of strings. All properties that are in this array will not be validated + */ + public validate(options?: ValidationOptions): Promise; + + /** + * This is the same as calling `set` and then calling `save`. + */ + public update(key: K, value: TModelAttributes[K] | Col | Fn | Literal, options?: InstanceUpdateOptions): Promise; + public update( + keys: { + [key in keyof TModelAttributes]?: TModelAttributes[key] | Fn | Col | Literal; + }, + options?: InstanceUpdateOptions + ): Promise; + + /** + * Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will + * either be completely deleted, or have its deletedAt timestamp set to the current time. + */ + public destroy(options?: InstanceDestroyOptions): Promise; + + /** + * Restore the row corresponding to this instance. Only available for paranoid models. + */ + public restore(options?: InstanceRestoreOptions): Promise; + + /** + * Increment the value of one or more columns. This is done in the database, which means it does not use + * the values currently stored on the Instance. The increment is done using a + * ```sql + * SET column = column + X + * ``` + * query. To get the correct value after an increment into the Instance you should do a reload. + * + * ```js + * instance.increment('number') // increment number by 1 + * instance.increment(['number', 'count'], { by: 2 }) // increment number and count by 2 + * instance.increment({ answer: 42, tries: 1}, { by: 2 }) // increment answer by 42, and tries by 1. + * // `by` is ignored, since each column has its own + * // value + * ``` + * + * @param fields If a string is provided, that column is incremented by the value of `by` given in options. + * If an array is provided, the same is true for each column. + * If and object is provided, each column is incremented by the value given. + */ + public increment( + fields: K | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy + ): Promise; + + /** + * Decrement the value of one or more columns. This is done in the database, which means it does not use + * the values currently stored on the Instance. The decrement is done using a + * ```sql + * SET column = column - X + * ``` + * query. To get the correct value after an decrement into the Instance you should do a reload. + * + * ```js + * instance.decrement('number') // decrement number by 1 + * instance.decrement(['number', 'count'], { by: 2 }) // decrement number and count by 2 + * instance.decrement({ answer: 42, tries: 1}, { by: 2 }) // decrement answer by 42, and tries by 1. + * // `by` is ignored, since each column has its own + * // value + * ``` + * + * @param fields If a string is provided, that column is decremented by the value of `by` given in options. + * If an array is provided, the same is true for each column. + * If and object is provided, each column is decremented by the value given + */ + public decrement( + fields: K | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy + ): Promise; + + /** + * Check whether all values of this and `other` Instance are the same + */ + public equals(other: this): boolean; + + /** + * Check if this is equal to one of `others` by calling equals + */ + public equalsOneOf(others: readonly this[]): boolean; + + /** + * Convert the instance to a JSON representation. Proxies to calling `get` with no keys. This means get all + * values gotten from the DB, and apply all custom getters. + */ + public toJSON(): T; + public toJSON(): object; + + /** + * Helper method to determine if a instance is "soft deleted". This is + * particularly useful if the implementer renamed the deletedAt attribute to + * something different. This method requires paranoid to be enabled. + * + * Throws an error if paranoid is not enabled. + */ + public isSoftDeleted(): boolean; +} + +/** @deprecated use ModelStatic */ +export type ModelType = new () => Model; + +type NonConstructorKeys = ({[P in keyof T]: T[P] extends new () => any ? never : P })[keyof T]; +type NonConstructor = Pick>; + +/** @deprecated use ModelStatic */ +export type ModelCtor = ModelStatic; + +export type ModelDefined = ModelStatic>; + +// remove the existing constructor that tries to return `Model<{},{}>` which would be incompatible with models that have typing defined & replace with proper constructor. +export type ModelStatic = NonConstructor & { new(): M }; + +export default Model; + +/** + * Dummy Symbol used as branding by {@link NonAttribute}. + * + * Do not export, Do not use. + */ +declare const NonAttributeBrand: unique symbol; +interface NonAttributeBrandedArray extends Array { + [NonAttributeBrand]: true +} + +/** + * This is a Branded Type. + * You can use it to tag fields from your class that are NOT attributes. + * They will be ignored by {@link InferAttributes} and {@link InferCreationAttributes} + */ +export type NonAttribute = T extends Array + // Arrays are a special case when branding. Both sides need to be an array, + // otherwise property access breaks. + ? T | NonAttributeBrandedArray + : T | { [NonAttributeBrand]: true }; // this MUST be a union or nothing will be assignable to this type. + +/** + * Option bag for {@link InferAttributes}. + * + * - omit: properties to not treat as Attributes. + */ +type InferAttributesOptions = { omit?: Excluded }; + +/** + * Utility type to extract Attributes of a given Model class. + * + * It returns all instance properties defined in the Model, except: + * - those inherited from Model (intermediate inheritance works), + * - the ones whose type is a function, + * - the ones manually excluded using the second parameter. + * - the ones branded using {@link NonAttribute} + * + * It cannot detect whether something is a getter or not, you should use the `Excluded` + * parameter to exclude getter & setters from the attribute list. + * + * @example + * // listed attributes will be 'id' & 'firstName'. + * class User extends Model> { + * id: number; + * firstName: string; + * } + * + * @example + * // listed attributes will be 'id' & 'firstName'. + * // we're excluding the `name` getter & `projects` attribute using the `omit` option. + * class User extends Model> { + * id: number; + * firstName: string; + * + * // this is a getter, not an attribute. It should not be listed in attributes. + * get name(): string { return this.firstName; } + * // this is an association, it should not be listed in attributes + * projects?: Project[]; + * } + * + * @example + * // listed attributes will be 'id' & 'firstName'. + * // we're excluding the `name` getter & `test` attribute using the `NonAttribute` branded type. + * class User extends Model> { + * id: number; + * firstName: string; + * + * // this is a getter, not an attribute. It should not be listed in attributes. + * get name(): NonAttribute { return this.firstName; } + * // this is an association, it should not be listed in attributes + * projects?: NonAttribute; + * } + */ +export type InferAttributes< + M extends Model, + Options extends InferAttributesOptions = { omit: never } +> = { + [Key in keyof M as + M[Key] extends AnyFunction ? never + : { [NonAttributeBrand]: true } extends M[Key] ? never + // array special case + : M[Key] extends NonAttributeBrandedArray ? never + : Key extends keyof Model ? never + // check 'omit' option is provided + : Options['omit'] extends string ? (Key extends Options['omit'] ? never : Key) + : Key + ]: M[Key] +}; + +/** + * Dummy Symbol used as branding by {@link CreationOptional}. + * + * Do not export, Do not use. + */ +declare const CreationAttributeBrand: unique symbol; +interface CreationAttributeBrandedArray extends Array { + [CreationAttributeBrand]: true +} + +/** + * This is a Branded Type. + * You can use it to tag attributes that can be ommited during Model Creation. + * + * For use with {@link InferCreationAttributes}. + */ +export type CreationOptional = T extends Array + // Arrays are a special case when branding. Both sides need to be an array, + // otherwise property access breaks. + ? T | CreationAttributeBrandedArray + : T | { [CreationAttributeBrand]: true }; // this MUST be a union or nothing will be assignable to this type. + +/** + * Utility type to extract Creation Attributes of a given Model class. + * + * Works like {@link InferAttributes}, but fields that are tagged using + * {@link CreationOptional} will be optional. + * + * @example + * class User extends Model, InferCreationAttributes> { + * // this attribute is optional in Model#create + * declare id: CreationOptional; + * + * // this attribute is mandatory in Model#create + * declare name: string; + * } + */ +export type InferCreationAttributes< + M extends Model, + Options extends InferAttributesOptions = { omit: never } +> = { + [Key in keyof M as + M[Key] extends AnyFunction ? never + : { [NonAttributeBrand]: true } extends M[Key] ? never + // array special case + : M[Key] extends NonAttributeBrandedArray ? never + : Key extends keyof Model ? never + // check 'omit' option is provided + : Options['omit'] extends string ? (Key extends Options['omit'] ? never : Key) + : Key + // it is normal that the brand extends the type. + // We're checking if it's in the type. + ]: { [CreationAttributeBrand]: true } extends M[Key] ? (M[Key] | undefined) + // array special case + : CreationAttributeBrandedArray extends M[Key] ? (M[Key] | undefined) + : M[Key] +}; + +// in v7, we should be able to drop InferCreationAttributes and InferAttributes, +// resolving this confusion. +/** + * Returns the creation attributes of a given Model. + * + * This returns the Creation Attributes of a Model, it does not build them. + * If you need to build them, use {@link InferCreationAttributes}. + * + * @example + * function buildModel(modelClass: ModelStatic, attributes: CreationAttributes) {} + */ +export type CreationAttributes = MakeUndefinedOptional; + +/** + * Returns the creation attributes of a given Model. + * + * This returns the Attributes of a Model that have already been defined, it does not build them. + * If you need to build them, use {@link InferAttributes}. + * + * @example + * function getValue(modelClass: ModelStatic, attribute: keyof Attributes) {} + */ +export type Attributes = M['_attributes']; diff --git a/types/lib/utils.d.ts b/types/lib/utils.d.ts new file mode 100644 index 000000000000..de1ee23dca2e --- /dev/null +++ b/types/lib/utils.d.ts @@ -0,0 +1,166 @@ +import { DataType } from './data-types'; +import { Model, ModelCtor, ModelType, WhereOptions, Attributes } from './model'; +import { Optional } from '..'; + +export type Primitive = 'string' | 'number' | 'boolean'; + +export type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + +export interface Inflector { + singularize(str: string): string; + pluralize(str: string): string; +} + +export function useInflection(inflection: Inflector): void; + +export function camelizeIf(string: string, condition?: boolean): string; +export function underscoredIf(string: string, condition?: boolean): string; +export function isPrimitive(val: unknown): val is Primitive; + +/** Same concept as _.merge, but don't overwrite properties that have already been assigned */ +export function mergeDefaults(a: T, b: Partial): T; +export function spliceStr(str: string, index: number, count: number, add: string): string; +export function camelize(str: string): string; +export function format(arr: string[], dialect: string): string; +export function formatNamedParameters(sql: string, parameters: { + [key: string]: string | number | boolean; +}, dialect: string): string; +export function cloneDeep(obj: T, fn?: (el: unknown) => unknown): T; + +export interface OptionsForMapping { + attributes?: string[]; + where?: WhereOptions; +} + +/** Expand and normalize finder options */ +export function mapFinderOptions>>( + options: T, + model: ModelCtor +): T; + +/* Used to map field names in attributes and where conditions */ +export function mapOptionFieldNames>>( + options: T, model: ModelCtor +): T; + +export function mapWhereFieldNames(attributes: object, model: ModelType): object; +/** Used to map field names in values */ +export function mapValueFieldNames(dataValues: object, fields: string[], model: ModelType): object; + +export function isColString(value: string): boolean; +export function canTreatArrayAsAnd(arr: unknown[]): boolean; +export function combineTableNames(tableName1: string, tableName2: string): string; + +export function singularize(s: string): string; +export function pluralize(s: string): string; + +export function toDefaultValue(value: unknown): unknown; + +/** + * Determine if the default value provided exists and can be described + * in a db schema using the DEFAULT directive. + * + * @param value Any default value. + */ +export function defaultValueSchemable(hash: DataType): boolean; +export function stack(): NodeJS.CallSite[]; +export function now(dialect: string): Date; + +// Note: Use the `quoteIdentifier()` and `escape()` methods on the +// `QueryInterface` instead for more portable code. +export const TICK_CHAR: string; +export function addTicks(s: string, tickChar?: string): string; +export function removeTicks(s: string, tickChar?: string): string; + +/** + * Wraps a constructor to not need the `new` keyword using a proxy. + * Only used for data types. + */ +export function classToInvokable any>(ctor: T): T & { + (...args: ConstructorParameters): T; +} + +export class SequelizeMethod { + +} + +/* + * Utility functions for representing SQL functions, and columns that should be escaped. + * Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead. + */ +export class Fn extends SequelizeMethod { + constructor(fn: string, args: unknown[]); + public clone(): this; +} + +export class Col extends SequelizeMethod { + public col: string; + constructor(col: string); +} + +export class Cast extends SequelizeMethod { + public val: unknown; + public type: string; + constructor(val: unknown, type?: string); +} + +export class Literal extends SequelizeMethod { + public val: unknown; + constructor(val: unknown); +} + +export class Json extends SequelizeMethod { + public conditions: object; + public path: string; + public value: string | number | boolean; + constructor(conditionsOrPath: string | object, value?: string | number | boolean); +} + +export class Where extends SequelizeMethod { + public attribute: object; + public comparator: string; + public logic: string | object; + constructor(attr: object, comparator: string, logic: string | object); + constructor(attr: object, logic: string | object); +} + +export type AnyFunction = (...args: any[]) => any; + +/** + * Returns all shallow properties that accept `undefined`. + * Does not include Optional properties, only `undefined`. + * + * @example + * type UndefinedProps = UndefinedPropertiesOf<{ + * id: number | undefined, + * createdAt: string | undefined, + * firstName: string, + * lastName?: string, // optional properties are not included. + * }>; + * + * // is equal to + * + * type UndefinedProps = 'id' | 'createdAt'; + */ +export type UndefinedPropertiesOf = { + [P in keyof T]-?: undefined extends T[P] ? P : never +}[keyof T]; + +/** + * Makes all shallow properties of an object `optional` if they accept `undefined` as a value. + * + * @example + * type MyOptionalType = MakeUndefinedOptional<{ + * id: number | undefined, + * name: string, + * }>; + * + * // is equal to + * + * type MyOptionalType = { + * // this property is optional. + * id?: number | undefined, + * name: string, + * }; + */ +export type MakeUndefinedOptional = Optional>; diff --git a/types/test/models/UserGroup.ts b/types/test/models/UserGroup.ts new file mode 100644 index 000000000000..e50d4ac6eec9 --- /dev/null +++ b/types/test/models/UserGroup.ts @@ -0,0 +1,61 @@ +import { + InferAttributes, + InferCreationAttributes, + CreationOptional, + DataTypes, + HasMany, + HasManyAddAssociationMixin, + HasManyAddAssociationsMixin, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + HasManyGetAssociationsMixin, + HasManyHasAssociationMixin, + HasManyRemoveAssociationMixin, + HasManyRemoveAssociationsMixin, + HasManySetAssociationsMixin, + Model +} from 'sequelize'; +import { sequelize } from '../connection'; +// associate +// it is important to import _after_ the model above is already exported so the circular reference works. +import { User } from './User'; + +// This class doesn't extend the generic Model, but should still +// function just fine, with a bit less safe type-checking +export class UserGroup extends Model< + InferAttributes, + InferCreationAttributes +> { + public static associations: { + users: HasMany + }; + + declare id: CreationOptional; + declare name: string; + + // mixins for association (optional) + declare users?: User[]; + declare getUsers: HasManyGetAssociationsMixin; + declare setUsers: HasManySetAssociationsMixin; + declare addUser: HasManyAddAssociationMixin; + declare addUsers: HasManyAddAssociationsMixin; + declare createUser: HasManyCreateAssociationMixin; + declare countUsers: HasManyCountAssociationsMixin; + declare hasUser: HasManyHasAssociationMixin; + declare removeUser: HasManyRemoveAssociationMixin; + declare removeUsers: HasManyRemoveAssociationsMixin; +} + +// attach all the metadata to the model +// instead of this, you could also use decorators +UserGroup.init({ + name: DataTypes.STRING, + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + } +}, { sequelize }); + +export const Users = UserGroup.hasMany(User, { as: 'users', foreignKey: 'groupId' }); diff --git a/types/test/typescriptDocs/Define.ts b/types/test/typescriptDocs/Define.ts new file mode 100644 index 000000000000..8ea23694d01c --- /dev/null +++ b/types/test/typescriptDocs/Define.ts @@ -0,0 +1,40 @@ +/** + * Keep this file in sync with the code in the "Usage of `sequelize.define`" + * section in /docs/manual/other-topics/typescript.md + * + * Don't include this comment in the md file. + */ +import { Sequelize, Model, DataTypes, Optional } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// We recommend you declare an interface for the attributes, for stricter typechecking +interface UserAttributes { + id: number; + name: string; +} + +// Some fields are optional when calling UserModel.create() or UserModel.build() +interface UserCreationAttributes extends Optional {} + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance + extends Model, + UserAttributes {} + +const UserModel = sequelize.define('User', { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + } +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} diff --git a/types/test/typescriptDocs/DefineNoAttributes.ts b/types/test/typescriptDocs/DefineNoAttributes.ts new file mode 100644 index 000000000000..8273cad18ff1 --- /dev/null +++ b/types/test/typescriptDocs/DefineNoAttributes.ts @@ -0,0 +1,32 @@ +/** + * Keep this file in sync with the code in the "Usage of `sequelize.define`" + * that doesn't have attribute types in /docs/manual/other-topics/typescript.md + * + * Don't include this comment in the md file. + */ +import { Sequelize, Model, DataTypes } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance extends Model { + id: number; + name: string; +} + +const UserModel = sequelize.define('User', { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + }, +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} diff --git a/types/test/typescriptDocs/ModelInit.ts b/types/test/typescriptDocs/ModelInit.ts new file mode 100644 index 000000000000..ea7ebe6cd6e3 --- /dev/null +++ b/types/test/typescriptDocs/ModelInit.ts @@ -0,0 +1,227 @@ +/** + * Keep this file in sync with the code in the "Usage" section + * in /docs/manual/other-topics/typescript.md + * + * Don't include this comment in the md file. + */ +import { + Association, DataTypes, HasManyAddAssociationMixin, HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, HasManyGetAssociationsMixin, HasManyHasAssociationMixin, + HasManySetAssociationsMixin, HasManyAddAssociationsMixin, HasManyHasAssociationsMixin, + HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, Model, ModelDefined, Optional, + Sequelize, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute +} from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// 'projects' is excluded as it's not an attribute, it's an association. +class User extends Model, InferCreationAttributes> { + // id can be undefined during creation when using `autoIncrement` + declare id: CreationOptional; + declare name: string; + declare preferredName: string | null; // for nullable fields + + // timestamps! + // createdAt can be undefined during creation + declare createdAt: CreationOptional; + // updatedAt can be undefined during creation + declare updatedAt: CreationOptional; + + // Since TS cannot determine model association at compile time + // we have to declare them here purely virtually + // these will not exist until `Model.init` was called. + declare getProjects: HasManyGetAssociationsMixin; // Note the null assertions! + declare addProject: HasManyAddAssociationMixin; + declare addProjects: HasManyAddAssociationsMixin; + declare setProjects: HasManySetAssociationsMixin; + declare removeProject: HasManyRemoveAssociationMixin; + declare removeProjects: HasManyRemoveAssociationsMixin; + declare hasProject: HasManyHasAssociationMixin; + declare hasProjects: HasManyHasAssociationsMixin; + declare countProjects: HasManyCountAssociationsMixin; + declare createProject: HasManyCreateAssociationMixin; + + // You can also pre-declare possible inclusions, these will only be populated if you + // actively include a relation. + declare projects?: NonAttribute; // Note this is optional since it's only populated when explicitly requested in code + + // getters that are not attributes should be tagged using NonAttribute + // to remove them from the model's Attribute Typings. + get fullName(): NonAttribute { + return this.name; + } + + declare static associations: { + projects: Association; + }; +} + +class Project extends Model< + InferAttributes, + InferCreationAttributes +> { + // id can be undefined during creation when using `autoIncrement` + declare id: CreationOptional; + declare ownerId: number; + declare name: string; + + // `owner` is an eagerly-loaded association. + // We tag it as `NonAttribute` + declare owner?: NonAttribute; + + // createdAt can be undefined during creation + declare createdAt: CreationOptional; + // updatedAt can be undefined during creation + declare updatedAt: CreationOptional; +} + +class Address extends Model< + InferAttributes
, + InferCreationAttributes
+> { + declare userId: number; + declare address: string; + + // createdAt can be undefined during creation + declare createdAt: CreationOptional; + // updatedAt can be undefined during creation + declare updatedAt: CreationOptional; +} + +Project.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true + }, + ownerId: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false + }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, + }, + { + sequelize, + tableName: 'projects' + } +); + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true + }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, + }, + { + tableName: 'users', + sequelize // passing the `sequelize` instance is required + } +); + +Address.init( + { + userId: { + type: DataTypes.INTEGER.UNSIGNED + }, + address: { + type: new DataTypes.STRING(128), + allowNull: false + }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE, + }, + { + tableName: 'address', + sequelize // passing the `sequelize` instance is required + } +); + +// You can also define modules in a functional way +interface NoteAttributes { + id: number; + title: string; + content: string; +} + +// You can also set multiple attributes optional at once +type NoteCreationAttributes = Optional; + +// And with a functional approach defining a module looks like this +const Note: ModelDefined< + NoteAttributes, + NoteCreationAttributes +> = sequelize.define( + 'Note', + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true + }, + title: { + type: new DataTypes.STRING(64), + defaultValue: 'Unnamed Note' + }, + content: { + type: new DataTypes.STRING(4096), + allowNull: false + } + }, + { + tableName: 'notes' + } +); + +// Here we associate which actually populates out pre-declared `association` static and other methods. +User.hasMany(Project, { + sourceKey: 'id', + foreignKey: 'ownerId', + as: 'projects' // this determines the name in `associations`! +}); + +Address.belongsTo(User, { targetKey: 'id' }); +User.hasOne(Address, { sourceKey: 'id' }); + +async function doStuffWithUser() { + const newUser = await User.create({ + name: 'Johnny', + preferredName: 'John', + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const project = await newUser.createProject({ + name: 'first!' + }); + + const ourUser = await User.findByPk(1, { + include: [User.associations.projects], + rejectOnEmpty: true // Specifying true here removes `null` from the return type! + }); + + // Note the `!` null assertion since TS can't know if we included + // the model or not + console.log(ourUser.projects![0].name); +} + +(async () => { + await sequelize.sync(); + await doStuffWithUser(); +})();