diff --git a/docs/decorator-reference.md b/docs/decorator-reference.md index 826d9d45e8..738eef01cf 100644 --- a/docs/decorator-reference.md +++ b/docs/decorator-reference.md @@ -1,42 +1,47 @@ # Decorator reference -* [Entity decorators](#entity-decorators) - * [`@Entity`](#entity) - * [`@ViewEntity`](#viewentity) -* [Column decorators](#column-decorators) - * [`@Column`](#column) - * [`@PrimaryColumn`](#primarycolumn) - * [`@PrimaryGeneratedColumn`](#primarygeneratedcolumn) - * [`@ObjectIdColumn`](#objectidcolumn) - * [`@CreateDateColumn`](#createdatecolumn) - * [`@UpdateDateColumn`](#updatedatecolumn) - * [`@DeleteDateColumn`](#deletedatecolumn) - * [`@VersionColumn`](#versioncolumn) - * [`@Generated`](#generated) -* [Relation decorators](#relation-decorators) - * [`@OneToOne`](#onetoone) - * [`@ManyToOne`](#manytoone) - * [`@OneToMany`](#onetomany) - * [`@ManyToMany`](#manytomany) - * [`@JoinColumn`](#joincolumn) - * [`@JoinTable`](#jointable) - * [`@RelationId`](#relationid) -* [Subscriber and listener decorators](#subscriber-and-listener-decorators) - * [`@AfterLoad`](#afterload) - * [`@BeforeInsert`](#beforeinsert) - * [`@AfterInsert`](#afterinsert) - * [`@BeforeUpdate`](#beforeupdate) - * [`@AfterUpdate`](#afterupdate) - * [`@BeforeRemove`](#beforeremove) - * [`@AfterRemove`](#afterremove) - * [`@EventSubscriber`](#eventsubscriber) -* [Other decorators](#other-decorators) - * [`@Index`](#index) - * [`@Unique`](#unique) - * [`@Check`](#check) - * [`@Exclusion`](#exclusion) - * [`@Transaction`, `@TransactionManager` and `@TransactionRepository`](#transaction-transactionmanager-and-transactionrepository) - * [`@EntityRepository`](#entityrepository) +- [Decorators reference](#decorators-reference) + - [Entity decorators](#entity-decorators) + - [`@Entity`](#entity) + - [`@ViewEntity`](#viewentity) + - [Column decorators](#column-decorators) + - [`@Column`](#column) + - [`@PrimaryColumn`](#primarycolumn) + - [`@PrimaryGeneratedColumn`](#primarygeneratedcolumn) + - [`@ObjectIdColumn`](#objectidcolumn) + - [`@CreateDateColumn`](#createdatecolumn) + - [`@UpdateDateColumn`](#updatedatecolumn) + - [`@DeleteDateColumn`](#deletedatecolumn) + - [`@VersionColumn`](#versioncolumn) + - [`@Generated`](#generated) + - [Relation decorators](#relation-decorators) + - [`@OneToOne`](#onetoone) + - [`@ManyToOne`](#manytoone) + - [`@OneToMany`](#onetomany) + - [`@ManyToMany`](#manytomany) + - [`@JoinColumn`](#joincolumn) + - [`@JoinTable`](#jointable) + - [`@RelationId`](#relationid) + - [Subscriber and listener decorators](#subscriber-and-listener-decorators) + - [`@AfterLoad`](#afterload) + - [`@BeforeInsert`](#beforeinsert) + - [`@AfterInsert`](#afterinsert) + - [`@BeforeUpdate`](#beforeupdate) + - [`@AfterUpdate`](#afterupdate) + - [`@BeforeRemove`](#beforeremove) + - [`@AfterRemove`](#afterremove) + - [`@BeforeSoftRemove`](#beforesoftremove) + - [`@AfterSoftRemove`](#aftersoftremove) + - [`@BeforeRecover`](#beforerecover) + - [`@AfterRecover`](#afterrecover) + - [`@EventSubscriber`](#eventsubscriber) + - [Other decorators](#other-decorators) + - [`@Index`](#index) + - [`@Unique`](#unique) + - [`@Check`](#check) + - [`@Exclusion`](#exclusion) + - [`@Transaction`, `@TransactionManager` and `@TransactionRepository`](#transaction-transactionmanager-and-transactionrepository) + - [`@EntityRepository`](#entityrepository) ## Entity decorators @@ -700,6 +705,82 @@ export class Post { Learn more about [listeners](listeners-and-subscribers.md). +#### `@BeforeSoftRemove` + +You can define a method with any name in the entity and mark it with `@BeforeSoftRemove` +and TypeORM will call it before a entity is soft removed using repository/manager `softRemove`. +Example: + +```typescript +@Entity() +export class Post { + + @BeforeSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +Learn more about [listeners](listeners-and-subscribers.md). + +#### `@AfterSoftRemove` + +You can define a method with any name in the entity and mark it with `@AfterSoftRemove` +and TypeORM will call it after the entity is soft removed using repository/manager `softRemove`. +Example: + +```typescript +@Entity() +export class Post { + + @AfterSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +Learn more about [listeners](listeners-and-subscribers.md). + +#### `@BeforeRecover` + +You can define a method with any name in the entity and mark it with `@BeforeRecover` +and TypeORM will call it before a entity is recovered using repository/manager `recover`. +Example: + +```typescript +@Entity() +export class Post { + + @BeforeRecover() + updateStatus() { + this.status = "recovered"; + } +} +``` + +Learn more about [listeners](listeners-and-subscribers.md). + +#### `@AfterRecover` + +You can define a method with any name in the entity and mark it with `@AfterRecover` +and TypeORM will call it after the entity is recovered using repository/manager `recover`. +Example: + +```typescript +@Entity() +export class Post { + + @AfterRecover() + updateStatus() { + this.status = "recovered"; + } +} +``` + +Learn more about [listeners](listeners-and-subscribers.md). + #### `@EventSubscriber` Marks a class as an event subscriber which can listen to specific entity events or any entity's events. diff --git a/docs/listeners-and-subscribers.md b/docs/listeners-and-subscribers.md index e5844c0776..a7e3627b3f 100644 --- a/docs/listeners-and-subscribers.md +++ b/docs/listeners-and-subscribers.md @@ -1,15 +1,20 @@ # Entity Listeners and Subscribers -* [What is an Entity Listener](#what-is-an-entity-listener) - * [`@AfterLoad`](#afterload) - * [`@BeforeInsert`](#beforeinsert) - * [`@AfterInsert`](#afterinsert) - * [`@BeforeUpdate`](#beforeupdate) - * [`@AfterUpdate`](#afterupdate) - * [`@BeforeRemove`](#beforeremove) - * [`@AfterRemove`](#afterremove) -* [What is a Subscriber](#what-is-a-subscriber) - * [`Event Object`](#event-object) +- [Entity Listeners and Subscribers](#entity-listeners-and-subscribers) + - [What is an Entity Listener](#what-is-an-entity-listener) + - [`@AfterLoad`](#afterload) + - [`@BeforeInsert`](#beforeinsert) + - [`@AfterInsert`](#afterinsert) + - [`@BeforeUpdate`](#beforeupdate) + - [`@AfterUpdate`](#afterupdate) + - [`@BeforeRemove`](#beforeremove) + - [`@AfterRemove`](#afterremove) + - [`@BeforeSoftRemove`](#beforesoftremove) + - [`@AfterSoftRemove`](#aftersoftremove) + - [`@BeforeRecover`](#beforerecover) + - [`@AfterRecover`](#afterrecover) + - [What is a Subscriber](#what-is-a-subscriber) + - [`Event Object`](#event-object) ## What is an Entity Listener @@ -28,11 +33,9 @@ Example: ```typescript @Entity() export class Post { - @AfterLoad() updateCounters() { - if (this.likesCount === undefined) - this.likesCount = 0; + if (this.likesCount === undefined) this.likesCount = 0; } } ``` @@ -46,7 +49,6 @@ Example: ```typescript @Entity() export class Post { - @BeforeInsert() updateDates() { this.createdDate = new Date(); @@ -63,7 +65,6 @@ Example: ```typescript @Entity() export class Post { - @AfterInsert() resetCounters() { this.counters = 0; @@ -80,7 +81,6 @@ Example: ```typescript @Entity() export class Post { - @BeforeUpdate() updateDates() { this.updatedDate = new Date(); @@ -97,7 +97,6 @@ Example: ```typescript @Entity() export class Post { - @AfterUpdate() updateCounters() { this.counter = 0; @@ -114,7 +113,6 @@ Example: ```typescript @Entity() export class Post { - @BeforeRemove() updateStatus() { this.status = "removed"; @@ -131,7 +129,6 @@ Example: ```typescript @Entity() export class Post { - @AfterRemove() updateStatus() { this.status = "removed"; @@ -139,6 +136,70 @@ export class Post { } ``` +### `@BeforeSoftRemove` + +You can define a method with any name in the entity and mark it with `@BeforeSoftRemove` +and TypeORM will call it before a entity is soft removed using repository/manager `softRemove`. +Example: + +```typescript +@Entity() +export class Post { + @BeforeSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +### `@AfterSoftRemove` + +You can define a method with any name in the entity and mark it with `@AfterSoftRemove` +and TypeORM will call it after the entity is soft removed using repository/manager `softRemove`. +Example: + +```typescript +@Entity() +export class Post { + @AfterSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +### `@BeforeRecover` + +You can define a method with any name in the entity and mark it with `@BeforeRecover` +and TypeORM will call it before a entity is recovered using repository/manager `recover`. +Example: + +```typescript +@Entity() +export class Post { + @BeforeRecover() + updateStatus() { + this.status = "recovered"; + } +} +``` + +### `@AfterRecover` + +You can define a method with any name in the entity and mark it with `@AfterRecover` +and TypeORM will call it after the entity is recovered using repository/manager `recover`. +Example: + +```typescript +@Entity() +export class Post { + @AfterSoftRemove() + updateStatus() { + this.status = "recovered"; + } +} +``` + ## What is a Subscriber Marks a class as an event subscriber which can listen to specific entity events or any entity events. @@ -148,8 +209,6 @@ Example: ```typescript @EventSubscriber() export class PostSubscriber implements EntitySubscriberInterface { - - /** * Indicates that this subscriber only listen to Post events. */ @@ -163,7 +222,6 @@ export class PostSubscriber implements EntitySubscriberInterface { beforeInsert(event: InsertEvent) { console.log(`BEFORE POST INSERTED: `, event.entity); } - } ``` @@ -173,7 +231,6 @@ To listen to any entity you just omit `listenTo` method and use `any`: ```typescript @EventSubscriber() export class PostSubscriber implements EntitySubscriberInterface { - /** * Called after entity is loaded. */ @@ -213,14 +270,60 @@ export class PostSubscriber implements EntitySubscriberInterface { * Called before entity removal. */ beforeRemove(event: RemoveEvent) { - console.log(`BEFORE ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); + console.log( + `BEFORE ENTITY WITH ID ${event.entityId} REMOVED: `, + event.entity + ); } /** * Called after entity removal. */ afterRemove(event: RemoveEvent) { - console.log(`AFTER ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); + console.log( + `AFTER ENTITY WITH ID ${event.entityId} REMOVED: `, + event.entity + ); + } + + /** + * Called before entity removal. + */ + beforeSoftRemove(event: SoftRemoveEvent) { + console.log( + `BEFORE ENTITY WITH ID ${event.entityId} SOFT REMOVED: `, + event.entity + ); + } + + /** + * Called after entity removal. + */ + afterSoftRemove(event: SoftRemoveEvent) { + console.log( + `AFTER ENTITY WITH ID ${event.entityId} SOFT REMOVED: `, + event.entity + ); + } + + /** + * Called before entity removal. + */ + beforeRecover(event: RecoverEvent) { + console.log( + `BEFORE ENTITY WITH ID ${event.entityId} RECOVERED: `, + event.entity + ); + } + + /** + * Called after entity removal. + */ + afterRecover(event: RecoverEvent) { + console.log( + `AFTER ENTITY WITH ID ${event.entityId} RECOVERED: `, + event.entity + ); } /** @@ -264,7 +367,6 @@ export class PostSubscriber implements EntitySubscriberInterface { afterTransactionRollback(event: TransactionRollbackEvent) { console.log(`AFTER TRANSACTION ROLLBACK: `, event); } - } ``` @@ -274,9 +376,9 @@ Make sure your `subscribers` property is set in your [Connection Options](./conn Excluding `listenTo`, all `EntitySubscriberInterface` methods are passed an event object that has the following base properties: -- `connection: Connection` - Connection used in the event. -- `queryRunner: QueryRunner` - QueryRunner used in the event transaction. -- `manager: EntityManager` - EntityManager used in the event transaction. +- `connection: Connection` - Connection used in the event. +- `queryRunner: QueryRunner` - QueryRunner used in the event transaction. +- `manager: EntityManager` - EntityManager used in the event transaction. See each [Event's interface](https://github.com/typeorm/typeorm/tree/master/src/subscriber/event) for additional properties. diff --git a/docs/zh_CN/decorator-reference.md b/docs/zh_CN/decorator-reference.md index 81d9f58e9f..af26888974 100644 --- a/docs/zh_CN/decorator-reference.md +++ b/docs/zh_CN/decorator-reference.md @@ -28,6 +28,10 @@ * [`@AfterUpdate`](#afterupdate) * [`@BeforeRemove`](#beforeremove) * [`@AfterRemove`](#afterremove) + * [`@BeforeSoftRemove`](#beforeremove) + * [`@AfterSoftRemove`](#afterremove) + * [`@BeforeRecover`](#beforeremove) + * [`@AfterRecover`](#afterremove) * [`@EventSubscriber`](#eventsubscriber) * [其他装饰器](#其他装饰器) * [`@Index`](#index) @@ -618,6 +622,74 @@ export class Post { } ``` +了解有关 [listeners](listeners-and-subscribers.md) 的更多信息 + +#### `@BeforeSoftRemove` + +你可以在实体中定义任何名称的方法,并使用`@BeforeSoftRemove`标记,TypeORM 将在使用 repository/manager`softRemove`删除实体之前调用它。 +例如: + +```typescript +@Entity() +export class Post { + @BeforeSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +了解有关 [listeners](listeners-and-subscribers.md)的更多信息。 + +#### `@AfterSoftRemove` + +你可以在实体中定义一个任何名称的方法,并使用`@AfterSoftRemove`标记它,TypeORM 将在使用 repository/manager`softRemove`删除实体之后调用它。 +例如: + +```typescript +@Entity() +export class Post { + @AfterSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +了解有关 [listeners](listeners-and-subscribers.md) 的更多信息 + +#### `@BeforeRecover` + +你可以在实体中定义任何名称的方法,并使用`@BeforeRecover`标记,TypeORM 将在使用 repository/manager`recover`删除实体之前调用它。 +例如: + +```typescript +@Entity() +export class Post { + @BeforeRecover() + updateStatus() { + this.status = "recovered"; + } +} +``` + +了解有关 [listeners](listeners-and-subscribers.md)的更多信息。 + +#### `@AfterRecover` + +你可以在实体中定义一个任何名称的方法,并使用`@AfterRecover`标记它,TypeORM 将在使用 repository/manager`recover`删除实体之后调用它。 +例如: + +```typescript +@Entity() +export class Post { + @AfterRecover() + updateStatus() { + this.status = "recovered"; + } +} +``` + 了解有关 [listeners](listeners-and-subscribers.md)的更多信息。 #### `@EventSubscriber` diff --git a/docs/zh_CN/listeners-and-subscribers.md b/docs/zh_CN/listeners-and-subscribers.md index eb8c0f0160..3ef49cb1e3 100644 --- a/docs/zh_CN/listeners-and-subscribers.md +++ b/docs/zh_CN/listeners-and-subscribers.md @@ -1,13 +1,18 @@ # 实体监听器和订阅者 -* [监听器](#监听器) - * [`@AfterLoad`](#afterload) - * [`@BeforeInsert`](#beforeinsert) - * [`@AfterInsert`](#afterinsert) - * [`@BeforeUpdate`](#beforeupdate) - * [`@AfterUpdate`](#afterupdate) - * [`@BeforeRemove`](#beforeremove) - * [`@AfterRemove`](#afterremove) -* [订阅者](#订阅者) +- [实体监听器和订阅者](#实体监听器和订阅者) + - [监听器](#监听器) + - [`@AfterLoad`](#afterload) + - [`@BeforeInsert`](#beforeinsert) + - [`@AfterInsert`](#afterinsert) + - [`@BeforeUpdate`](#beforeupdate) + - [`@AfterUpdate`](#afterupdate) + - [`@BeforeRemove`](#beforeremove) + - [`@AfterRemove`](#afterremove) + - [`@BeforeSoftRemove`](#beforesoftremove) + - [`@AfterSoftRemove`](#aftersoftremove) + - [`@BeforeRecover`](#beforerecover) + - [`@AfterRecover`](#afterrecover) + - [订阅者](#订阅者) ## 监听器 @@ -106,7 +111,7 @@ export class Post { ### `@AfterRemove` -你可以在实体中定义一个具有任何名称的方法,并使用`@ AfterRemove`标记它,TypeORM 将在使用 repository/manager `remove`删除实体后调用它。 +你可以在实体中定义一个具有任何名称的方法,并使用`@AfterRemove`标记它,TypeORM 将在使用 repository/manager `remove`删除实体后调用它。 例如: ```typescript @@ -119,6 +124,66 @@ export class Post { } ``` +### `@BeforeSoftRemove` + +你可以在实体中定义具有任何名称的方法,并使用`@BeforeSoftRemove`标记它,并且 TypeORM 将在使用 repository/manager `softRemove`删除实体之前调用它。 +例如: + +```typescript +@Entity() +export class Post { + @BeforeSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +### `@AfterSoftRemove` + +你可以在实体中定义一个具有任何名称的方法,并使用`@AfterSoftRemove`标记它,TypeORM 将在使用 repository/manager `softRemove`删除实体后调用它。 +例如: + +```typescript +@Entity() +export class Post { + @AfterSoftRemove() + updateStatus() { + this.status = "soft-removed"; + } +} +``` + +### `@BeforeRecover` + +你可以在实体中定义具有任何名称的方法,并使用`@BeforeRecover`标记它,并且 TypeORM 将在使用 repository/manager `recover`删除实体之前调用它。 +例如: + +```typescript +@Entity() +export class Post { + @BeforeRecover() + updateStatus() { + this.status = "recovered"; + } +} +``` + +### `@AfterRecover` + +你可以在实体中定义一个具有任何名称的方法,并使用`@AfterRecover`标记它,TypeORM 将在使用 repository/manager `recover`删除实体后调用它。 +例如: + +```typescript +@Entity() +export class Post { + @AfterRecover() + updateStatus() { + this.status = "recovered"; + } +} +``` + ## 订阅者 将类标记为可以侦听特定实体事件或任何实体事件的事件订阅者。 diff --git a/extra/typeorm-class-transformer-shim.js b/extra/typeorm-class-transformer-shim.js index 33b5dd9e25..a6257cad78 100644 --- a/extra/typeorm-class-transformer-shim.js +++ b/extra/typeorm-class-transformer-shim.js @@ -117,6 +117,16 @@ exports.AfterInsert = AfterInsert; } exports.AfterLoad = AfterLoad; +/* export */ function AfterRecover() { + return function(object, propertyName) {}; +} +exports.AfterRecover = AfterRecover; + +/* export */ function AfterSoftRemove() { + return function(object, propertyName) {}; +} +exports.AfterSoftRemove = AfterSoftRemove; + /* export */ function AfterRemove() { return function(object, propertyName) {}; } @@ -132,6 +142,16 @@ exports.AfterUpdate = AfterUpdate; } exports.BeforeInsert = BeforeInsert; +/* export */ function BeforeRecover() { + return function(object, propertyName) {}; +} +exports.BeforeRecover = BeforeRecover; + +/* export */ function BeforeSoftRemove() { + return function(object, propertyName) {}; +} +exports.BeforeSoftRemove = BeforeSoftRemove; + /* export */ function BeforeRemove() { return function(object, propertyName) {}; } diff --git a/extra/typeorm-model-shim.js b/extra/typeorm-model-shim.js index ff69bc0897..9dbe87c733 100644 --- a/extra/typeorm-model-shim.js +++ b/extra/typeorm-model-shim.js @@ -76,6 +76,16 @@ exports.AfterInsert = AfterInsert; } exports.AfterLoad = AfterLoad; +/* export */ function AfterSoftRemove() { + return function () {}; +} +exports.AfterSoftRemove = AfterSoftRemove; + +/* export */ function AfterRecover() { + return function () {}; +} +exports.AfterRecover = AfterRecover; + /* export */ function AfterRemove() { return function () {}; } @@ -91,6 +101,16 @@ exports.AfterUpdate = AfterUpdate; } exports.BeforeInsert = BeforeInsert; +/* export */ function BeforeSoftRemove() { + return function () {}; +} +exports.BeforeSoftRemove = BeforeSoftRemove; + +/* export */ function BeforeRecover() { + return function () {}; +} +exports.BeforeRecover = BeforeRecover; + /* export */ function BeforeRemove() { return function () {}; } diff --git a/sample/sample5-subscribers/subscriber/EverythingSubscriber.ts b/sample/sample5-subscribers/subscriber/EverythingSubscriber.ts index ebf795582b..2c1b9d7a3d 100644 --- a/sample/sample5-subscribers/subscriber/EverythingSubscriber.ts +++ b/sample/sample5-subscribers/subscriber/EverythingSubscriber.ts @@ -1,7 +1,9 @@ import {EventSubscriber} from "../../../src/decorator/listeners/EventSubscriber"; import {EntitySubscriberInterface} from "../../../src/subscriber/EntitySubscriberInterface"; import {InsertEvent} from "../../../src/subscriber/event/InsertEvent"; +import {RecoverEvent} from "../../../src/subscriber/event/RecoverEvent"; import {RemoveEvent} from "../../../src/subscriber/event/RemoveEvent"; +import { SoftRemoveEvent } from "../../../src/subscriber/event/SoftRemoveEvent"; import {UpdateEvent} from "../../../src/subscriber/event/UpdateEvent"; @EventSubscriber() @@ -27,6 +29,20 @@ export class EverythingSubscriber implements EntitySubscriberInterface { beforeRemove(event: RemoveEvent) { console.log(`BEFORE ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); } + + /** + * Called before entity insertion. + */ + beforeSoftRemove(event: SoftRemoveEvent) { + console.log(`BEFORE ENTITY WITH ID ${event.entityId} SOFT-REMOVED: `, event.entity); + } + + /** + * Called before entity insertion. + */ + beforeRecover(event: RecoverEvent) { + console.log(`BEFORE ENTITY WITH ID ${event.entityId} RECOVERED: `, event.entity); + } /** * Called after entity insertion. @@ -49,6 +65,20 @@ export class EverythingSubscriber implements EntitySubscriberInterface { console.log(`AFTER ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); } + /** + * Called after entity insertion. + */ + afterSoftRemove(event: SoftRemoveEvent) { + console.log(`AFTER ENTITY WITH ID ${event.entityId} SOFT-REMOVED: `, event.entity); + } + + /** + * Called after entity insertion. + */ + afterRecover(event: RecoverEvent) { + console.log(`AFTER ENTITY WITH ID ${event.entityId} RECOVERED: `, event.entity); + } + /** * Called after entity is loaded. */ diff --git a/sample/sample9-entity-listeners/entity/Post.ts b/sample/sample9-entity-listeners/entity/Post.ts index bab7374b3e..f57370c606 100644 --- a/sample/sample9-entity-listeners/entity/Post.ts +++ b/sample/sample9-entity-listeners/entity/Post.ts @@ -9,6 +9,10 @@ import {BeforeUpdate} from "../../../src/decorator/listeners/BeforeUpdate"; import {AfterUpdate} from "../../../src/decorator/listeners/AfterUpdate"; import {BeforeRemove} from "../../../src/decorator/listeners/BeforeRemove"; import {AfterRemove} from "../../../src/decorator/listeners/AfterRemove"; +import {AfterRecover} from "../../../src/decorator/listeners/AfterRecover"; +import {BeforeRecover} from "../../../src/decorator/listeners/BeforeRecover"; +import {AfterSoftRemove} from "../../../src/decorator/listeners/AfterSoftRemove"; +import {BeforeSoftRemove} from "../../../src/decorator/listeners/BeforeSoftRemove"; import {JoinTable} from "../../../src/decorator/relations/JoinTable"; @Entity("sample9_post") @@ -72,4 +76,24 @@ export class Post { console.log("event: Post entity has been removed and callback executed"); } -} \ No newline at end of file + @BeforeSoftRemove() + doSomethingBeforeSoftRemove() { + console.log("event: Post entity will be soft-removed so soon..."); + } + + @AfterSoftRemove() + doSomethingAfterSoftRemove() { + console.log("event: Post entity has been soft-removed and callback executed"); + } + + @BeforeRecover() + doSomethingBeforeRecover() { + console.log("event: Post entity will be recovered so soon..."); + } + + @AfterRecover() + doSomethingAfterRecover() { + console.log("event: Post entity has been recovered and callback executed"); + } + +} diff --git a/sample/sample9-entity-listeners/entity/PostAuthor.ts b/sample/sample9-entity-listeners/entity/PostAuthor.ts index c9f31d3ee4..0fd6070550 100644 --- a/sample/sample9-entity-listeners/entity/PostAuthor.ts +++ b/sample/sample9-entity-listeners/entity/PostAuthor.ts @@ -7,6 +7,10 @@ import {AfterUpdate} from "../../../src/decorator/listeners/AfterUpdate"; import {BeforeUpdate} from "../../../src/decorator/listeners/BeforeUpdate"; import {AfterInsert} from "../../../src/decorator/listeners/AfterInsert"; import {BeforeInsert} from "../../../src/decorator/listeners/BeforeInsert"; +import {AfterRecover} from "../../../src/decorator/listeners/AfterRecover"; +import {BeforeRecover} from "../../../src/decorator/listeners/BeforeRecover"; +import {AfterSoftRemove} from "../../../src/decorator/listeners/AfterSoftRemove"; +import {BeforeSoftRemove} from "../../../src/decorator/listeners/BeforeSoftRemove"; @Entity("sample9_post_author") export class PostAuthor { @@ -50,4 +54,24 @@ export class PostAuthor { console.log("event: PostAuthor entity has been removed and callback executed"); } -} \ No newline at end of file + @BeforeSoftRemove() + doSomethingBeforeSoftRemove() { + console.log("event: PostAuthor entity will be removed so soon..."); + } + + @AfterSoftRemove() + doSomethingAfterSoftRemove() { + console.log("event: PostAuthor entity has been removed and callback executed"); + } + + @BeforeRecover() + doSomethingBeforeRecover() { + console.log("event: PostAuthor entity will be removed so soon..."); + } + + @AfterRecover() + doSomethingAfterRecover() { + console.log("event: PostAuthor entity has been removed and callback executed"); + } + +} diff --git a/sample/sample9-entity-listeners/entity/PostCategory.ts b/sample/sample9-entity-listeners/entity/PostCategory.ts index 9b1af3e4b9..3f633e16e5 100644 --- a/sample/sample9-entity-listeners/entity/PostCategory.ts +++ b/sample/sample9-entity-listeners/entity/PostCategory.ts @@ -7,6 +7,10 @@ import {AfterUpdate} from "../../../src/decorator/listeners/AfterUpdate"; import {BeforeUpdate} from "../../../src/decorator/listeners/BeforeUpdate"; import {AfterInsert} from "../../../src/decorator/listeners/AfterInsert"; import {BeforeInsert} from "../../../src/decorator/listeners/BeforeInsert"; +import {AfterRecover} from "../../../src/decorator/listeners/AfterRecover"; +import {BeforeRecover} from "../../../src/decorator/listeners/BeforeRecover"; +import {AfterSoftRemove} from "../../../src/decorator/listeners/AfterSoftRemove"; +import {BeforeSoftRemove} from "../../../src/decorator/listeners/BeforeSoftRemove"; @Entity("sample9_post_category") export class PostCategory { @@ -52,4 +56,24 @@ export class PostCategory { console.log(`event: PostCategory "${this.name}" has been removed and callback executed`); } -} \ No newline at end of file + @BeforeSoftRemove() + doSomethingBeforeSoftRemove() { + console.log(`event: PostCategory "${this.name}" will be soft-removed so soon...`); + } + + @AfterSoftRemove() + doSomethingAfterSoftRemove() { + console.log(`event: PostCategory "${this.name}" has been soft-removed and callback executed`); + } + + @BeforeRecover() + doSomethingBeforeRecover() { + console.log(`event: PostCategory "${this.name}" will be recovered so soon...`); + } + + @AfterRecover() + doSomethingAfterRecover() { + console.log(`event: PostCategory "${this.name}" has been recovered and callback executed`); + } + +} diff --git a/src/decorator/listeners/AfterRecover.ts b/src/decorator/listeners/AfterRecover.ts new file mode 100644 index 0000000000..33d6d0846c --- /dev/null +++ b/src/decorator/listeners/AfterRecover.ts @@ -0,0 +1,17 @@ +import {getMetadataArgsStorage} from "../../globals"; +import {EventListenerTypes} from "../../metadata/types/EventListenerTypes"; +import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs"; + +/** + * Calls a method on which this decorator is applied before this entity soft removal. + */ +export function AfterRecover(): PropertyDecorator { + return function (object: Object, propertyName: string) { + + getMetadataArgsStorage().entityListeners.push({ + target: object.constructor, + propertyName: propertyName, + type: EventListenerTypes.AFTER_RECOVER + } as EntityListenerMetadataArgs); + }; +} diff --git a/src/decorator/listeners/AfterSoftRemove.ts b/src/decorator/listeners/AfterSoftRemove.ts new file mode 100644 index 0000000000..e87dc8dd62 --- /dev/null +++ b/src/decorator/listeners/AfterSoftRemove.ts @@ -0,0 +1,17 @@ +import {getMetadataArgsStorage} from "../../globals"; +import {EventListenerTypes} from "../../metadata/types/EventListenerTypes"; +import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs"; + +/** + * Calls a method on which this decorator is applied before this entity soft removal. + */ +export function AfterSoftRemove(): PropertyDecorator { + return function (object: Object, propertyName: string) { + + getMetadataArgsStorage().entityListeners.push({ + target: object.constructor, + propertyName: propertyName, + type: EventListenerTypes.AFTER_SOFT_REMOVE + } as EntityListenerMetadataArgs); + }; +} diff --git a/src/decorator/listeners/BeforeRecover.ts b/src/decorator/listeners/BeforeRecover.ts new file mode 100644 index 0000000000..505be765aa --- /dev/null +++ b/src/decorator/listeners/BeforeRecover.ts @@ -0,0 +1,17 @@ +import {getMetadataArgsStorage} from "../../globals"; +import {EventListenerTypes} from "../../metadata/types/EventListenerTypes"; +import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs"; + +/** + * Calls a method on which this decorator is applied before this entity soft removal. + */ +export function BeforeRecover(): PropertyDecorator { + return function (object: Object, propertyName: string) { + + getMetadataArgsStorage().entityListeners.push({ + target: object.constructor, + propertyName: propertyName, + type: EventListenerTypes.BEFORE_RECOVER + } as EntityListenerMetadataArgs); + }; +} diff --git a/src/decorator/listeners/BeforeSoftRemove.ts b/src/decorator/listeners/BeforeSoftRemove.ts new file mode 100644 index 0000000000..8024d65244 --- /dev/null +++ b/src/decorator/listeners/BeforeSoftRemove.ts @@ -0,0 +1,17 @@ +import {getMetadataArgsStorage} from "../../globals"; +import {EventListenerTypes} from "../../metadata/types/EventListenerTypes"; +import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs"; + +/** + * Calls a method on which this decorator is applied before this entity soft removal. + */ +export function BeforeSoftRemove(): PropertyDecorator { + return function (object: Object, propertyName: string) { + + getMetadataArgsStorage().entityListeners.push({ + target: object.constructor, + propertyName: propertyName, + type: EventListenerTypes.BEFORE_SOFT_REMOVE + } as EntityListenerMetadataArgs); + }; +} diff --git a/src/index.ts b/src/index.ts index 65476dd44e..b5bd4ee1ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,9 +26,13 @@ export * from "./decorator/columns/ObjectIdColumn"; export * from "./decorator/listeners/AfterInsert"; export * from "./decorator/listeners/AfterLoad"; export * from "./decorator/listeners/AfterRemove"; +export * from "./decorator/listeners/AfterSoftRemove"; +export * from "./decorator/listeners/AfterRecover"; export * from "./decorator/listeners/AfterUpdate"; export * from "./decorator/listeners/BeforeInsert"; export * from "./decorator/listeners/BeforeRemove"; +export * from "./decorator/listeners/BeforeSoftRemove"; +export * from "./decorator/listeners/BeforeRecover"; export * from "./decorator/listeners/BeforeUpdate"; export * from "./decorator/listeners/EventSubscriber"; export * from "./decorator/options/ColumnOptions"; diff --git a/src/metadata-builder/EntityMetadataBuilder.ts b/src/metadata-builder/EntityMetadataBuilder.ts index 1302abc918..8d23fe8fd9 100644 --- a/src/metadata-builder/EntityMetadataBuilder.ts +++ b/src/metadata-builder/EntityMetadataBuilder.ts @@ -637,9 +637,13 @@ export class EntityMetadataBuilder { entityMetadata.afterInsertListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.AFTER_INSERT); entityMetadata.afterUpdateListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.AFTER_UPDATE); entityMetadata.afterRemoveListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.AFTER_REMOVE); + entityMetadata.afterSoftRemoveListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.AFTER_SOFT_REMOVE); + entityMetadata.afterRecoverListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.AFTER_RECOVER); entityMetadata.beforeInsertListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.BEFORE_INSERT); entityMetadata.beforeUpdateListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.BEFORE_UPDATE); entityMetadata.beforeRemoveListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.BEFORE_REMOVE); + entityMetadata.beforeSoftRemoveListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.BEFORE_SOFT_REMOVE); + entityMetadata.beforeRecoverListeners = entityMetadata.listeners.filter(listener => listener.type === EventListenerTypes.BEFORE_RECOVER); entityMetadata.indices = entityMetadata.embeddeds.reduce((indices, embedded) => indices.concat(embedded.indicesFromTree), entityMetadata.ownIndices); entityMetadata.uniques = entityMetadata.embeddeds.reduce((uniques, embedded) => uniques.concat(embedded.uniquesFromTree), entityMetadata.ownUniques); entityMetadata.primaryColumns = entityMetadata.columns.filter(column => column.isPrimary); diff --git a/src/metadata/EntityMetadata.ts b/src/metadata/EntityMetadata.ts index efbce1b8b9..ef6961ae59 100644 --- a/src/metadata/EntityMetadata.ts +++ b/src/metadata/EntityMetadata.ts @@ -479,11 +479,31 @@ export class EntityMetadata { */ beforeRemoveListeners: EntityListenerMetadata[] = []; + /** + * Listener metadatas with "BEFORE SOFT REMOVE" type. + */ + beforeSoftRemoveListeners: EntityListenerMetadata[] = []; + + /** + * Listener metadatas with "BEFORE RECOVER" type. + */ + beforeRecoverListeners: EntityListenerMetadata[] = []; + /** * Listener metadatas with "AFTER REMOVE" type. */ afterRemoveListeners: EntityListenerMetadata[] = []; + /** + * Listener metadatas with "AFTER SOFT REMOVE" type. + */ + afterSoftRemoveListeners: EntityListenerMetadata[] = []; + + /** + * Listener metadatas with "AFTER RECOVER" type. + */ + afterRecoverListeners: EntityListenerMetadata[] = []; + /** * Map of columns and relations of the entity. * diff --git a/src/metadata/types/EventListenerTypes.ts b/src/metadata/types/EventListenerTypes.ts index 9a310b9aa1..551dce6f4d 100644 --- a/src/metadata/types/EventListenerTypes.ts +++ b/src/metadata/types/EventListenerTypes.ts @@ -8,7 +8,11 @@ export type EventListenerType = | "before-update" | "after-update" | "before-remove" - | "after-remove"; + | "after-remove" + | "before-soft-remove" + | "after-soft-remove" + | "before-recover" + | "after-recover"; /** * Provides a constants for each entity listener type. @@ -21,4 +25,8 @@ export class EventListenerTypes { static AFTER_UPDATE = "after-update" as const; static BEFORE_REMOVE = "before-remove" as const; static AFTER_REMOVE = "after-remove" as const; + static BEFORE_SOFT_REMOVE = "before-soft-remove" as const; + static AFTER_SOFT_REMOVE = "after-soft-remove" as const; + static BEFORE_RECOVER = "before-recover" as const; + static AFTER_RECOVER = "after-recover" as const; } diff --git a/src/persistence/SubjectExecutor.ts b/src/persistence/SubjectExecutor.ts index 40a9d3586b..593a497a36 100644 --- a/src/persistence/SubjectExecutor.ts +++ b/src/persistence/SubjectExecutor.ts @@ -203,7 +203,7 @@ export class SubjectExecutor { } /** - * Broadcasts "BEFORE_INSERT", "BEFORE_UPDATE", "BEFORE_REMOVE" events for all given subjects. + * Broadcasts "BEFORE_INSERT", "BEFORE_UPDATE", "BEFORE_REMOVE", "BEFORE_SOFT_REMOVE", "BEFORE_RECOVER" events for all given subjects. */ protected broadcastBeforeEventsForAll(): BroadcasterResult { const result = new BroadcasterResult(); @@ -214,14 +214,14 @@ export class SubjectExecutor { if (this.removeSubjects.length) this.removeSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastBeforeRemoveEvent(result, subject.metadata, subject.entity!, subject.databaseEntity)); if (this.softRemoveSubjects.length) - this.softRemoveSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastBeforeUpdateEvent(result, subject.metadata, subject.entity!, subject.databaseEntity, subject.diffColumns, subject.diffRelations)); + this.softRemoveSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastBeforeSoftRemoveEvent(result, subject.metadata, subject.entity!, subject.databaseEntity)); if (this.recoverSubjects.length) - this.recoverSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastBeforeUpdateEvent(result, subject.metadata, subject.entity!, subject.databaseEntity, subject.diffColumns, subject.diffRelations)); + this.recoverSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastBeforeRecoverEvent(result, subject.metadata, subject.entity!, subject.databaseEntity)); return result; } /** - * Broadcasts "AFTER_INSERT", "AFTER_UPDATE", "AFTER_REMOVE" events for all given subjects. + * Broadcasts "AFTER_INSERT", "AFTER_UPDATE", "AFTER_REMOVE", "AFTER_SOFT_REMOVE", "AFTER_RECOVER" events for all given subjects. * Returns void if there wasn't any listener or subscriber executed. * Note: this method has a performance-optimized code organization. */ @@ -234,9 +234,9 @@ export class SubjectExecutor { if (this.removeSubjects.length) this.removeSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastAfterRemoveEvent(result, subject.metadata, subject.entity!, subject.databaseEntity)); if (this.softRemoveSubjects.length) - this.softRemoveSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastAfterUpdateEvent(result, subject.metadata, subject.entity!, subject.databaseEntity, subject.diffColumns, subject.diffRelations)); + this.softRemoveSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastAfterSoftRemoveEvent(result, subject.metadata, subject.entity!, subject.databaseEntity)); if (this.recoverSubjects.length) - this.recoverSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastAfterUpdateEvent(result, subject.metadata, subject.entity!, subject.databaseEntity, subject.diffColumns, subject.diffRelations)); + this.recoverSubjects.forEach(subject => this.queryRunner.broadcaster.broadcastAfterRecoverEvent(result, subject.metadata, subject.entity!, subject.databaseEntity)); return result; } diff --git a/src/query-builder/SoftDeleteQueryBuilder.ts b/src/query-builder/SoftDeleteQueryBuilder.ts index 293f7d56a1..8439a7937d 100644 --- a/src/query-builder/SoftDeleteQueryBuilder.ts +++ b/src/query-builder/SoftDeleteQueryBuilder.ts @@ -63,9 +63,12 @@ export class SoftDeleteQueryBuilder extends QueryBuilder impleme transactionStartedByUs = true; } - // call before updation methods in listeners and subscribers + // call before soft remove and recover methods in listeners and subscribers if (this.expressionMap.callListeners === true && this.expressionMap.mainAlias!.hasMetadata) { - await queryRunner.broadcaster.broadcast("BeforeUpdate", this.expressionMap.mainAlias!.metadata); + if (this.expressionMap.queryType === "soft-delete") + await queryRunner.broadcaster.broadcast("BeforeSoftRemove", this.expressionMap.mainAlias!.metadata); + else if (this.expressionMap.queryType === "restore") + await queryRunner.broadcaster.broadcast("BeforeRecover", this.expressionMap.mainAlias!.metadata); } // if update entity mode is enabled we may need extra columns for the returning statement @@ -89,9 +92,12 @@ export class SoftDeleteQueryBuilder extends QueryBuilder impleme await returningResultsEntityUpdator.update(updateResult, this.expressionMap.whereEntities); } - // call after updation methods in listeners and subscribers + // call after soft remove and recover methods in listeners and subscribers if (this.expressionMap.callListeners === true && this.expressionMap.mainAlias!.hasMetadata) { - await queryRunner.broadcaster.broadcast("AfterUpdate", this.expressionMap.mainAlias!.metadata); + if (this.expressionMap.queryType === "soft-delete") + await queryRunner.broadcaster.broadcast("AfterSoftRemove", this.expressionMap.mainAlias!.metadata); + else if (this.expressionMap.queryType === "restore") + await queryRunner.broadcaster.broadcast("AfterRecover", this.expressionMap.mainAlias!.metadata); } // close transaction if we started it diff --git a/src/subscriber/Broadcaster.ts b/src/subscriber/Broadcaster.ts index 1c5312f9bc..515a87c4cb 100644 --- a/src/subscriber/Broadcaster.ts +++ b/src/subscriber/Broadcaster.ts @@ -23,6 +23,12 @@ interface BroadcasterEvents { "BeforeRemove": (metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral) => void; "AfterRemove": (metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral) => void; + "BeforeSoftRemove": (metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral) => void; + "AfterSoftRemove": (metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral) => void; + + "BeforeRecover": (metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral) => void; + "AfterRecover": (metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral) => void; + "Load": (metadata: EntityMetadata, entities: ObjectLiteral[]) => void; } @@ -179,6 +185,86 @@ export class Broadcaster { } } + /** + * Broadcasts "BEFORE_SOFT_REMOVE" event. + * Before soft remove event is executed before entity is being soft removed from the database. + * All subscribers and entity listeners who listened to this event will be executed at this point. + * Subscribers and entity listeners can return promises, it will wait until they are resolved. + * + * Note: this method has a performance-optimized code organization, do not change code structure. + */ + broadcastBeforeSoftRemoveEvent(result: BroadcasterResult, metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral): void { + if (entity && metadata.beforeSoftRemoveListeners.length) { + metadata.beforeSoftRemoveListeners.forEach(listener => { + if (listener.isAllowed(entity)) { + const executionResult = listener.execute(entity); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + + if (this.queryRunner.connection.subscribers.length) { + this.queryRunner.connection.subscribers.forEach(subscriber => { + if (this.isAllowedSubscriber(subscriber, metadata.target) && subscriber.beforeSoftRemove) { + const executionResult = subscriber.beforeSoftRemove({ + connection: this.queryRunner.connection, + queryRunner: this.queryRunner, + manager: this.queryRunner.manager, + entity: entity, + metadata: metadata, + databaseEntity: databaseEntity, + entityId: metadata.getEntityIdMixedMap(databaseEntity) + }); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + } + + /** + * Broadcasts "BEFORE_RECOVER" event. + * Before recover event is executed before entity is being recovered in the database. + * All subscribers and entity listeners who listened to this event will be executed at this point. + * Subscribers and entity listeners can return promises, it will wait until they are resolved. + * + * Note: this method has a performance-optimized code organization, do not change code structure. + */ + broadcastBeforeRecoverEvent(result: BroadcasterResult, metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral): void { + if (entity && metadata.beforeRecoverListeners.length) { + metadata.beforeRecoverListeners.forEach(listener => { + if (listener.isAllowed(entity)) { + const executionResult = listener.execute(entity); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + + if (this.queryRunner.connection.subscribers.length) { + this.queryRunner.connection.subscribers.forEach(subscriber => { + if (this.isAllowedSubscriber(subscriber, metadata.target) && subscriber.beforeRecover) { + const executionResult = subscriber.beforeRecover({ + connection: this.queryRunner.connection, + queryRunner: this.queryRunner, + manager: this.queryRunner.manager, + entity: entity, + metadata: metadata, + databaseEntity: databaseEntity, + entityId: metadata.getEntityIdMixedMap(databaseEntity) + }); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + } + /** * Broadcasts "AFTER_INSERT" event. * After insert event is executed after entity is being persisted to the database for the first time. @@ -421,6 +507,88 @@ export class Broadcaster { } } + /** + * Broadcasts "AFTER_SOFT_REMOVE" event. + * After soft remove event is executed after entity is being soft removed from the database. + * All subscribers and entity listeners who listened to this event will be executed at this point. + * Subscribers and entity listeners can return promises, it will wait until they are resolved. + * + * Note: this method has a performance-optimized code organization, do not change code structure. + */ + broadcastAfterSoftRemoveEvent(result: BroadcasterResult, metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral): void { + + if (entity && metadata.afterSoftRemoveListeners.length) { + metadata.afterSoftRemoveListeners.forEach(listener => { + if (listener.isAllowed(entity)) { + const executionResult = listener.execute(entity); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + + if (this.queryRunner.connection.subscribers.length) { + this.queryRunner.connection.subscribers.forEach(subscriber => { + if (this.isAllowedSubscriber(subscriber, metadata.target) && subscriber.afterSoftRemove) { + const executionResult = subscriber.afterSoftRemove({ + connection: this.queryRunner.connection, + queryRunner: this.queryRunner, + manager: this.queryRunner.manager, + entity: entity, + metadata: metadata, + databaseEntity: databaseEntity, + entityId: metadata.getEntityIdMixedMap(databaseEntity) + }); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + } + + /** + * Broadcasts "AFTER_RECOVER" event. + * After recover event is executed after entity is being recovered in the database. + * All subscribers and entity listeners who listened to this event will be executed at this point. + * Subscribers and entity listeners can return promises, it will wait until they are resolved. + * + * Note: this method has a performance-optimized code organization, do not change code structure. + */ + broadcastAfterRecoverEvent(result: BroadcasterResult, metadata: EntityMetadata, entity?: ObjectLiteral, databaseEntity?: ObjectLiteral): void { + + if (entity && metadata.afterRecoverListeners.length) { + metadata.afterRecoverListeners.forEach(listener => { + if (listener.isAllowed(entity)) { + const executionResult = listener.execute(entity); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + + if (this.queryRunner.connection.subscribers.length) { + this.queryRunner.connection.subscribers.forEach(subscriber => { + if (this.isAllowedSubscriber(subscriber, metadata.target) && subscriber.afterRecover) { + const executionResult = subscriber.afterRecover({ + connection: this.queryRunner.connection, + queryRunner: this.queryRunner, + manager: this.queryRunner.manager, + entity: entity, + metadata: metadata, + databaseEntity: databaseEntity, + entityId: metadata.getEntityIdMixedMap(databaseEntity) + }); + if (executionResult instanceof Promise) + result.promises.push(executionResult); + result.count++; + } + }); + } + } + /** * @deprecated Use `broadcastLoadForAllEvent` */ diff --git a/src/subscriber/EntitySubscriberInterface.ts b/src/subscriber/EntitySubscriberInterface.ts index 3cfb577664..6e3f4448c2 100644 --- a/src/subscriber/EntitySubscriberInterface.ts +++ b/src/subscriber/EntitySubscriberInterface.ts @@ -5,6 +5,8 @@ import {UpdateEvent} from "./event/UpdateEvent"; import {RemoveEvent} from "./event/RemoveEvent"; import {InsertEvent} from "./event/InsertEvent"; import {LoadEvent} from "./event/LoadEvent"; +import { SoftRemoveEvent } from "./event/SoftRemoveEvent"; +import { RecoverEvent } from "./event/RecoverEvent"; /** * Classes that implement this interface are subscribers that subscribe for the specific events in the ORM. @@ -52,11 +54,31 @@ export interface EntitySubscriberInterface { */ beforeRemove?(event: RemoveEvent): Promise|void; + /** + * Called before entity is soft removed from the database. + */ + beforeSoftRemove?(event: SoftRemoveEvent): Promise|void; + + /** + * Called before entity is recovered in the database. + */ + beforeRecover?(event: RecoverEvent): Promise|void; + /** * Called after entity is removed from the database. */ afterRemove?(event: RemoveEvent): Promise|void; + /** + * Called after entity is soft removed from the database. + */ + afterSoftRemove?(event: SoftRemoveEvent): Promise|void; + + /** + * Called after entity is recovered in the database. + */ + afterRecover?(event: RecoverEvent): Promise|void; + /** * Called before transaction is started. */ diff --git a/src/subscriber/event/RecoverEvent.ts b/src/subscriber/event/RecoverEvent.ts new file mode 100644 index 0000000000..9c6eafd081 --- /dev/null +++ b/src/subscriber/event/RecoverEvent.ts @@ -0,0 +1,6 @@ +import { RemoveEvent } from "./RemoveEvent"; + +/** + * RecoverEvent is an object that broadcaster sends to the entity subscriber when entity is being recovered in the database. + */ +export interface RecoverEvent extends RemoveEvent {} diff --git a/src/subscriber/event/SoftRemoveEvent.ts b/src/subscriber/event/SoftRemoveEvent.ts new file mode 100644 index 0000000000..89f4bd20f3 --- /dev/null +++ b/src/subscriber/event/SoftRemoveEvent.ts @@ -0,0 +1,6 @@ +import { RemoveEvent } from "./RemoveEvent"; + +/** + * SoftRemoveEvent is an object that broadcaster sends to the entity subscriber when entity is being soft removed to the database. + */ +export interface SoftRemoveEvent extends RemoveEvent {} diff --git a/test/functional/persistence/persistence-options/listeners/entity/PostWithDeleteDateColumn.ts b/test/functional/persistence/persistence-options/listeners/entity/PostWithDeleteDateColumn.ts index 23d2cfb39d..62a55ca16a 100644 --- a/test/functional/persistence/persistence-options/listeners/entity/PostWithDeleteDateColumn.ts +++ b/test/functional/persistence/persistence-options/listeners/entity/PostWithDeleteDateColumn.ts @@ -1,9 +1,8 @@ import {Entity} from "../../../../../../src/decorator/entity/Entity"; import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; import {Column} from "../../../../../../src/decorator/columns/Column"; -import {BeforeUpdate} from "../../../../../../src/decorator/listeners/BeforeUpdate"; -import {AfterUpdate} from "../../../../../../src/decorator/listeners/AfterUpdate"; import {DeleteDateColumn} from "../../../../../../src/decorator/columns/DeleteDateColumn"; +import { AfterSoftRemove, BeforeSoftRemove } from "../../../../../../src"; @Entity() export class PostWithDeleteDateColumn { @@ -22,13 +21,13 @@ export class PostWithDeleteDateColumn { isSoftRemoved: boolean = false; - @BeforeUpdate() - beforeUpdate() { + @BeforeSoftRemove() + beforeSoftRemove() { this.title += "!"; } - @AfterUpdate() - afterUpdate() { + @AfterSoftRemove() + afterSoftRemove() { this.isSoftRemoved = true; } diff --git a/test/github-issues/6327/subscriber/PostSubscriber.ts b/test/github-issues/6327/subscriber/PostSubscriber.ts index 01544b04d6..f48c09ceca 100644 --- a/test/github-issues/6327/subscriber/PostSubscriber.ts +++ b/test/github-issues/6327/subscriber/PostSubscriber.ts @@ -1,5 +1,7 @@ import { expect } from "chai"; -import { EntitySubscriberInterface, EventSubscriber, UpdateEvent } from "../../../../src"; +import { EntitySubscriberInterface, EventSubscriber } from "../../../../src"; +import { RecoverEvent } from "../../../../src/subscriber/event/RecoverEvent"; +import { SoftRemoveEvent } from "../../../../src/subscriber/event/SoftRemoveEvent"; import { Post } from "../entity/Post"; @EventSubscriber() @@ -8,17 +10,15 @@ export class PostSubscriber implements EntitySubscriberInterface { return Post; } - afterUpdate(event: UpdateEvent): void { - const { entity, queryRunner: { data } } = event; - - expect(["soft-delete", "restore"]).to.include(data!.action); + afterSoftRemove(event: SoftRemoveEvent): void { + const { entity } = event; + + expect(Object.prototype.toString.call(entity!.deletedAt)).to.be.eq("[object Date]"); + } - if (data!.action === "soft-delete") { - expect(Object.prototype.toString.call(entity!.deletedAt)).to.be.eq("[object Date]"); - } + afterRecover(event: RecoverEvent): void { + const { entity } = event; - if (data!.action === "restore") { - expect(entity!.deletedAt).to.be.null; - } + expect(entity!.deletedAt).to.be.null; } } diff --git a/test/github-issues/8398/entity/Post.ts b/test/github-issues/8398/entity/Post.ts new file mode 100644 index 0000000000..2761fc2c11 --- /dev/null +++ b/test/github-issues/8398/entity/Post.ts @@ -0,0 +1,90 @@ +import { + AfterRecover, + AfterSoftRemove, + AfterUpdate, + BeforeRecover, + BeforeSoftRemove, + BeforeUpdate, + Column, + DeleteDateColumn, + Entity, + PrimaryGeneratedColumn, +} from "../../../../src"; + +@Entity() +export class Post { + @PrimaryGeneratedColumn() + id: number; + + @Column() + data: string; + + @DeleteDateColumn() + deletedAt: Date; + + @Column({ default: 0 }) + beforeUpdateListener: number; + + @Column({ default: 0 }) + afterUpdateListener: number; + + @Column({ default: 0 }) + beforeSoftRemoveListener: number; + + @Column({ default: 0 }) + afterSoftRemoveListener: number; + + @Column({ default: 0 }) + beforeRecoverListener: number; + + @Column({ default: 0 }) + afterRecoverListener: number; + + @Column({ default: 0 }) + beforeUpdateSubscriber: number; + + @Column({ default: 0 }) + afterUpdateSubscriber: number; + + @Column({ default: 0 }) + beforeSoftRemoveSubscriber: number; + + @Column({ default: 0 }) + afterSoftRemoveSubscriber: number; + + @Column({ default: 0 }) + beforeRecoverSubscriber: number; + + @Column({ default: 0 }) + afterRecoverSubscriber: number; + + @BeforeUpdate() + beforeUpdate() { + this.beforeUpdateListener++; + } + + @AfterUpdate() + afterUpdate() { + this.afterUpdateListener++; + } + + @BeforeSoftRemove() + beforeSoftRemove() { + this.beforeSoftRemoveListener++; + } + + @AfterSoftRemove() + afterSoftRemove() { + this.afterSoftRemoveListener++; + } + + @BeforeRecover() + beforeRecover() { + this.beforeRecoverListener++; + } + + @AfterRecover() + afterRecover() { + this.afterRecoverListener++; + } +} diff --git a/test/github-issues/8398/issue-8398.ts b/test/github-issues/8398/issue-8398.ts new file mode 100644 index 0000000000..66fab59742 --- /dev/null +++ b/test/github-issues/8398/issue-8398.ts @@ -0,0 +1,52 @@ +import "reflect-metadata"; +import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils"; +import { Connection } from "../../../src/connection/Connection"; +import { Post } from "./entity/Post"; +import { expect } from "chai"; + +describe("github issues > #8398 Separate update event into the update, soft remove and restore events", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + subscribers: [__dirname + "/subscriber/*{.js,.ts}"], + schemaCreate: true, + dropSchema: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should trigger different events for update, soft remove, and recover", () => Promise.all(connections.map(async connection => { + + const manager = connection.manager; + + const post = new Post(); + post.data = "insert"; + await manager.save(post); + + post.data = "update"; + await manager.save(post); + await manager.softRemove(post); + const entity = await manager.recover(post); + + // Check if update listeners and subscribers are called only ones + expect(entity.beforeUpdateListener).to.be.eq(1); + expect(entity.afterUpdateListener).to.be.eq(1); + expect(entity.beforeUpdateSubscriber).to.be.eq(1); + expect(entity.afterUpdateSubscriber).to.be.eq(1); + + // Check if soft remove listeners and subscribers are called only ones + expect(entity.beforeSoftRemoveListener).to.be.eq(1); + expect(entity.afterSoftRemoveListener).to.be.eq(1); + expect(entity.beforeSoftRemoveSubscriber).to.be.eq(1); + expect(entity.afterSoftRemoveSubscriber).to.be.eq(1); + + // Check if recover listeners and subscribers are called only ones + expect(entity.beforeRecoverListener).to.be.eq(1); + expect(entity.afterRecoverListener).to.be.eq(1); + expect(entity.beforeRecoverSubscriber).to.be.eq(1); + expect(entity.afterRecoverSubscriber).to.be.eq(1); + + }))); + +}); diff --git a/test/github-issues/8398/subscriber/PostSubscriber.ts b/test/github-issues/8398/subscriber/PostSubscriber.ts new file mode 100644 index 0000000000..8087db9ec4 --- /dev/null +++ b/test/github-issues/8398/subscriber/PostSubscriber.ts @@ -0,0 +1,41 @@ +import { EntitySubscriberInterface, EventSubscriber, UpdateEvent } from "../../../../src"; +import { RecoverEvent } from "../../../../src/subscriber/event/RecoverEvent"; +import { SoftRemoveEvent } from "../../../../src/subscriber/event/SoftRemoveEvent"; +import { Post } from "../entity/Post"; + +@EventSubscriber() +export class PostSubscriber implements EntitySubscriberInterface { + listenTo() { + return Post; + } + + beforeUpdate(event: UpdateEvent): void { + const { entity } = event; + entity!.beforeUpdateSubscriber++; + } + + afterUpdate(event: UpdateEvent): void { + const { entity } = event; + entity!.afterUpdateSubscriber++; + } + + beforeSoftRemove(event: SoftRemoveEvent): void { + const { entity } = event; + entity!.beforeSoftRemoveSubscriber++; + } + + afterSoftRemove(event: SoftRemoveEvent): void { + const { entity } = event; + entity!.afterSoftRemoveSubscriber++; + } + + beforeRecover(event: RecoverEvent): void { + const { entity } = event; + entity!.beforeRecoverSubscriber++; + } + + afterRecover(event: RecoverEvent): void { + const { entity } = event; + entity!.afterRecoverSubscriber++; + } +}