diff --git a/README.md b/README.md index 6b7266f6..062678ac 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Decorators and some other extras for sequelize (v3 + v4). - [Multiple relations of same models](#multiple-relations-of-same-models) - [Model validation](#model-validation) - [Scopes](#scopes) + - [Hooks](#hooks) - [Why `() => Model`?](#user-content-why---model) - [Recommendations and limitations](#recommendations-and-limitations) @@ -552,6 +553,34 @@ export class ShoeWithScopes extends Model { } ``` +## Hooks +Hooks can be attached to your models. All Model-level hooks are supported. See [the related unit tests](test/models/Hook.ts) for a summary. + +Each hook must be a `static` method. Multiple hooks can be attached to a single method, and you can define multiple methods for a given hook. + +The name of the method cannot be the same as the name of the hook (for example, a `@BeforeCreate` hook method cannot be named `beforeCreate`). That’s because Sequelize has pre-defined methods with those names. + +```typescript +@Table +export class Person extends Model { + @Column + name: string; + + @BeforeUpdate + @BeforeCreate + static makeUpperCase(instance: Person) { + // this will be called when an instance is created or updated + instance.name = instance.name.toLocaleUpperCase(); + } + + @BeforeCreate + static addUnicorn(instance: Person) { + // this will also be called when an instance is created + instance.name += ' 🦄'; + } +} +``` + ## Why `() => Model`? `@ForeignKey(Model)` is much easier to read, so why is `@ForeignKey(() => Model)` so important? When it comes to circular-dependencies (which are in general solved by node for you) `Model` can be `undefined` diff --git a/index.ts b/index.ts index bde62901..05a3e94e 100644 --- a/index.ts +++ b/index.ts @@ -51,6 +51,40 @@ export {NotIn} from "./lib/annotations/validation/NotIn"; export {NotNull} from "./lib/annotations/validation/NotNull"; export {Validate} from "./lib/annotations/validation/Validate"; +// hooks +export {BeforeValidate} from "./lib/annotations/hooks/BeforeValidate"; +export {AfterValidate} from "./lib/annotations/hooks/AfterValidate"; +export {ValidationFailed} from "./lib/annotations/hooks/ValidationFailed"; +export {BeforeCreate} from "./lib/annotations/hooks/BeforeCreate"; +export {AfterCreate} from "./lib/annotations/hooks/AfterCreate"; +export {BeforeDestroy} from "./lib/annotations/hooks/BeforeDestroy"; +export {AfterDestroy} from "./lib/annotations/hooks/AfterDestroy"; +export {BeforeRestore} from "./lib/annotations/hooks/BeforeRestore"; +export {AfterRestore} from "./lib/annotations/hooks/AfterRestore"; +export {BeforeUpdate} from "./lib/annotations/hooks/BeforeUpdate"; +export {AfterUpdate} from "./lib/annotations/hooks/AfterUpdate"; +export {BeforeSave} from "./lib/annotations/hooks/BeforeSave"; +export {AfterSave} from "./lib/annotations/hooks/AfterSave"; +export {BeforeUpsert} from "./lib/annotations/hooks/BeforeUpsert"; +export {AfterUpsert} from "./lib/annotations/hooks/AfterUpsert"; +export {BeforeBulkCreate} from "./lib/annotations/hooks/BeforeBulkCreate"; +export {AfterBulkCreate} from "./lib/annotations/hooks/AfterBulkCreate"; +export {BeforeBulkDestroy} from "./lib/annotations/hooks/BeforeBulkDestroy"; +export {AfterBulkDestroy} from "./lib/annotations/hooks/AfterBulkDestroy"; +export {BeforeBulkRestore} from "./lib/annotations/hooks/BeforeBulkRestore"; +export {AfterBulkRestore} from "./lib/annotations/hooks/AfterBulkRestore"; +export {BeforeBulkUpdate} from "./lib/annotations/hooks/BeforeBulkUpdate"; +export {AfterBulkUpdate} from "./lib/annotations/hooks/AfterBulkUpdate"; +export {BeforeFind} from "./lib/annotations/hooks/BeforeFind"; +export {BeforeFindAfterExpandIncludeAll} from "./lib/annotations/hooks/BeforeFindAfterExpandIncludeAll"; +export {BeforeFindAfterOptions} from "./lib/annotations/hooks/BeforeFindAfterOptions"; +export {AfterFind} from "./lib/annotations/hooks/AfterFind"; +export {BeforeCount} from "./lib/annotations/hooks/BeforeCount"; +export {BeforeDelete} from "./lib/annotations/hooks/BeforeDelete"; +export {AfterDelete} from "./lib/annotations/hooks/AfterDelete"; +export {BeforeBulkDelete} from "./lib/annotations/hooks/BeforeBulkDelete"; +export {AfterBulkDelete} from "./lib/annotations/hooks/AfterBulkDelete"; + // interfaces export {IAssociationActionOptions} from "./lib/interfaces/IAssociationActionOptions"; export {IBuildOptions} from "./lib/interfaces/IBuildOptions"; diff --git a/lib/annotations/hooks/AfterBulkCreate.ts b/lib/annotations/hooks/AfterBulkCreate.ts new file mode 100644 index 00000000..50cf80d8 --- /dev/null +++ b/lib/annotations/hooks/AfterBulkCreate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterBulkCreate(target: any, propertyName: string): void; +export function AfterBulkCreate(options: IHookOptions): Function; +export function AfterBulkCreate(...args: any[]): void|Function { + return implementHookDecorator('afterBulkCreate', args); +} diff --git a/lib/annotations/hooks/AfterBulkDelete.ts b/lib/annotations/hooks/AfterBulkDelete.ts new file mode 100644 index 00000000..2d151121 --- /dev/null +++ b/lib/annotations/hooks/AfterBulkDelete.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterBulkDelete(target: any, propertyName: string): void; +export function AfterBulkDelete(options: IHookOptions): Function; +export function AfterBulkDelete(...args: any[]): void|Function { + return implementHookDecorator('afterBulkDelete', args); +} diff --git a/lib/annotations/hooks/AfterBulkDestroy.ts b/lib/annotations/hooks/AfterBulkDestroy.ts new file mode 100644 index 00000000..efdde0a3 --- /dev/null +++ b/lib/annotations/hooks/AfterBulkDestroy.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterBulkDestroy(target: any, propertyName: string): void; +export function AfterBulkDestroy(options: IHookOptions): Function; +export function AfterBulkDestroy(...args: any[]): void|Function { + return implementHookDecorator('afterBulkDestroy', args); +} diff --git a/lib/annotations/hooks/AfterBulkRestore.ts b/lib/annotations/hooks/AfterBulkRestore.ts new file mode 100644 index 00000000..1d0869c6 --- /dev/null +++ b/lib/annotations/hooks/AfterBulkRestore.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterBulkRestore(target: any, propertyName: string): void; +export function AfterBulkRestore(options: IHookOptions): Function; +export function AfterBulkRestore(...args: any[]): void|Function { + return implementHookDecorator('afterBulkRestore', args); +} diff --git a/lib/annotations/hooks/AfterBulkSync.ts b/lib/annotations/hooks/AfterBulkSync.ts new file mode 100644 index 00000000..16699660 --- /dev/null +++ b/lib/annotations/hooks/AfterBulkSync.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterBulkSync(target: any, propertyName: string): void; +export function AfterBulkSync(options: IHookOptions): Function; +export function AfterBulkSync(...args: any[]): void|Function { + return implementHookDecorator('afterBulkSync', args); +} diff --git a/lib/annotations/hooks/AfterBulkUpdate.ts b/lib/annotations/hooks/AfterBulkUpdate.ts new file mode 100644 index 00000000..2cb08b06 --- /dev/null +++ b/lib/annotations/hooks/AfterBulkUpdate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterBulkUpdate(target: any, propertyName: string): void; +export function AfterBulkUpdate(options: IHookOptions): Function; +export function AfterBulkUpdate(...args: any[]): void|Function { + return implementHookDecorator('afterBulkUpdate', args); +} diff --git a/lib/annotations/hooks/AfterConnect.ts b/lib/annotations/hooks/AfterConnect.ts new file mode 100644 index 00000000..e956ac1a --- /dev/null +++ b/lib/annotations/hooks/AfterConnect.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterConnect(target: any, propertyName: string): void; +export function AfterConnect(options: IHookOptions): Function; +export function AfterConnect(...args: any[]): void|Function { + return implementHookDecorator('afterConnect', args); +} diff --git a/lib/annotations/hooks/AfterCreate.ts b/lib/annotations/hooks/AfterCreate.ts new file mode 100644 index 00000000..0b541260 --- /dev/null +++ b/lib/annotations/hooks/AfterCreate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterCreate(target: any, propertyName: string): void; +export function AfterCreate(options: IHookOptions): Function; +export function AfterCreate(...args: any[]): void|Function { + return implementHookDecorator('afterCreate', args); +} diff --git a/lib/annotations/hooks/AfterDefine.ts b/lib/annotations/hooks/AfterDefine.ts new file mode 100644 index 00000000..1c306f35 --- /dev/null +++ b/lib/annotations/hooks/AfterDefine.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterDefine(target: any, propertyName: string): void; +export function AfterDefine(options: IHookOptions): Function; +export function AfterDefine(...args: any[]): void|Function { + return implementHookDecorator('afterDefine', args); +} diff --git a/lib/annotations/hooks/AfterDelete.ts b/lib/annotations/hooks/AfterDelete.ts new file mode 100644 index 00000000..dac29b2c --- /dev/null +++ b/lib/annotations/hooks/AfterDelete.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterDelete(target: any, propertyName: string): void; +export function AfterDelete(options: IHookOptions): Function; +export function AfterDelete(...args: any[]): void|Function { + return implementHookDecorator('afterDelete', args); +} diff --git a/lib/annotations/hooks/AfterDestroy.ts b/lib/annotations/hooks/AfterDestroy.ts new file mode 100644 index 00000000..503cef72 --- /dev/null +++ b/lib/annotations/hooks/AfterDestroy.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterDestroy(target: any, propertyName: string): void; +export function AfterDestroy(options: IHookOptions): Function; +export function AfterDestroy(...args: any[]): void|Function { + return implementHookDecorator('afterDestroy', args); +} diff --git a/lib/annotations/hooks/AfterFind.ts b/lib/annotations/hooks/AfterFind.ts new file mode 100644 index 00000000..6bd0c824 --- /dev/null +++ b/lib/annotations/hooks/AfterFind.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterFind(target: any, propertyName: string): void; +export function AfterFind(options: IHookOptions): Function; +export function AfterFind(...args: any[]): void|Function { + return implementHookDecorator('afterFind', args); +} diff --git a/lib/annotations/hooks/AfterInit.ts b/lib/annotations/hooks/AfterInit.ts new file mode 100644 index 00000000..34796ccf --- /dev/null +++ b/lib/annotations/hooks/AfterInit.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterInit(target: any, propertyName: string): void; +export function AfterInit(options: IHookOptions): Function; +export function AfterInit(...args: any[]): void|Function { + return implementHookDecorator('afterInit', args); +} diff --git a/lib/annotations/hooks/AfterRestore.ts b/lib/annotations/hooks/AfterRestore.ts new file mode 100644 index 00000000..6a63e49e --- /dev/null +++ b/lib/annotations/hooks/AfterRestore.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterRestore(target: any, propertyName: string): void; +export function AfterRestore(options: IHookOptions): Function; +export function AfterRestore(...args: any[]): void|Function { + return implementHookDecorator('afterRestore', args); +} diff --git a/lib/annotations/hooks/AfterSave.ts b/lib/annotations/hooks/AfterSave.ts new file mode 100644 index 00000000..fb1fa9ff --- /dev/null +++ b/lib/annotations/hooks/AfterSave.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterSave(target: any, propertyName: string): void; +export function AfterSave(options: IHookOptions): Function; +export function AfterSave(...args: any[]): void|Function { + return implementHookDecorator('afterSave', args); +} diff --git a/lib/annotations/hooks/AfterSync.ts b/lib/annotations/hooks/AfterSync.ts new file mode 100644 index 00000000..7967da2b --- /dev/null +++ b/lib/annotations/hooks/AfterSync.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterSync(target: any, propertyName: string): void; +export function AfterSync(options: IHookOptions): Function; +export function AfterSync(...args: any[]): void|Function { + return implementHookDecorator('afterSync', args); +} diff --git a/lib/annotations/hooks/AfterUpdate.ts b/lib/annotations/hooks/AfterUpdate.ts new file mode 100644 index 00000000..1275215f --- /dev/null +++ b/lib/annotations/hooks/AfterUpdate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterUpdate(target: any, propertyName: string): void; +export function AfterUpdate(options: IHookOptions): Function; +export function AfterUpdate(...args: any[]): void|Function { + return implementHookDecorator('afterUpdate', args); +} diff --git a/lib/annotations/hooks/AfterUpsert.ts b/lib/annotations/hooks/AfterUpsert.ts new file mode 100644 index 00000000..c5537476 --- /dev/null +++ b/lib/annotations/hooks/AfterUpsert.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterUpsert(target: any, propertyName: string): void; +export function AfterUpsert(options: IHookOptions): Function; +export function AfterUpsert(...args: any[]): void|Function { + return implementHookDecorator('afterUpsert', args); +} diff --git a/lib/annotations/hooks/AfterValidate.ts b/lib/annotations/hooks/AfterValidate.ts new file mode 100644 index 00000000..552c9f19 --- /dev/null +++ b/lib/annotations/hooks/AfterValidate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function AfterValidate(target: any, propertyName: string): void; +export function AfterValidate(options: IHookOptions): Function; +export function AfterValidate(...args: any[]): void|Function { + return implementHookDecorator('afterValidate', args); +} diff --git a/lib/annotations/hooks/BeforeBulkCreate.ts b/lib/annotations/hooks/BeforeBulkCreate.ts new file mode 100644 index 00000000..9fa85763 --- /dev/null +++ b/lib/annotations/hooks/BeforeBulkCreate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeBulkCreate(target: any, propertyName: string): void; +export function BeforeBulkCreate(options: IHookOptions): Function; +export function BeforeBulkCreate(...args: any[]): void|Function { + return implementHookDecorator('beforeBulkCreate', args); +} diff --git a/lib/annotations/hooks/BeforeBulkDelete.ts b/lib/annotations/hooks/BeforeBulkDelete.ts new file mode 100644 index 00000000..41c720a0 --- /dev/null +++ b/lib/annotations/hooks/BeforeBulkDelete.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeBulkDelete(target: any, propertyName: string): void; +export function BeforeBulkDelete(options: IHookOptions): Function; +export function BeforeBulkDelete(...args: any[]): void|Function { + return implementHookDecorator('beforeBulkDelete', args); +} diff --git a/lib/annotations/hooks/BeforeBulkDestroy.ts b/lib/annotations/hooks/BeforeBulkDestroy.ts new file mode 100644 index 00000000..3a262b8c --- /dev/null +++ b/lib/annotations/hooks/BeforeBulkDestroy.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeBulkDestroy(target: any, propertyName: string): void; +export function BeforeBulkDestroy(options: IHookOptions): Function; +export function BeforeBulkDestroy(...args: any[]): void|Function { + return implementHookDecorator('beforeBulkDestroy', args); +} diff --git a/lib/annotations/hooks/BeforeBulkRestore.ts b/lib/annotations/hooks/BeforeBulkRestore.ts new file mode 100644 index 00000000..2851926d --- /dev/null +++ b/lib/annotations/hooks/BeforeBulkRestore.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeBulkRestore(target: any, propertyName: string): void; +export function BeforeBulkRestore(options: IHookOptions): Function; +export function BeforeBulkRestore(...args: any[]): void|Function { + return implementHookDecorator('beforeBulkRestore', args); +} diff --git a/lib/annotations/hooks/BeforeBulkSync.ts b/lib/annotations/hooks/BeforeBulkSync.ts new file mode 100644 index 00000000..caa74d99 --- /dev/null +++ b/lib/annotations/hooks/BeforeBulkSync.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeBulkSync(target: any, propertyName: string): void; +export function BeforeBulkSync(options: IHookOptions): Function; +export function BeforeBulkSync(...args: any[]): void|Function { + return implementHookDecorator('beforeBulkSync', args); +} diff --git a/lib/annotations/hooks/BeforeBulkUpdate.ts b/lib/annotations/hooks/BeforeBulkUpdate.ts new file mode 100644 index 00000000..927534f3 --- /dev/null +++ b/lib/annotations/hooks/BeforeBulkUpdate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeBulkUpdate(target: any, propertyName: string): void; +export function BeforeBulkUpdate(options: IHookOptions): Function; +export function BeforeBulkUpdate(...args: any[]): void|Function { + return implementHookDecorator('beforeBulkUpdate', args); +} diff --git a/lib/annotations/hooks/BeforeConnect.ts b/lib/annotations/hooks/BeforeConnect.ts new file mode 100644 index 00000000..106272c3 --- /dev/null +++ b/lib/annotations/hooks/BeforeConnect.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeConnect(target: any, propertyName: string): void; +export function BeforeConnect(options: IHookOptions): Function; +export function BeforeConnect(...args: any[]): void|Function { + return implementHookDecorator('beforeConnect', args); +} diff --git a/lib/annotations/hooks/BeforeCount.ts b/lib/annotations/hooks/BeforeCount.ts new file mode 100644 index 00000000..6311afc6 --- /dev/null +++ b/lib/annotations/hooks/BeforeCount.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeCount(target: any, propertyName: string): void; +export function BeforeCount(options: IHookOptions): Function; +export function BeforeCount(...args: any[]): void|Function { + return implementHookDecorator('beforeCount', args); +} diff --git a/lib/annotations/hooks/BeforeCreate.ts b/lib/annotations/hooks/BeforeCreate.ts new file mode 100644 index 00000000..40adcfed --- /dev/null +++ b/lib/annotations/hooks/BeforeCreate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeCreate(target: any, propertyName: string): void; +export function BeforeCreate(options: IHookOptions): Function; +export function BeforeCreate(...args: any[]): void|Function { + return implementHookDecorator('beforeCreate', args); +} diff --git a/lib/annotations/hooks/BeforeDefine.ts b/lib/annotations/hooks/BeforeDefine.ts new file mode 100644 index 00000000..bbc8d71b --- /dev/null +++ b/lib/annotations/hooks/BeforeDefine.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeDefine(target: any, propertyName: string): void; +export function BeforeDefine(options: IHookOptions): Function; +export function BeforeDefine(...args: any[]): void|Function { + return implementHookDecorator('beforeDefine', args); +} diff --git a/lib/annotations/hooks/BeforeDelete.ts b/lib/annotations/hooks/BeforeDelete.ts new file mode 100644 index 00000000..8677beff --- /dev/null +++ b/lib/annotations/hooks/BeforeDelete.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeDelete(target: any, propertyName: string): void; +export function BeforeDelete(options: IHookOptions): Function; +export function BeforeDelete(...args: any[]): void|Function { + return implementHookDecorator('beforeDelete', args); +} diff --git a/lib/annotations/hooks/BeforeDestroy.ts b/lib/annotations/hooks/BeforeDestroy.ts new file mode 100644 index 00000000..9341cbae --- /dev/null +++ b/lib/annotations/hooks/BeforeDestroy.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeDestroy(target: any, propertyName: string): void; +export function BeforeDestroy(options: IHookOptions): Function; +export function BeforeDestroy(...args: any[]): void|Function { + return implementHookDecorator('beforeDestroy', args); +} diff --git a/lib/annotations/hooks/BeforeFind.ts b/lib/annotations/hooks/BeforeFind.ts new file mode 100644 index 00000000..ebafb520 --- /dev/null +++ b/lib/annotations/hooks/BeforeFind.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeFind(target: any, propertyName: string): void; +export function BeforeFind(options: IHookOptions): Function; +export function BeforeFind(...args: any[]): void|Function { + return implementHookDecorator('beforeFind', args); +} diff --git a/lib/annotations/hooks/BeforeFindAfterExpandIncludeAll.ts b/lib/annotations/hooks/BeforeFindAfterExpandIncludeAll.ts new file mode 100644 index 00000000..a741fe00 --- /dev/null +++ b/lib/annotations/hooks/BeforeFindAfterExpandIncludeAll.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeFindAfterExpandIncludeAll(target: any, propertyName: string): void; +export function BeforeFindAfterExpandIncludeAll(options: IHookOptions): Function; +export function BeforeFindAfterExpandIncludeAll(...args: any[]): void|Function { + return implementHookDecorator('beforeFindAfterExpandIncludeAll', args); +} diff --git a/lib/annotations/hooks/BeforeFindAfterOptions.ts b/lib/annotations/hooks/BeforeFindAfterOptions.ts new file mode 100644 index 00000000..b32687f1 --- /dev/null +++ b/lib/annotations/hooks/BeforeFindAfterOptions.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeFindAfterOptions(target: any, propertyName: string): void; +export function BeforeFindAfterOptions(options: IHookOptions): Function; +export function BeforeFindAfterOptions(...args: any[]): void|Function { + return implementHookDecorator('beforeFindAfterOptions', args); +} diff --git a/lib/annotations/hooks/BeforeInit.ts b/lib/annotations/hooks/BeforeInit.ts new file mode 100644 index 00000000..db46a5b4 --- /dev/null +++ b/lib/annotations/hooks/BeforeInit.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeInit(target: any, propertyName: string): void; +export function BeforeInit(options: IHookOptions): Function; +export function BeforeInit(...args: any[]): void|Function { + return implementHookDecorator('beforeInit', args); +} diff --git a/lib/annotations/hooks/BeforeRestore.ts b/lib/annotations/hooks/BeforeRestore.ts new file mode 100644 index 00000000..aeab5fa6 --- /dev/null +++ b/lib/annotations/hooks/BeforeRestore.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeRestore(target: any, propertyName: string): void; +export function BeforeRestore(options: IHookOptions): Function; +export function BeforeRestore(...args: any[]): void|Function { + return implementHookDecorator('beforeRestore', args); +} diff --git a/lib/annotations/hooks/BeforeSave.ts b/lib/annotations/hooks/BeforeSave.ts new file mode 100644 index 00000000..3115d647 --- /dev/null +++ b/lib/annotations/hooks/BeforeSave.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeSave(target: any, propertyName: string): void; +export function BeforeSave(options: IHookOptions): Function; +export function BeforeSave(...args: any[]): void|Function { + return implementHookDecorator('beforeSave', args); +} diff --git a/lib/annotations/hooks/BeforeSync.ts b/lib/annotations/hooks/BeforeSync.ts new file mode 100644 index 00000000..9a1df733 --- /dev/null +++ b/lib/annotations/hooks/BeforeSync.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeSync(target: any, propertyName: string): void; +export function BeforeSync(options: IHookOptions): Function; +export function BeforeSync(...args: any[]): void|Function { + return implementHookDecorator('beforeSync', args); +} diff --git a/lib/annotations/hooks/BeforeUpdate.ts b/lib/annotations/hooks/BeforeUpdate.ts new file mode 100644 index 00000000..8bc54b4e --- /dev/null +++ b/lib/annotations/hooks/BeforeUpdate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeUpdate(target: any, propertyName: string): void; +export function BeforeUpdate(options: IHookOptions): Function; +export function BeforeUpdate(...args: any[]): void|Function { + return implementHookDecorator('beforeUpdate', args); +} diff --git a/lib/annotations/hooks/BeforeUpsert.ts b/lib/annotations/hooks/BeforeUpsert.ts new file mode 100644 index 00000000..3c653609 --- /dev/null +++ b/lib/annotations/hooks/BeforeUpsert.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeUpsert(target: any, propertyName: string): void; +export function BeforeUpsert(options: IHookOptions): Function; +export function BeforeUpsert(...args: any[]): void|Function { + return implementHookDecorator('beforeUpsert', args); +} diff --git a/lib/annotations/hooks/BeforeValidate.ts b/lib/annotations/hooks/BeforeValidate.ts new file mode 100644 index 00000000..3232a355 --- /dev/null +++ b/lib/annotations/hooks/BeforeValidate.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function BeforeValidate(target: any, propertyName: string): void; +export function BeforeValidate(options: IHookOptions): Function; +export function BeforeValidate(...args: any[]): void|Function { + return implementHookDecorator('beforeValidate', args); +} diff --git a/lib/annotations/hooks/ValidationFailed.ts b/lib/annotations/hooks/ValidationFailed.ts new file mode 100644 index 00000000..2bd60193 --- /dev/null +++ b/lib/annotations/hooks/ValidationFailed.ts @@ -0,0 +1,8 @@ +import {IHookOptions} from "../../interfaces/IHookOptions"; +import {implementHookDecorator} from "../../services/hooks"; + +export function ValidationFailed(target: any, propertyName: string): void; +export function ValidationFailed(options: IHookOptions): Function; +export function ValidationFailed(...args: any[]): void|Function { + return implementHookDecorator('validationFailed', args); +} diff --git a/lib/interfaces/IHookOptions.ts b/lib/interfaces/IHookOptions.ts new file mode 100644 index 00000000..c4e29380 --- /dev/null +++ b/lib/interfaces/IHookOptions.ts @@ -0,0 +1,4 @@ +export interface IHookOptions { + + name?: string; +} diff --git a/lib/interfaces/ISequelizeHook.ts b/lib/interfaces/ISequelizeHook.ts new file mode 100644 index 00000000..cbcbe825 --- /dev/null +++ b/lib/interfaces/ISequelizeHook.ts @@ -0,0 +1,7 @@ +import {IHookOptions} from "./IHookOptions"; + +export interface ISequelizeHook { + hookType: string; + methodName: string; + options?: IHookOptions; +} diff --git a/lib/models/BaseSequelize.ts b/lib/models/BaseSequelize.ts index 5e68fdbd..f75a0dfc 100644 --- a/lib/models/BaseSequelize.ts +++ b/lib/models/BaseSequelize.ts @@ -3,6 +3,7 @@ import {DEFAULT_DEFINE_OPTIONS, getModels} from "../services/models"; import {getAssociations, processAssociation} from "../services/association"; import {ISequelizeConfig} from "../interfaces/ISequelizeConfig"; import {resolveScopes} from "../services/scopes"; +import {installHooks} from "../services/hooks"; import {ISequelizeValidationOnlyConfig} from "../interfaces/ISequelizeValidationOnlyConfig"; import {extend} from "../utils/object"; import {ISequelizeAssociation} from "../interfaces/ISequelizeAssociation"; @@ -61,6 +62,7 @@ export abstract class BaseSequelize { models.forEach(model => model.isInitialized = true); this.associateModels(models); resolveScopes(models); + installHooks​​(models); models.forEach(model => this._[model.name] = model); } diff --git a/lib/services/hooks.ts b/lib/services/hooks.ts new file mode 100644 index 00000000..1725580c --- /dev/null +++ b/lib/services/hooks.ts @@ -0,0 +1,100 @@ +import 'reflect-metadata'; +import {Model} from "../models/Model"; +import {ISequelizeHook} from "../interfaces/ISequelizeHook"; +import {IHookOptions} from "../interfaces/IHookOptions"; + +const HOOKS_KEY = 'sequelize:hooks'; + +/** + * Installs hooks on the specified models + */ +export function installHooks(models: Array): void { + models.forEach(model => { + const hooks = getHooks(model); + + if (hooks) { + hooks.forEach(hook => { + installHook(model, hook); + }); + } + }); +} + +/** + * Implementation for hook decorator functions. These are polymorphic. When + * called with a single argument (IHookOptions) they return a decorator + * factory function. When called with multiple arguments, they add the hook + * to the model’s metadata. + */ +export function implementHookDecorator(hookType: string, args: any[]): Function | void { + if (args.length === 1) { + + const options: IHookOptions = args[0]; + + return (target: any, propertyName: string) => + addHook(target, hookType, propertyName, options); + } else { + + const target = args[0]; + const propertyName = args[1]; + + addHook(target, hookType, propertyName); + } +} + +/** + * Adds hook meta data for specified model + * @throws if applied to a non-static method + * @throws if the hook method name is reserved + */ +export function addHook(target: any, hookType: string, methodName: string, options: IHookOptions = {}): void { + if (typeof target !== 'function') { + throw new Error(`Hook method '${methodName}' is not a static method. ` + + `Only static methods can be used for hooks`); + } + + // make sure the hook name doesn’t conflict with Sequelize’s existing methods + if (methodName === hookType) { + throw new Error(`Hook method cannot be named '${methodName}'. That name is ` + + `reserved by Sequelize`); + } + + const hooks = getHooks(target) || []; + + hooks.push({ + hookType, + methodName, + options + }); + + setHooks(target, hooks); +} + +/** + * Install a hook + */ +function installHook(model: typeof Model, hook: ISequelizeHook): void { + if (hook.options && hook.options.name) { + model.addHook(hook.hookType, hook.options.name, model[hook.methodName]); + return; + } + + model.addHook(hook.hookType, model[hook.methodName]); +} + +/** + * Returns hooks meta data from specified class + */ +export function getHooks(target: any): ISequelizeHook[] | undefined { + const hooks = Reflect.getMetadata(HOOKS_KEY, target); + if (hooks) { + return [...hooks]; + } +} + +/** + * Saves hooks meta data for the specified class + */ +export function setHooks(target: any, hooks: ISequelizeHook[]): void { + Reflect.defineMetadata(HOOKS_KEY, hooks, target); +} diff --git a/test/models/Hook.ts b/test/models/Hook.ts new file mode 100644 index 00000000..7168c525 --- /dev/null +++ b/test/models/Hook.ts @@ -0,0 +1,224 @@ +import { AfterBulkCreate, AfterUpsert, BeforeBulkCreate, BeforeUpsert } from "../../index"; +import { AfterBulkDestroy, BeforeBulkDestroy, BeforeBulkRestore } from "../../index"; +import { AfterBulkRestore, AfterBulkUpdate, BeforeBulkUpdate } from "../../index"; +import { AfterCreate, AfterValidate, ValidationFailed } from "../../index"; +import { AfterDestroy, AfterRestore, BeforeDestroy, BeforeRestore } from "../../index"; +import { AfterFind, BeforeCount, BeforeFindAfterOptions } from "../../index"; +import { AfterSave, AfterUpdate, BeforeSave, BeforeUpdate } from "../../index"; +import { BeforeCreate, BeforeValidate, Column, Model, Table } from "../../index"; +import { BeforeFind, BeforeFindAfterExpandIncludeAll } from "../../index"; +import { BeforeBulkDelete, AfterBulkDelete, AfterDelete, BeforeDelete } from "../../index"; + +/** + * Model used to test hook decorators. Defined hooks are mocked out for testing. + */ +@Table +export class Hook extends Model { + + @Column + name: string; + + @BeforeValidate + static beforeValidateHook(instance: Hook, options: any): void {} + + @AfterValidate + static afterValidateHook(instance: Hook, options: any): void {} + + @ValidationFailed + static validationFailedHook(instance: Hook, options: any, err: any): void {} + + @BeforeCreate + static beforeCreateHook(instance: Hook, options: any): void {} + + @AfterCreate + static afterCreateHook(instance: Hook, options: any): void {} + + @BeforeDestroy + static beforeDestroyHook(instance: Hook, options: any): void {} + + @BeforeDelete + static beforeDeleteHook(instance: Hook, options: any): void {} + + @AfterDestroy + static afterDestroyHook(instance: Hook, options: any): void {} + + @AfterDelete + static afterDeleteHook(instance: Hook, options: any): void {} + + @BeforeRestore + static beforeRestoreHook(instance: Hook, options: any): void {} + + @AfterRestore + static afterRestoreHook(instance: Hook, options: any): void {} + + @BeforeUpdate + static beforeUpdateHook(instance: Hook, options: any): void {} + + @AfterUpdate + static afterUpdateHook(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @BeforeSave + static beforeSaveHook(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @AfterSave + static afterSaveHook(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @BeforeUpsert + static beforeUpsertHook(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @AfterUpsert + static afterUpsertHook(instance: Hook, options: any): void {} + + @BeforeBulkCreate + static beforeBulkCreateHook(instances: Hook[], options: any): void {} + + @AfterBulkCreate + static afterBulkCreateHook(instances: Hook[], options: any): void {} + + @BeforeBulkDestroy + static beforeBulkDestroyHook(options: any): void {} + + @BeforeBulkDelete + static beforeBulkDeleteHook(options: any): void {} + + @AfterBulkDestroy + static afterBulkDestroyHook(options: any): void {} + + @AfterBulkDelete + static afterBulkDeleteHook(options: any): void {} + + @BeforeBulkRestore + static beforeBulkRestoreHook(options: any): void {} + + @AfterBulkRestore + static afterBulkRestoreHook(options: any): void {} + + @BeforeBulkUpdate + static beforeBulkUpdateHook(options: any): void {} + + @AfterBulkUpdate + static afterBulkUpdateHook(options: any): void {} + + @BeforeFind + static beforeFindHook(options: any): void {} + + @BeforeFindAfterExpandIncludeAll + static beforeFindAfterExpandIncludeAllHook(options: any): void {} + + @BeforeFindAfterOptions + static beforeFindAfterOptionsHook(options: any): void {} + + @AfterFind + static afterFindHook(options: any): void {} + + @BeforeCount + static beforeCountHook(options: any): void {} + + // Hooks can also be named. This allows them to be removed at a later time using + // Model.removeHook('hookType', 'hookName'). Please be aware that hook removal does not + // work correctly in versions of Sequelize earlier than 4.4.10. + + @BeforeValidate({ name: 'myBeforeValidateHook' }) + static beforeValidateHookWithName(instance: Hook, options: any): void {} + + @AfterValidate({ name: 'myAfterValidateHook' }) + static afterValidateHookWithName(instance: Hook, options: any): void {} + + @ValidationFailed({ name: 'myValidationFailedHook' }) + static validationFailedHookWithName(instance: Hook, options: any, err: any): void {} + + @BeforeCreate({ name: 'myBeforeCreateHook' }) + static beforeCreateHookWithName(instance: Hook, options: any): void {} + + @AfterCreate({ name: 'myAfterCreateHook' }) + static afterCreateHookWithName(instance: Hook, options: any): void {} + + @BeforeDestroy({ name: 'myBeforeDestroyHook' }) + static beforeDestroyHookWithName(instance: Hook, options: any): void {} + + @BeforeDelete({ name: 'myBeforeDeleteHook' }) + static beforeDeleteHookWithName(instance: Hook, options: any): void {} + + @AfterDestroy({ name: 'myAfterDestroyHook' }) + static afterDestroyHookWithName(instance: Hook, options: any): void {} + + @AfterDelete({ name: 'myAfterDeleteHook' }) + static afterDeleteHookWithName(instance: Hook, options: any): void {} + + @BeforeRestore({ name: 'myBeforeRestoreHook' }) + static beforeRestoreHookWithName(instance: Hook, options: any): void {} + + @AfterRestore({ name: 'myAfterRestoreHook' }) + static afterRestoreHookWithName(instance: Hook, options: any): void {} + + @BeforeUpdate({ name: 'myBeforeUpdateHook' }) + static beforeUpdateHookWithName(instance: Hook, options: any): void {} + + @AfterUpdate({ name: 'myAfterUpdateHook' }) + static afterUpdateHookWithName(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @BeforeSave({ name: 'myBeforeSaveHook' }) + static beforeSaveHookWithName(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @AfterSave({ name: 'myAfterSaveHook' }) + static afterSaveHookWithName(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @BeforeUpsert({ name: 'myBeforeUpsertHook' }) + static beforeUpsertHookWithName(instance: Hook, options: any): void {} + + // NOTE: this hook only available in Sequelize v4 + @AfterUpsert({ name: 'myAfterUpsertHook' }) + static afterUpsertHookWithName(instance: Hook, options: any): void {} + + @BeforeBulkCreate({ name: 'myBeforeBulkCreateHook' }) + static beforeBulkCreateHookWithName(instances: Hook[], options: any): void {} + + @AfterBulkCreate({ name: 'myAfterBulkCreateHook' }) + static afterBulkCreateHookWithName(instances: Hook[], options: any): void {} + + @BeforeBulkDestroy({ name: 'myBeforeBulkDestroyHook' }) + static beforeBulkDestroyHookWithName(options: any): void {} + + @BeforeBulkDelete({name: 'myBeforeBulkDeleteHook' }) + static beforeBulkDeleteHookWithName(options: any): void {} + + @AfterBulkDestroy({ name: 'myAfterBulkDestroyHook' }) + static afterBulkDestroyHookWithName(options: any): void {} + + @AfterBulkDelete({ name: 'myAfterBulkDeleteHook' }) + static afterBulkDeleteHookWithName(options: any): void {} + + @BeforeBulkRestore({ name: 'myBeforeBulkRestoreHook' }) + static beforeBulkRestoreHookWithName(options: any): void {} + + @AfterBulkRestore({ name: 'myAfterBulkRestoreHook' }) + static afterBulkRestoreHookWithName(options: any): void {} + + @BeforeBulkUpdate({ name: 'myBeforeBulkUpdateHook' }) + static beforeBulkUpdateHookWithName(options: any): void {} + + @AfterBulkUpdate({ name: 'myAfterBulkUpdateHook' }) + static afterBulkUpdateHookWithName(options: any): void {} + + @BeforeFind({ name: 'myBeforeFindHook' }) + static beforeFindHookWithName(options: any): void {} + + @BeforeFindAfterExpandIncludeAll({ name: 'myBeforeFindAfterExpandIncludeAllHook' }) + static beforeFindAfterExpandIncludeAllHookWithName(options: any): void {} + + @BeforeFindAfterOptions({ name: 'myBeforeFindAfterOptionsHook' }) + static beforeFindAfterOptionsHookWithName(options: any): void {} + + @AfterFind({ name: 'myAfterFindHook' }) + static afterFindHookWithName(options: any): void {} + + @BeforeCount({ name: 'myBeforeCountHook' }) + static beforeCountHookWithName(options: any): void {} +} diff --git a/test/specs/hooks/hooks.spec.ts b/test/specs/hooks/hooks.spec.ts new file mode 100644 index 00000000..cdd4030a --- /dev/null +++ b/test/specs/hooks/hooks.spec.ts @@ -0,0 +1,262 @@ +import * as OriginSequelize from 'sequelize'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +import { BeforeCreate, Sequelize } from "../../../index"; +import { Column, Model, Table } from "../../../index"; + +import { Hook } from "../../models/Hook"; +import { createSequelize } from "../../utils/sequelize"; + +const expect = chai.expect; +chai.use(sinonChai); + +describe('hook', () => { + const sequelize: Sequelize = createSequelize(false); + + beforeEach(() => { + + return sequelize.sync({force: true}); + }); + + it('should throw on non-static hooks', () => { + expect(() => { + @Table + class User extends Model { + + @Column + firstName: string; + + @Column + lastName: string; + + @BeforeCreate + nonStaticHookFunction(): void {} + } + }).to.throw(Error, /not a static method/); + }); + + it('should throw on methods with reserved names', () => { + expect(() => { + // tslint:disable-next-line:max-classes-per-file + @Table + class User extends Model { + + @Column + firstName: string; + + @Column + lastName: string; + + @BeforeCreate + static beforeCreate(): void {} + } + }).to.throw(Error, /name is reserved/); + }); + + it('should install all hooks', () => { + const beforeValidateHookStub = sinon.stub(Hook, 'beforeValidateHook'); + const afterValidateHookStub = sinon.stub(Hook, 'afterValidateHook'); + const validationFailedHookStub = sinon.stub(Hook, 'validationFailedHook'); + const beforeCreateHookStub = sinon.stub(Hook, 'beforeCreateHook'); + const afterCreateHookStub = sinon.stub(Hook, 'afterCreateHook'); + const beforeDestroyHookStub = sinon.stub(Hook, 'beforeDestroyHook'); + const afterDestroyHookStub = sinon.stub(Hook, 'afterDestroyHook'); + const beforeRestoreHookStub = sinon.stub(Hook, 'beforeRestoreHook'); + const afterRestoreHookStub = sinon.stub(Hook, 'afterRestoreHook'); + const beforeUpdateHookStub = sinon.stub(Hook, 'beforeUpdateHook'); + const afterUpdateHookStub = sinon.stub(Hook, 'afterUpdateHook'); + const beforeBulkCreateHookStub = sinon.stub(Hook, 'beforeBulkCreateHook'); + const afterBulkCreateHookStub = sinon.stub(Hook, 'afterBulkCreateHook'); + const beforeBulkDestroyHookStub = sinon.stub(Hook, 'beforeBulkDestroyHook'); + const afterBulkDestroyHookStub = sinon.stub(Hook, 'afterBulkDestroyHook'); + const beforeBulkRestoreHookStub = sinon.stub(Hook, 'beforeBulkRestoreHook'); + const afterBulkRestoreHookStub = sinon.stub(Hook, 'afterBulkRestoreHook'); + const beforeBulkUpdateHookStub = sinon.stub(Hook, 'beforeBulkUpdateHook'); + const afterBulkUpdateHookStub = sinon.stub(Hook, 'afterBulkUpdateHook'); + const beforeFindHookStub = sinon.stub(Hook, 'beforeFindHook'); + const beforeFindAfterExpandIncludeAllHookStub = sinon.stub(Hook, 'beforeFindAfterExpandIncludeAllHook'); + const beforeFindAfterOptionsHookStub = sinon.stub(Hook, 'beforeFindAfterOptionsHook'); + const afterFindHookStub = sinon.stub(Hook, 'afterFindHook'); + const beforeCountHookStub = sinon.stub(Hook, 'beforeCountHook'); + + // these hooks are aliases for the equivalent “destroy” hooks + const beforeDeleteHookStub = sinon.stub(Hook, 'beforeDeleteHook'); + const afterDeleteHookStub = sinon.stub(Hook, 'afterDeleteHook'); + const beforeBulkDeleteHookStub = sinon.stub(Hook, 'beforeBulkDeleteHook'); + const afterBulkDeleteHookStub = sinon.stub(Hook, 'afterBulkDeleteHook'); + + // some hooks are only available in Sequelize v4 + let beforeSaveHookStub: sinon.SinonStub; + let afterSaveHookStub: sinon.SinonStub; + let beforeUpsertHookStub: sinon.SinonStub; + let afterUpsertHookStub: sinon.SinonStub; + if (OriginSequelize['version'].split('.')[0] === '4') { + beforeSaveHookStub = sinon.stub(Hook, 'beforeSaveHook'); + afterSaveHookStub = sinon.stub(Hook, 'afterSaveHook'); + beforeUpsertHookStub = sinon.stub(Hook, 'beforeUpsertHook'); + afterUpsertHookStub = sinon.stub(Hook, 'afterUpsertHook'); + } + + const beforeValidateHookWithNameStub = sinon.stub(Hook, 'beforeValidateHookWithName'); + const afterValidateHookWithNameStub = sinon.stub(Hook, 'afterValidateHookWithName'); + const validationFailedHookWithNameStub = sinon.stub(Hook, 'validationFailedHookWithName'); + const beforeCreateHookWithNameStub = sinon.stub(Hook, 'beforeCreateHookWithName'); + const afterCreateHookWithNameStub = sinon.stub(Hook, 'afterCreateHookWithName'); + const beforeDestroyHookWithNameStub = sinon.stub(Hook, 'beforeDestroyHookWithName'); + const afterDestroyHookWithNameStub = sinon.stub(Hook, 'afterDestroyHookWithName'); + const beforeRestoreHookWithNameStub = sinon.stub(Hook, 'beforeRestoreHookWithName'); + const afterRestoreHookWithNameStub = sinon.stub(Hook, 'afterRestoreHookWithName'); + const beforeUpdateHookWithNameStub = sinon.stub(Hook, 'beforeUpdateHookWithName'); + const afterUpdateHookWithNameStub = sinon.stub(Hook, 'afterUpdateHookWithName'); + const beforeBulkCreateHookWithNameStub = sinon.stub(Hook, 'beforeBulkCreateHookWithName'); + const afterBulkCreateHookWithNameStub = sinon.stub(Hook, 'afterBulkCreateHookWithName'); + const beforeBulkDestroyHookWithNameStub = sinon.stub(Hook, 'beforeBulkDestroyHookWithName'); + const afterBulkDestroyHookWithNameStub = sinon.stub(Hook, 'afterBulkDestroyHookWithName'); + const beforeBulkRestoreHookWithNameStub = sinon.stub(Hook, 'beforeBulkRestoreHookWithName'); + const afterBulkRestoreHookWithNameStub = sinon.stub(Hook, 'afterBulkRestoreHookWithName'); + const beforeBulkUpdateHookWithNameStub = sinon.stub(Hook, 'beforeBulkUpdateHookWithName'); + const afterBulkUpdateHookWithNameStub = sinon.stub(Hook, 'afterBulkUpdateHookWithName'); + const beforeFindHookWithNameStub = sinon.stub(Hook, 'beforeFindHookWithName'); + const beforeFindAfterExpandIncludeAllHookWithNameStub = sinon.stub(Hook, 'beforeFindAfterExpandIncludeAllHookWithName'); + const beforeFindAfterOptionsHookWithNameStub = sinon.stub(Hook, 'beforeFindAfterOptionsHookWithName'); + const afterFindHookWithNameStub = sinon.stub(Hook, 'afterFindHookWithName'); + const beforeCountHookWithNameStub = sinon.stub(Hook, 'beforeCountHookWithName'); + + // these hooks are aliases for the equivalent “destroy” hooks + const beforeDeleteHookWithNameStub = sinon.stub(Hook, 'beforeDeleteHookWithName'); + const afterDeleteHookWithNameStub = sinon.stub(Hook, 'afterDeleteHookWithName'); + const beforeBulkDeleteHookWithNameStub = sinon.stub(Hook, 'beforeBulkDeleteHookWithName'); + const afterBulkDeleteHookWithNameStub = sinon.stub(Hook, 'afterBulkDeleteHookWithName'); + + // some hooks are only available in Sequelize v4 + let beforeSaveHookWithNameStub: sinon.SinonStub; + let afterSaveHookWithNameStub: sinon.SinonStub; + let beforeUpsertHookWithNameStub: sinon.SinonStub; + let afterUpsertHookWithNameStub: sinon.SinonStub; + if (OriginSequelize['version'].split('.')[0] === '4') { + beforeSaveHookWithNameStub = sinon.stub(Hook, 'beforeSaveHookWithName'); + afterSaveHookWithNameStub = sinon.stub(Hook, 'afterSaveHookWithName'); + beforeUpsertHookWithNameStub = sinon.stub(Hook, 'beforeUpsertHookWithName'); + afterUpsertHookWithNameStub = sinon.stub(Hook, 'afterUpsertHookWithName'); + } + + sequelize.addModels([Hook]); + + // Sequelize provides no public API to retrieve existing hooks. We are relying on an + // implementation detail: that the addHook method works by adding the specified + // function to the Model’s options.hooks object. + // + // We are not testing that the hooks are called: that’s in Sequelize’s domain. Our job + // is to ensure that the hooks are installed. + + expect(Hook['options'].hooks['beforeValidate']).to.include(beforeValidateHookStub); + expect(Hook['options'].hooks['afterValidate']).to.include(afterValidateHookStub); + expect(Hook['options'].hooks['validationFailed']).to.include(validationFailedHookStub); + expect(Hook['options'].hooks['beforeCreate']).to.include(beforeCreateHookStub); + expect(Hook['options'].hooks['afterCreate']).to.include(afterCreateHookStub); + expect(Hook['options'].hooks['beforeDestroy']).to.include(beforeDestroyHookStub); + expect(Hook['options'].hooks['afterDestroy']).to.include(afterDestroyHookStub); + expect(Hook['options'].hooks['beforeRestore']).to.include(beforeRestoreHookStub); + expect(Hook['options'].hooks['afterRestore']).to.include(afterRestoreHookStub); + expect(Hook['options'].hooks['beforeUpdate']).to.include(beforeUpdateHookStub); + expect(Hook['options'].hooks['afterUpdate']).to.include(afterUpdateHookStub); + expect(Hook['options'].hooks['beforeBulkCreate']).to.include(beforeBulkCreateHookStub); + expect(Hook['options'].hooks['afterBulkCreate']).to.include(afterBulkCreateHookStub); + expect(Hook['options'].hooks['beforeBulkDestroy']).to.include(beforeBulkDestroyHookStub); + expect(Hook['options'].hooks['afterBulkDestroy']).to.include(afterBulkDestroyHookStub); + expect(Hook['options'].hooks['beforeBulkRestore']).to.include(beforeBulkRestoreHookStub); + expect(Hook['options'].hooks['afterBulkRestore']).to.include(afterBulkRestoreHookStub); + expect(Hook['options'].hooks['beforeBulkUpdate']).to.include(beforeBulkUpdateHookStub); + expect(Hook['options'].hooks['afterBulkUpdate']).to.include(afterBulkUpdateHookStub); + expect(Hook['options'].hooks['beforeFind']).to.include(beforeFindHookStub); + expect(Hook['options'].hooks['beforeFindAfterExpandIncludeAll']).to.include(beforeFindAfterExpandIncludeAllHookStub); + expect(Hook['options'].hooks['beforeFindAfterOptions']).to.include(beforeFindAfterOptionsHookStub); + expect(Hook['options'].hooks['afterFind']).to.include(afterFindHookStub); + expect(Hook['options'].hooks['beforeCount']).to.include(beforeCountHookStub); + + expect(Hook['options'].hooks['beforeDestroy']).to.include(beforeDeleteHookStub); + expect(Hook['options'].hooks['afterDestroy']).to.include(afterDeleteHookStub); + expect(Hook['options'].hooks['beforeBulkDestroy']).to.include(beforeBulkDeleteHookStub); + expect(Hook['options'].hooks['afterBulkDestroy']).to.include(afterBulkDeleteHookStub); + + if (OriginSequelize['version'].split('.')[0] === '4') { + expect(Hook['options'].hooks['beforeSave']).to.include(beforeSaveHookStub); + expect(Hook['options'].hooks['afterSave']).to.include(afterSaveHookStub); + expect(Hook['options'].hooks['beforeUpsert']).to.include(beforeUpsertHookStub); + expect(Hook['options'].hooks['afterUpsert']).to.include(afterUpsertHookStub); + } + + // Named hooks + + expect(Hook['options'].hooks['beforeValidate']) + .to.include({ name: 'myBeforeValidateHook', fn: beforeValidateHookWithNameStub }); + expect(Hook['options'].hooks['afterValidate']) + .to.include({ name: 'myAfterValidateHook', fn: afterValidateHookWithNameStub }); + expect(Hook['options'].hooks['validationFailed']) + .to.include({ name: 'myValidationFailedHook', fn: validationFailedHookWithNameStub }); + expect(Hook['options'].hooks['beforeCreate']) + .to.include({ name: 'myBeforeCreateHook', fn: beforeCreateHookWithNameStub }); + expect(Hook['options'].hooks['afterCreate']) + .to.include({ name: 'myAfterCreateHook', fn: afterCreateHookWithNameStub }); + expect(Hook['options'].hooks['beforeDestroy']) + .to.include({ name: 'myBeforeDestroyHook', fn: beforeDestroyHookWithNameStub }); + expect(Hook['options'].hooks['afterDestroy']) + .to.include({ name: 'myAfterDestroyHook', fn: afterDestroyHookWithNameStub }); + expect(Hook['options'].hooks['beforeRestore']) + .to.include({ name: 'myBeforeRestoreHook', fn: beforeRestoreHookWithNameStub }); + expect(Hook['options'].hooks['afterRestore']) + .to.include({ name: 'myAfterRestoreHook', fn: afterRestoreHookWithNameStub }); + expect(Hook['options'].hooks['beforeUpdate']) + .to.include({ name: 'myBeforeUpdateHook', fn: beforeUpdateHookWithNameStub }); + expect(Hook['options'].hooks['afterUpdate']) + .to.include({ name: 'myAfterUpdateHook', fn: afterUpdateHookWithNameStub }); + expect(Hook['options'].hooks['beforeBulkCreate']) + .to.include({ name: 'myBeforeBulkCreateHook', fn: beforeBulkCreateHookWithNameStub }); + expect(Hook['options'].hooks['afterBulkCreate']) + .to.include({ name: 'myAfterBulkCreateHook', fn: afterBulkCreateHookWithNameStub }); + expect(Hook['options'].hooks['beforeBulkDestroy']) + .to.include({ name: 'myBeforeBulkDestroyHook', fn: beforeBulkDestroyHookWithNameStub }); + expect(Hook['options'].hooks['afterBulkDestroy']) + .to.include({ name: 'myAfterBulkDestroyHook', fn: afterBulkDestroyHookWithNameStub }); + expect(Hook['options'].hooks['beforeBulkRestore']) + .to.include({ name: 'myBeforeBulkRestoreHook', fn: beforeBulkRestoreHookWithNameStub }); + expect(Hook['options'].hooks['afterBulkRestore']) + .to.include({ name: 'myAfterBulkRestoreHook', fn: afterBulkRestoreHookWithNameStub }); + expect(Hook['options'].hooks['beforeBulkUpdate']) + .to.include({ name: 'myBeforeBulkUpdateHook', fn: beforeBulkUpdateHookWithNameStub }); + expect(Hook['options'].hooks['afterBulkUpdate']) + .to.include({ name: 'myAfterBulkUpdateHook', fn: afterBulkUpdateHookWithNameStub }); + expect(Hook['options'].hooks['beforeFind']) + .to.include({ name: 'myBeforeFindHook', fn: beforeFindHookWithNameStub }); + expect(Hook['options'].hooks['beforeFindAfterExpandIncludeAll']) + .to.include({ name: 'myBeforeFindAfterExpandIncludeAllHook', fn: beforeFindAfterExpandIncludeAllHookWithNameStub }); + expect(Hook['options'].hooks['beforeFindAfterOptions']) + .to.include({ name: 'myBeforeFindAfterOptionsHook', fn: beforeFindAfterOptionsHookWithNameStub }); + expect(Hook['options'].hooks['afterFind']) + .to.include({ name: 'myAfterFindHook', fn: afterFindHookWithNameStub }); + expect(Hook['options'].hooks['beforeCount']) + .to.include({ name: 'myBeforeCountHook', fn: beforeCountHookWithNameStub }); + + expect(Hook['options'].hooks['beforeDestroy']) + .to.include({ name: 'myBeforeDeleteHook', fn: beforeDeleteHookWithNameStub }); + expect(Hook['options'].hooks['afterDestroy']) + .to.include({ name: 'myAfterDeleteHook', fn: afterDeleteHookWithNameStub }); + expect(Hook['options'].hooks['beforeBulkDestroy']) + .to.include({ name: 'myBeforeBulkDeleteHook', fn: beforeBulkDeleteHookWithNameStub }); + expect(Hook['options'].hooks['afterBulkDestroy']) + .to.include({ name: 'myAfterBulkDeleteHook', fn: afterBulkDeleteHookWithNameStub }); + + if (OriginSequelize['version'].split('.')[0] === '4') { + expect(Hook['options'].hooks['beforeSave']) + .to.include({ name: 'myBeforeSaveHook', fn: beforeSaveHookWithNameStub }); + expect(Hook['options'].hooks['afterSave']) + .to.include({ name: 'myAfterSaveHook', fn: afterSaveHookWithNameStub }); + expect(Hook['options'].hooks['beforeUpsert']) + .to.include({ name: 'myBeforeUpsertHook', fn: beforeUpsertHookWithNameStub }); + expect(Hook['options'].hooks['afterUpsert']) + .to.include({ name: 'myAfterUpsertHook', fn: afterUpsertHookWithNameStub }); + } + }); +});