From 93536ac7c2199ed5ed9d088d0109e1ac9ce9a794 Mon Sep 17 00:00:00 2001 From: Livio Date: Thu, 3 Jan 2019 10:47:11 +0100 Subject: [PATCH 1/8] refactor(core): Move callModuleInitHook into dedicated file --- packages/core/hooks/on-module-init.hook.ts | 53 ++++++++++++ packages/core/injector/instance-trancient.ts | 35 ++++++++ packages/core/nest-application-context.ts | 91 +------------------- 3 files changed, 92 insertions(+), 87 deletions(-) create mode 100644 packages/core/hooks/on-module-init.hook.ts create mode 100644 packages/core/injector/instance-trancient.ts diff --git a/packages/core/hooks/on-module-init.hook.ts b/packages/core/hooks/on-module-init.hook.ts new file mode 100644 index 00000000000..a8ffb37fee0 --- /dev/null +++ b/packages/core/hooks/on-module-init.hook.ts @@ -0,0 +1,53 @@ +import iterate from 'iterare'; + +import { OnModuleInit } from '@nestjs/common'; +import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils'; + +import { Module } from '../injector/module'; +import { InstanceWrapper } from '../injector/instance-wrapper'; +import { getTransientInstances, getNonTransientInstances } from '../injector/instance-trancient'; + +/** + * Returns true or false if the given instance has a `onModuleInit` function + * + * @param instance The instance which should be checked + */ +function hasOnModuleInitHook(instance: unknown): instance is OnModuleInit { + return !isUndefined((instance as OnModuleInit).onModuleInit); +} + +/** + * Calls the given instances + */ +function callOperator(instances: InstanceWrapper[]): Promise[] { + return iterate(instances) + .filter(instance => !isNil(instance)) + .filter(hasOnModuleInitHook) + .map(async instance => (instance as any as OnModuleInit).onModuleInit()) + .toArray(); +} + +/** + * Calls the `onModuleInit` function on the module and its children + * (providers / controllers). + * + * @param module The module which will be initialized + */ +export async function callModuleInitHook(module: Module): Promise { + const providers = [...module.providers]; + // Module (class) instance is the first element of the providers array + // Lifecycle hook has to be called once all classes are properly initialized + const [_, { instance: moduleClassInstance }] = providers.shift(); + const instances = [...module.controllers, ...providers]; + + const nonTransientInstances = getNonTransientInstances(instances); + await Promise.all(callOperator(nonTransientInstances)); + + const transientInstances = getTransientInstances(instances); + await Promise.all(callOperator(transientInstances)); + + // Call the instance itself + if (moduleClassInstance && hasOnModuleInitHook(moduleClassInstance)) { + await (moduleClassInstance as OnModuleInit).onModuleInit(); + } +} diff --git a/packages/core/injector/instance-trancient.ts b/packages/core/injector/instance-trancient.ts new file mode 100644 index 00000000000..d7312d72547 --- /dev/null +++ b/packages/core/injector/instance-trancient.ts @@ -0,0 +1,35 @@ +import iterate from 'iterare'; + +import { InstanceWrapper } from './instance-wrapper'; + +/** + * Returns the instances which are transient + * @param instances The instances which should be checked whether they are transcient + */ +export function getTransientInstances( + instances: [string, InstanceWrapper][], +): InstanceWrapper[] { + return iterate(instances) + .filter(([_, wrapper]) => wrapper.isDependencyTreeStatic()) + .map(([_, wrapper]) => wrapper.getStaticTransientInstances()) + .flatten() + .filter(item => !!item) + .map(({ instance }: any) => instance) + .toArray() as InstanceWrapper[]; +} + +/** + * Returns the instances which are not transient + * @param instances The instances which should be checked whether they are transcient + */ +export function getNonTransientInstances( + instances: [string, InstanceWrapper][] +): InstanceWrapper[] { + return iterate(instances) + .filter( + ([key, wrapper]) => + wrapper.isDependencyTreeStatic() && !wrapper.isTransient, + ) + .map(([key, { instance }]) => instance) + .toArray() as InstanceWrapper[]; +} \ No newline at end of file diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index 3380bf7f95d..69d65b88713 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -15,6 +15,8 @@ import { NestContainer } from './injector/container'; import { ContainerScanner } from './injector/container-scanner'; import { Module } from './injector/module'; import { ModuleTokenFactory } from './injector/module-token-factory'; +import { callModuleInitHook } from './hooks/on-module-init.hook'; +import { callModuleBootstrapHook } from 'hooks/on-app-bootstrap.hook'; export class NestApplicationContext implements INestApplicationContext { private readonly moduleTokenFactory = new ModuleTokenFactory(); @@ -76,44 +78,10 @@ export class NestApplicationContext implements INestApplicationContext { protected async callInitHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of [...modulesContainer.values()].reverse()) { - await this.callModuleInitHook(module); + await callModuleInitHook(module); } } - protected async callModuleInitHook(module: Module): Promise { - const providers = [...module.providers]; - // Module (class) instance is the first element of the providers array - // Lifecycle hook has to be called once all classes are properly initialized - const [_, { instance: moduleClassInstance }] = providers.shift(); - const instances = [...module.controllers, ...providers]; - const callOperator = (list: any) => - list - .filter(instance => !isNil(instance)) - .filter(this.hasOnModuleInitHook) - .map(async instance => (instance as OnModuleInit).onModuleInit()); - - await Promise.all( - callOperator( - iterate(instances) - .filter( - ([key, wrapper]) => - wrapper.isDependencyTreeStatic() && !wrapper.isTransient, - ) - .map(([key, { instance }]) => instance), - ), - ); - const transientInstances = this.getTransientInstances(instances); - await Promise.all(callOperator(iterate(transientInstances))); - - if (moduleClassInstance && this.hasOnModuleInitHook(moduleClassInstance)) { - await (moduleClassInstance as OnModuleInit).onModuleInit(); - } - } - - protected hasOnModuleInitHook(instance: any): instance is OnModuleInit { - return !isUndefined((instance as OnModuleInit).onModuleInit); - } - protected async callDestroyHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of modulesContainer.values()) { @@ -161,49 +129,10 @@ export class NestApplicationContext implements INestApplicationContext { protected async callBootstrapHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of [...modulesContainer.values()].reverse()) { - await this.callModuleBootstrapHook(module); + await callModuleBootstrapHook(module); } } - protected async callModuleBootstrapHook(module: Module): Promise { - const providers = [...module.providers]; - const [_, { instance: moduleClassInstance }] = providers.shift(); - const instances = [...module.controllers, ...providers]; - - const callOperator = (list: any) => - list - .filter(instance => !isNil(instance)) - .filter(this.hasOnAppBotstrapHook) - .map(async instance => - (instance as OnApplicationBootstrap).onApplicationBootstrap(), - ); - - await Promise.all( - callOperator( - iterate(instances) - .filter( - ([key, wrapper]) => - wrapper.isDependencyTreeStatic() && !wrapper.isTransient, - ) - .map(([key, { instance }]) => instance), - ), - ); - const transientInstances = this.getTransientInstances(instances); - await Promise.all(callOperator(iterate(transientInstances))); - - if (moduleClassInstance && this.hasOnAppBotstrapHook(moduleClassInstance)) { - await (moduleClassInstance as OnApplicationBootstrap).onApplicationBootstrap(); - } - } - - protected hasOnAppBotstrapHook( - instance: any, - ): instance is OnApplicationBootstrap { - return !isUndefined( - (instance as OnApplicationBootstrap).onApplicationBootstrap, - ); - } - protected find( typeOrToken: Type | string | symbol, ): TResult { @@ -219,16 +148,4 @@ export class NestApplicationContext implements INestApplicationContext { TResult >(metatypeOrToken, contextModule); } - - private getTransientInstances( - instances: [string, InstanceWrapper][], - ): InstanceWrapper[] { - return iterate(instances) - .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) - .map(([key, wrapper]) => wrapper.getStaticTransientInstances()) - .flatten() - .filter(item => !!item) - .map(({ instance }: any) => instance) - .toArray() as InstanceWrapper[]; - } } From 6f6cf0189fcf69043e5a1636ec442a274e765c99 Mon Sep 17 00:00:00 2001 From: Livio Date: Thu, 3 Jan 2019 10:55:38 +0100 Subject: [PATCH 2/8] refactor(core): Move callOnAppBootstrapHook into dedicated file --- packages/core/hooks/on-app-bootstrap.hook.ts | 54 ++++++++++++++++++++ packages/core/nest-application-context.ts | 42 ++------------- 2 files changed, 57 insertions(+), 39 deletions(-) create mode 100644 packages/core/hooks/on-app-bootstrap.hook.ts diff --git a/packages/core/hooks/on-app-bootstrap.hook.ts b/packages/core/hooks/on-app-bootstrap.hook.ts new file mode 100644 index 00000000000..d4dcfcfd7c3 --- /dev/null +++ b/packages/core/hooks/on-app-bootstrap.hook.ts @@ -0,0 +1,54 @@ +import iterate from 'iterare'; + +import { isUndefined, isNil } from '@nestjs/common/utils/shared.utils'; +import { OnApplicationBootstrap } from '@nestjs/common'; + +import { Module } from '../injector/module'; +import { InstanceWrapper } from '../injector/instance-wrapper'; +import { getTransientInstances, getNonTransientInstances } from 'injector/instance-trancient'; + +/** + * Checks if the given instance has the `onApplicationBootstrap` function + * + * @param instance The instance which should be checked + */ +function hasOnAppBootstrapHook(instance: unknown): instance is OnApplicationBootstrap { + return !isUndefined( + (instance as OnApplicationBootstrap).onApplicationBootstrap, + ); +} + +/** + * Calls the given instances + */ +function callOperator(instances: InstanceWrapper[]): Promise[] { + return iterate(instances) + .filter(instance => !isNil(instance)) + .filter(hasOnAppBootstrapHook) + .map(async instance => (instance as any as OnApplicationBootstrap).onApplicationBootstrap()) + .toArray(); +} + +/** + * Calls the `onApplicationBootstrap` function on the module and its children + * (providers / controllers). + * + * @param module The module which will be initialized + */ +export async function callModuleBootstrapHook(module: Module): Promise { + const providers = [...module.providers]; + // Module (class) instance is the first element of the providers array + // Lifecycle hook has to be called once all classes are properly initialized + const [_, { instance: moduleClassInstance }] = providers.shift(); + const instances = [...module.controllers, ...providers]; + + const nonTransientInstances = getNonTransientInstances(instances); + await Promise.all(callOperator(nonTransientInstances)); + const transientInstances = getTransientInstances(instances); + await Promise.all(callOperator(transientInstances)); + + // Call the instance itself + if (moduleClassInstance && hasOnAppBootstrapHook(moduleClassInstance)) { + await (moduleClassInstance as OnApplicationBootstrap).onApplicationBootstrap(); + } +} diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index 69d65b88713..734c1395885 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -16,7 +16,8 @@ import { ContainerScanner } from './injector/container-scanner'; import { Module } from './injector/module'; import { ModuleTokenFactory } from './injector/module-token-factory'; import { callModuleInitHook } from './hooks/on-module-init.hook'; -import { callModuleBootstrapHook } from 'hooks/on-app-bootstrap.hook'; +import { callModuleBootstrapHook } from './hooks/on-app-bootstrap.hook'; +import { callModuleDestroyHook } from './hooks/on-module-destory.hook'; export class NestApplicationContext implements INestApplicationContext { private readonly moduleTokenFactory = new ModuleTokenFactory(); @@ -85,47 +86,10 @@ export class NestApplicationContext implements INestApplicationContext { protected async callDestroyHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of modulesContainer.values()) { - await this.callModuleDestroyHook(module); + await callModuleDestroyHook(module); } } - protected async callModuleDestroyHook(module: Module): Promise { - const providers = [...module.providers]; - // Module (class) instance is the first element of the providers array - // Lifecycle hook has to be called once all classes are properly destroyed - const [_, { instance: moduleClassInstance }] = providers.shift(); - const instances = [...module.controllers, ...providers]; - const callOperator = (list: any) => - list - .filter(instance => !isNil(instance)) - .filter(this.hasOnModuleDestroyHook) - .map(async instance => (instance as OnModuleDestroy).onModuleDestroy()); - - await Promise.all( - callOperator( - iterate(instances) - .filter( - ([key, wrapper]) => - wrapper.isDependencyTreeStatic() && !wrapper.isTransient, - ) - .map(([key, { instance }]) => instance), - ), - ); - const transientInstances = this.getTransientInstances(instances); - await Promise.all(callOperator(iterate(transientInstances))); - - if ( - moduleClassInstance && - this.hasOnModuleDestroyHook(moduleClassInstance) - ) { - await (moduleClassInstance as OnModuleDestroy).onModuleDestroy(); - } - } - - protected hasOnModuleDestroyHook(instance: any): instance is OnModuleDestroy { - return !isUndefined((instance as OnModuleDestroy).onModuleDestroy); - } - protected async callBootstrapHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of [...modulesContainer.values()].reverse()) { From d30c64d2a6bc647177ecad289de8b40f03c4ec7a Mon Sep 17 00:00:00 2001 From: Livio Date: Thu, 3 Jan 2019 10:56:36 +0100 Subject: [PATCH 3/8] refactor(core): Move call onModuleDestroyHook into dedicated file --- packages/core/hooks/on-module-destroy.hook.ts | 56 +++++++++++++++++++ packages/core/nest-application-context.ts | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 packages/core/hooks/on-module-destroy.hook.ts diff --git a/packages/core/hooks/on-module-destroy.hook.ts b/packages/core/hooks/on-module-destroy.hook.ts new file mode 100644 index 00000000000..0c00194aca8 --- /dev/null +++ b/packages/core/hooks/on-module-destroy.hook.ts @@ -0,0 +1,56 @@ +import iterate from 'iterare'; + +import { isUndefined, isNil } from '@nestjs/common/utils/shared.utils'; +import { OnModuleDestroy } from '@nestjs/common'; + +import { Module } from '../injector/module'; +import { InstanceWrapper } from '../injector/instance-wrapper'; +import { getTransientInstances, getNonTransientInstances } from '../injector/instance-trancient'; + +/** + * Returns true or false if the given instance has a `onModuleDestroy` function + * + * @param instance The instance which should be checked + */ +function hasOnModuleDestroyHook(instance: unknown): instance is OnModuleDestroy { + return !isUndefined((instance as OnModuleDestroy).onModuleDestroy); +} + +/** + * Calls the given instances onModuleDestroy hook + */ +function callOperator(instances: InstanceWrapper[]): Promise[] { + return iterate(instances) + .filter(instance => !isNil(instance)) + .filter(hasOnModuleDestroyHook) + .map(async instance => (instance as any as OnModuleDestroy).onModuleDestroy()) + .toArray(); +} + +/** + * Calls the `onModuleDestroy` function on the module and its children + * (providers / controllers). + * + * @param module The module which will be initialized + */ +export async function callModuleDestroyHook(module: Module): Promise { + const providers = [...module.providers]; + // Module (class) instance is the first element of the providers array + // Lifecycle hook has to be called once all classes are properly destroyed + const [_, { instance: moduleClassInstance }] = providers.shift(); + const instances = [...module.controllers, ...providers]; + + const nonTransientInstances = getNonTransientInstances(instances); + await Promise.all(callOperator(nonTransientInstances)); + + const transientInstances = getTransientInstances(instances); + await Promise.all(callOperator(transientInstances)); + + // Call the module instance itself + if ( + moduleClassInstance && + hasOnModuleDestroyHook(moduleClassInstance) + ) { + await (moduleClassInstance as OnModuleDestroy).onModuleDestroy(); + } +} diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index 734c1395885..ebf6c569263 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -17,7 +17,7 @@ import { Module } from './injector/module'; import { ModuleTokenFactory } from './injector/module-token-factory'; import { callModuleInitHook } from './hooks/on-module-init.hook'; import { callModuleBootstrapHook } from './hooks/on-app-bootstrap.hook'; -import { callModuleDestroyHook } from './hooks/on-module-destory.hook'; +import { callModuleDestroyHook } from './hooks/on-module-destroy.hook'; export class NestApplicationContext implements INestApplicationContext { private readonly moduleTokenFactory = new ModuleTokenFactory(); From 17fff02dbb32c3a5193ff8dcddf80975a56e8090 Mon Sep 17 00:00:00 2001 From: Livio Date: Thu, 3 Jan 2019 11:01:57 +0100 Subject: [PATCH 4/8] refactor(core): Cleanup unneeded imports in nest-application-context --- packages/core/nest-application-context.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index ebf6c569263..368ae52cdd9 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -1,15 +1,5 @@ -import { - INestApplicationContext, - Logger, - LoggerService, - OnApplicationBootstrap, - OnModuleDestroy, - OnModuleInit, -} from '@nestjs/common'; +import { INestApplicationContext, Logger, LoggerService } from '@nestjs/common'; import { Type } from '@nestjs/common/interfaces/type.interface'; -import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils'; -import { InstanceWrapper } from 'injector/instance-wrapper'; -import iterate from 'iterare'; import { UnknownModuleException } from './errors/exceptions/unknown-module.exception'; import { NestContainer } from './injector/container'; import { ContainerScanner } from './injector/container-scanner'; From c4eeb7bdb08ad3224d09a06272a383e4620cf544 Mon Sep 17 00:00:00 2001 From: Livio Date: Thu, 3 Jan 2019 11:04:23 +0100 Subject: [PATCH 5/8] refactor(core): add comments to the hook caller functions --- packages/core/hooks/on-app-bootstrap.hook.ts | 2 +- packages/core/nest-application-context.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/core/hooks/on-app-bootstrap.hook.ts b/packages/core/hooks/on-app-bootstrap.hook.ts index d4dcfcfd7c3..15ebae8e5c2 100644 --- a/packages/core/hooks/on-app-bootstrap.hook.ts +++ b/packages/core/hooks/on-app-bootstrap.hook.ts @@ -5,7 +5,7 @@ import { OnApplicationBootstrap } from '@nestjs/common'; import { Module } from '../injector/module'; import { InstanceWrapper } from '../injector/instance-wrapper'; -import { getTransientInstances, getNonTransientInstances } from 'injector/instance-trancient'; +import { getTransientInstances, getNonTransientInstances } from '../injector/instance-trancient'; /** * Checks if the given instance has the `onApplicationBootstrap` function diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index 368ae52cdd9..8e04ed14cd6 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -66,21 +66,33 @@ export class NestApplicationContext implements INestApplicationContext { Logger.overrideLogger(logger); } - protected async callInitHook(): Promise { + /** + * Calls the `callInitHook` function on the registered + * modules and its children. + */ + protected async callInitHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of [...modulesContainer.values()].reverse()) { await callModuleInitHook(module); } } - protected async callDestroyHook(): Promise { + /** + * Calls the `callDestroyHook` function on the registered + * modules and its children. + */ + protected async callDestroyHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of modulesContainer.values()) { await callModuleDestroyHook(module); } } - protected async callBootstrapHook(): Promise { + /** + * Calls the `onApplicationBootstrap` function on the registered + * modules and its children. + */ + protected async callBootstrapHook(): Promise { const modulesContainer = this.container.getModules(); for (const module of [...modulesContainer.values()].reverse()) { await callModuleBootstrapHook(module); From f5531822c3e0c663b5f04d102f555eac0402d6b1 Mon Sep 17 00:00:00 2001 From: Livio Date: Mon, 7 Jan 2019 20:20:56 +0100 Subject: [PATCH 6/8] feat(core): Add onApplicationShutdown hook --- packages/common/index.ts | 1 + packages/common/interfaces/index.ts | 1 + .../on-application-shutdown.interface.ts | 3 ++ packages/core/constants.ts | 14 +++++ packages/core/hooks/on-app-shutdown.hook.ts | 54 +++++++++++++++++++ packages/core/nest-application-context.ts | 27 +++++++++- packages/core/nest-application.ts | 1 + 7 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 packages/common/interfaces/on-application-shutdown.interface.ts create mode 100644 packages/core/hooks/on-app-shutdown.hook.ts diff --git a/packages/common/index.ts b/packages/common/index.ts index 3ba47ebb44b..0dc85fca239 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -29,6 +29,7 @@ export { NestMiddleware, NestModule, OnApplicationBootstrap, + OnApplicationShutdown, OnModuleDestroy, OnModuleInit, Paramtype, diff --git a/packages/common/interfaces/index.ts b/packages/common/interfaces/index.ts index e14039176cd..5e5eae6e47e 100644 --- a/packages/common/interfaces/index.ts +++ b/packages/common/interfaces/index.ts @@ -26,6 +26,7 @@ export * from './nest-application-context.interface'; export * from './nest-application.interface'; export * from './nest-microservice.interface'; export * from './on-application-bootstrap.interface'; +export * from './on-application-shutdown.interface'; export * from './request-mapping-metadata.interface'; export * from './scope-options.interface'; export * from './type.interface'; diff --git a/packages/common/interfaces/on-application-shutdown.interface.ts b/packages/common/interfaces/on-application-shutdown.interface.ts new file mode 100644 index 00000000000..249c3f5a192 --- /dev/null +++ b/packages/common/interfaces/on-application-shutdown.interface.ts @@ -0,0 +1,3 @@ +export interface OnApplicationShutdown { + onApplicationShutdown(signal: string): any; +} diff --git a/packages/core/constants.ts b/packages/core/constants.ts index 29d14d75c33..5a02bf533b9 100644 --- a/packages/core/constants.ts +++ b/packages/core/constants.ts @@ -9,3 +9,17 @@ export const APP_INTERCEPTOR = 'APP_INTERCEPTOR'; export const APP_PIPE = 'APP_PIPE'; export const APP_GUARD = 'APP_GUARD'; export const APP_FILTER = 'APP_FILTER'; +export const SHUTDOWN_SIGNALS = [ + 'SIGHUP', + 'SIGINT', + 'SIGQUIT', + 'SIGILL', + 'SIGTRAP', + 'SIGABRT', + 'SIGBUS', + 'SIGFPE', + 'SIGUSR1', + 'SIGSEGV', + 'SIGUSR2', + 'SIGTERM', +]; diff --git a/packages/core/hooks/on-app-shutdown.hook.ts b/packages/core/hooks/on-app-shutdown.hook.ts new file mode 100644 index 00000000000..8b16929f449 --- /dev/null +++ b/packages/core/hooks/on-app-shutdown.hook.ts @@ -0,0 +1,54 @@ +import iterate from 'iterare'; + +import { isUndefined, isNil } from '@nestjs/common/utils/shared.utils'; +import { OnApplicationShutdown } from '@nestjs/common'; + +import { Module } from '../injector/module'; +import { InstanceWrapper } from '../injector/instance-wrapper'; +import { getTransientInstances, getNonTransientInstances } from '../injector/instance-trancient'; + +/** + * Checks if the given instance has the `onApplicationShutdown` function + * + * @param instance The instance which should be checked + */ +function hasOnAppBootstrapHook(instance: unknown): instance is OnApplicationShutdown { + return !isUndefined( + (instance as OnApplicationShutdown).onApplicationShutdown, + ); +} + +/** + * Calls the given instances + */ +function callOperator(instances: InstanceWrapper[], signal: string): Promise[] { + return iterate(instances) + .filter(instance => !isNil(instance)) + .filter(hasOnAppBootstrapHook) + .map(async instance => (instance as any as OnApplicationShutdown).onApplicationShutdown(signal)) + .toArray(); +} + +/** + * Calls the `onApplicationShutdown` function on the module and its children + * (providers / controllers). + * + * @param module The module which will be initialized + */ +export async function callAppShutdownHook(module: Module, signal: string): Promise { + const providers = [...module.providers]; + // Module (class) instance is the first element of the providers array + // Lifecycle hook has to be called once all classes are properly initialized + const [_, { instance: moduleClassInstance }] = providers.shift(); + const instances = [...module.controllers, ...providers]; + + const nonTransientInstances = getNonTransientInstances(instances); + await Promise.all(callOperator(nonTransientInstances, signal)); + const transientInstances = getTransientInstances(instances); + await Promise.all(callOperator(transientInstances, signal)); + + // Call the instance itself + if (moduleClassInstance && hasOnAppBootstrapHook(moduleClassInstance)) { + await (moduleClassInstance as OnApplicationShutdown).onApplicationShutdown(signal); + } +} diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index 8e04ed14cd6..e4e505a895d 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -8,6 +8,8 @@ import { ModuleTokenFactory } from './injector/module-token-factory'; import { callModuleInitHook } from './hooks/on-module-init.hook'; import { callModuleBootstrapHook } from './hooks/on-app-bootstrap.hook'; import { callModuleDestroyHook } from './hooks/on-module-destroy.hook'; +import { callAppShutdownHook } from './hooks/on-app-shutdown.hook'; +import { SHUTDOWN_SIGNALS } from './constants'; export class NestApplicationContext implements INestApplicationContext { private readonly moduleTokenFactory = new ModuleTokenFactory(); @@ -55,6 +57,7 @@ export class NestApplicationContext implements INestApplicationContext { public async init(): Promise { await this.callInitHook(); await this.callBootstrapHook(); + await this.listenToShutdownSignals(); return this; } @@ -66,8 +69,17 @@ export class NestApplicationContext implements INestApplicationContext { Logger.overrideLogger(logger); } + protected listenToShutdownSignals() { + SHUTDOWN_SIGNALS.forEach((signal: any) => + process.on(signal, async () => { + await this.close(); + await this.callShutdownHook(signal); + }), + ); + } + /** - * Calls the `callInitHook` function on the registered + * Calls the `onModuleInit` function on the registered * modules and its children. */ protected async callInitHook(): Promise { @@ -78,7 +90,7 @@ export class NestApplicationContext implements INestApplicationContext { } /** - * Calls the `callDestroyHook` function on the registered + * Calls the `onModuleDestroy` function on the registered * modules and its children. */ protected async callDestroyHook(): Promise { @@ -99,6 +111,17 @@ export class NestApplicationContext implements INestApplicationContext { } } + /** + * Calls the `onApplicationShutdown` function on the registered + * modules and children. + */ + protected async callShutdownHook(signal: string): Promise { + const modulesContainer = this.container.getModules(); + for (const module of [...modulesContainer.values()].reverse()) { + await callAppShutdownHook(module, signal); + } + } + protected find( typeOrToken: Type | string | symbol, ): TResult { diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts index a84eea46fa1..70ab5bee338 100644 --- a/packages/core/nest-application.ts +++ b/packages/core/nest-application.ts @@ -134,6 +134,7 @@ export class NestApplication extends NestApplicationContext await this.callInitHook(); await this.registerRouterHooks(); await this.callBootstrapHook(); + await this.listenToShutdownSignals(); this.isInitialized = true; this.logger.log(MESSAGES.APPLICATION_READY); From 1022bad2fb4578b2be1d0765f300e524416584ae Mon Sep 17 00:00:00 2001 From: Livio Date: Mon, 7 Jan 2019 20:29:59 +0100 Subject: [PATCH 7/8] refactor(core): Add barrel file for hooks --- packages/core/hooks/index.ts | 4 ++++ packages/core/nest-application-context.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 packages/core/hooks/index.ts diff --git a/packages/core/hooks/index.ts b/packages/core/hooks/index.ts new file mode 100644 index 00000000000..08ae1acb6cc --- /dev/null +++ b/packages/core/hooks/index.ts @@ -0,0 +1,4 @@ +export * from './on-app-bootstrap.hook'; +export * from './on-app-shutdown.hook'; +export * from './on-module-destroy.hook'; +export * from './on-module-init.hook'; diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index e4e505a895d..76b83df987f 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -5,10 +5,12 @@ import { NestContainer } from './injector/container'; import { ContainerScanner } from './injector/container-scanner'; import { Module } from './injector/module'; import { ModuleTokenFactory } from './injector/module-token-factory'; -import { callModuleInitHook } from './hooks/on-module-init.hook'; -import { callModuleBootstrapHook } from './hooks/on-app-bootstrap.hook'; -import { callModuleDestroyHook } from './hooks/on-module-destroy.hook'; -import { callAppShutdownHook } from './hooks/on-app-shutdown.hook'; +import { + callModuleInitHook, + callModuleBootstrapHook, + callModuleDestroyHook, + callAppShutdownHook, +} from './hooks'; import { SHUTDOWN_SIGNALS } from './constants'; export class NestApplicationContext implements INestApplicationContext { From c735dc646db7871e8c32b562c192642ccf1fd038 Mon Sep 17 00:00:00 2001 From: Livio Date: Mon, 7 Jan 2019 22:13:02 +0100 Subject: [PATCH 8/8] test(core): Add tests for lifecycle hooks --- integration/hooks/.gitignore | 21 +++ integration/hooks/e2e/on-app-boostrap.spec.ts | 22 +++ integration/hooks/e2e/on-app-shutdown.spec.ts | 23 +++ .../hooks/e2e/on-module-destroy.spec.ts | 22 +++ integration/hooks/e2e/on-module-init.spec.ts | 22 +++ integration/hooks/package.json | 23 +++ integration/hooks/tsconfig.json | 22 +++ integration/hooks/tslint.json | 53 +++++++ package-lock.json | 132 +++++++++--------- .../on-application-shutdown.interface.ts | 2 +- packages/core/hooks/on-app-shutdown.hook.ts | 4 +- packages/core/nest-application-context.ts | 5 +- 12 files changed, 280 insertions(+), 71 deletions(-) create mode 100644 integration/hooks/.gitignore create mode 100644 integration/hooks/e2e/on-app-boostrap.spec.ts create mode 100644 integration/hooks/e2e/on-app-shutdown.spec.ts create mode 100644 integration/hooks/e2e/on-module-destroy.spec.ts create mode 100644 integration/hooks/e2e/on-module-init.spec.ts create mode 100644 integration/hooks/package.json create mode 100644 integration/hooks/tsconfig.json create mode 100644 integration/hooks/tslint.json diff --git a/integration/hooks/.gitignore b/integration/hooks/.gitignore new file mode 100644 index 00000000000..b5e5f97553a --- /dev/null +++ b/integration/hooks/.gitignore @@ -0,0 +1,21 @@ +# dependencies +/node_modules + +# IDE +/.idea +/.awcache +/.vscode + +# misc +npm-debug.log + +# example +/quick-start + +# tests +/test +/coverage +/.nyc_output + +# dist +/dist \ No newline at end of file diff --git a/integration/hooks/e2e/on-app-boostrap.spec.ts b/integration/hooks/e2e/on-app-boostrap.spec.ts new file mode 100644 index 00000000000..4d05bb8be81 --- /dev/null +++ b/integration/hooks/e2e/on-app-boostrap.spec.ts @@ -0,0 +1,22 @@ +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as Sinon from 'sinon'; +import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; + +@Injectable() +class TestInjectable implements OnApplicationBootstrap { + onApplicationBootstrap = Sinon.spy(); +} + +describe('OnApplicationBootstrap', () => { + it('should call onApplicationBootstrap when application starts', async () => { + const module = await Test.createTestingModule({ + providers: [TestInjectable], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + const instance = module.get(TestInjectable); + expect(instance.onApplicationBootstrap.called).to.be.true; + }); +}); diff --git a/integration/hooks/e2e/on-app-shutdown.spec.ts b/integration/hooks/e2e/on-app-shutdown.spec.ts new file mode 100644 index 00000000000..08dbe6cb157 --- /dev/null +++ b/integration/hooks/e2e/on-app-shutdown.spec.ts @@ -0,0 +1,23 @@ +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as Sinon from 'sinon'; +import { Injectable, OnApplicationShutdown } from '@nestjs/common'; +import { spawn } from 'child_process'; + +@Injectable() +class TestInjectable implements OnApplicationShutdown { + onApplicationShutdown = Sinon.spy(); +} + +describe('OnApplicationShutdown', () => { + it('should call onApplicationShutdown when application closes', async () => { + const module = await Test.createTestingModule({ + providers: [TestInjectable], + }).compile(); + + const app = module.createNestApplication(); + await app.close(); + const instance = module.get(TestInjectable); + expect(instance.onApplicationShutdown.called).to.be.true; + }); +}); diff --git a/integration/hooks/e2e/on-module-destroy.spec.ts b/integration/hooks/e2e/on-module-destroy.spec.ts new file mode 100644 index 00000000000..a15125f632a --- /dev/null +++ b/integration/hooks/e2e/on-module-destroy.spec.ts @@ -0,0 +1,22 @@ +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as Sinon from 'sinon'; +import { Injectable, OnModuleDestroy } from '@nestjs/common'; + +@Injectable() +class TestInjectable implements OnModuleDestroy { + onModuleDestroy = Sinon.spy(); +} + +describe('OnModuleDestroy', () => { + it('should call onModuleDestroy when application closes', async () => { + const module = await Test.createTestingModule({ + providers: [TestInjectable], + }).compile(); + + const app = module.createNestApplication(); + await app.close(); + const instance = module.get(TestInjectable); + expect(instance.onModuleDestroy.called).to.be.true; + }); +}); diff --git a/integration/hooks/e2e/on-module-init.spec.ts b/integration/hooks/e2e/on-module-init.spec.ts new file mode 100644 index 00000000000..fbc83103d18 --- /dev/null +++ b/integration/hooks/e2e/on-module-init.spec.ts @@ -0,0 +1,22 @@ +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as Sinon from 'sinon'; +import { Injectable, OnModuleInit } from '@nestjs/common'; + +@Injectable() +class TestInjectable implements OnModuleInit { + onModuleInit = Sinon.spy(); +} + +describe('OnModuleInit', () => { + it('should call onModuleInit when application starts', async () => { + const module = await Test.createTestingModule({ + providers: [TestInjectable], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + const instance = module.get(TestInjectable); + expect(instance.onModuleInit.called).to.be.true; + }); +}); diff --git a/integration/hooks/package.json b/integration/hooks/package.json new file mode 100644 index 00000000000..63740341603 --- /dev/null +++ b/integration/hooks/package.json @@ -0,0 +1,23 @@ +{ + "name": "nest-typescript-starter", + "version": "1.0.0", + "description": "Nest TypeScript starter repository", + "license": "MIT", + "scripts": { + "start": "ts-node src/main" + }, + "dependencies": { + "@nestjs/common": "^5.0.0", + "@nestjs/core": "^5.0.0", + "class-transformer": "^0.1.7", + "class-validator": "^0.7.2", + "reflect-metadata": "^0.1.12", + "rxjs": "^6.0.0", + "typescript": "^3.1.0" + }, + "devDependencies": { + "@types/node": "^7.0.41", + "supertest": "^3.0.0", + "ts-node": "^6.0.0" + } +} diff --git a/integration/hooks/tsconfig.json b/integration/hooks/tsconfig.json new file mode 100644 index 00000000000..c6354c56487 --- /dev/null +++ b/integration/hooks/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "allowJs": true, + "outDir": "./dist" + }, + "include": [ + "src/**/*", + "e2e/**/*" + ], + "exclude": [ + "node_modules", + ] +} \ No newline at end of file diff --git a/integration/hooks/tslint.json b/integration/hooks/tslint.json new file mode 100644 index 00000000000..fbbb57c94a7 --- /dev/null +++ b/integration/hooks/tslint.json @@ -0,0 +1,53 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "eofline": false, + "quotemark": [ + true, + "single" + ], + "ordered-imports": [ + false + ], + "max-line-length": [ + 150 + ], + "member-ordering": [ + false + ], + "curly": false, + "interface-name": [ + false + ], + "array-type": [ + false + ], + "member-access": [ + false + ], + "no-empty-interface": false, + "no-empty": false, + "arrow-parens": false, + "object-literal-sort-keys": false, + "no-unused-expression": false, + "max-classes-per-file": [ + false + ], + "variable-name": [ + false + ], + "one-line": [ + false + ], + "one-variable-per-declaration": [ + false + ] + }, + "rulesDirectory": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8349986e695..350a5771ac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7046,22 +7046,22 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "ansi-regex": { "version": "2.1.1", - "resolved": false, + "resolved": "", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { "version": "1.1.5", - "resolved": false, + "resolved": "", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { "delegates": "^1.0.0", @@ -7070,12 +7070,12 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "resolved": false, + "resolved": "", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", @@ -7084,32 +7084,32 @@ }, "chownr": { "version": "1.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "resolved": false, + "resolved": "", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "debug": { "version": "2.6.9", - "resolved": false, + "resolved": "", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" @@ -7117,22 +7117,22 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": false, + "resolved": "", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "delegates": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-libc": { "version": "1.0.3", - "resolved": false, + "resolved": "", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "fs-minipass": { "version": "1.2.5", - "resolved": false, + "resolved": "", "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "requires": { "minipass": "^2.2.1" @@ -7140,12 +7140,12 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", @@ -7160,7 +7160,7 @@ }, "glob": { "version": "7.1.2", - "resolved": false, + "resolved": "", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { "fs.realpath": "^1.0.0", @@ -7173,12 +7173,12 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "iconv-lite": { "version": "0.4.23", - "resolved": false, + "resolved": "", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -7186,7 +7186,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, + "resolved": "", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "requires": { "minimatch": "^3.0.4" @@ -7194,7 +7194,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", @@ -7203,17 +7203,17 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, + "resolved": "", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "resolved": false, + "resolved": "", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" @@ -7221,7 +7221,7 @@ }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "lodash": { @@ -7231,7 +7231,7 @@ }, "minimatch": { "version": "3.0.4", - "resolved": false, + "resolved": "", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" @@ -7239,12 +7239,12 @@ }, "minimist": { "version": "1.2.0", - "resolved": false, + "resolved": "", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minipass": { "version": "2.3.3", - "resolved": false, + "resolved": "", "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", "requires": { "safe-buffer": "^5.1.2", @@ -7253,7 +7253,7 @@ }, "minizlib": { "version": "1.1.0", - "resolved": false, + "resolved": "", "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "requires": { "minipass": "^2.2.1" @@ -7261,7 +7261,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -7269,19 +7269,19 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, "ms": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "needle": { "version": "2.2.2", - "resolved": false, + "resolved": "", "integrity": "sha512-mW7W8dKuVYefCpNzE3Z7xUmPI9wSrSL/1qH31YGMxmSOAnjatS3S9Zv3cmiHrhx3Jkp1SrWWBdOFXjfF48Uq3A==", "requires": { "debug": "^2.1.2", @@ -7291,7 +7291,7 @@ }, "node-pre-gyp": { "version": "0.10.3", - "resolved": false, + "resolved": "", "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", "requires": { "detect-libc": "^1.0.2", @@ -7308,7 +7308,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "requires": { "abbrev": "1", @@ -7317,12 +7317,12 @@ }, "npm-bundled": { "version": "1.0.3", - "resolved": false, + "resolved": "", "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==" }, "npm-packlist": { "version": "1.1.11", - "resolved": false, + "resolved": "", "integrity": "sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==", "requires": { "ignore-walk": "^3.0.1", @@ -7331,7 +7331,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, + "resolved": "", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "~1.1.2", @@ -7342,17 +7342,17 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" @@ -7360,17 +7360,17 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", - "resolved": false, + "resolved": "", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", @@ -7379,17 +7379,17 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "rc": { "version": "1.2.8", - "resolved": false, + "resolved": "", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { "deep-extend": "^0.6.0", @@ -7400,7 +7400,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -7414,7 +7414,7 @@ }, "rimraf": { "version": "2.6.2", - "resolved": false, + "resolved": "", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { "glob": "^7.0.5" @@ -7422,37 +7422,37 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, + "resolved": "", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", - "resolved": false, + "resolved": "", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "1.2.4", - "resolved": false, + "resolved": "", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { "version": "5.5.0", - "resolved": false, + "resolved": "", "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", @@ -7462,7 +7462,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -7470,7 +7470,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -7478,12 +7478,12 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "tar": { "version": "4.4.6", - "resolved": false, + "resolved": "", "integrity": "sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg==", "requires": { "chownr": "^1.0.1", @@ -7497,12 +7497,12 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "wide-align": { "version": "1.1.3", - "resolved": false, + "resolved": "", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "requires": { "string-width": "^1.0.2 || 2" @@ -7510,12 +7510,12 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } diff --git a/packages/common/interfaces/on-application-shutdown.interface.ts b/packages/common/interfaces/on-application-shutdown.interface.ts index 249c3f5a192..891fc80c732 100644 --- a/packages/common/interfaces/on-application-shutdown.interface.ts +++ b/packages/common/interfaces/on-application-shutdown.interface.ts @@ -1,3 +1,3 @@ export interface OnApplicationShutdown { - onApplicationShutdown(signal: string): any; + onApplicationShutdown(signal?: string): any; } diff --git a/packages/core/hooks/on-app-shutdown.hook.ts b/packages/core/hooks/on-app-shutdown.hook.ts index 8b16929f449..24e3cd2711d 100644 --- a/packages/core/hooks/on-app-shutdown.hook.ts +++ b/packages/core/hooks/on-app-shutdown.hook.ts @@ -21,7 +21,7 @@ function hasOnAppBootstrapHook(instance: unknown): instance is OnApplicationShut /** * Calls the given instances */ -function callOperator(instances: InstanceWrapper[], signal: string): Promise[] { +function callOperator(instances: InstanceWrapper[], signal?: string): Promise[] { return iterate(instances) .filter(instance => !isNil(instance)) .filter(hasOnAppBootstrapHook) @@ -35,7 +35,7 @@ function callOperator(instances: InstanceWrapper[], signal: string): Promise { +export async function callAppShutdownHook(module: Module, signal?: string): Promise { const providers = [...module.providers]; // Module (class) instance is the first element of the providers array // Lifecycle hook has to be called once all classes are properly initialized diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index 76b83df987f..193e4b9e2ab 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -65,6 +65,7 @@ export class NestApplicationContext implements INestApplicationContext { public async close(): Promise { await this.callDestroyHook(); + await this.callShutdownHook(); } public useLogger(logger: LoggerService) { @@ -74,7 +75,7 @@ export class NestApplicationContext implements INestApplicationContext { protected listenToShutdownSignals() { SHUTDOWN_SIGNALS.forEach((signal: any) => process.on(signal, async () => { - await this.close(); + await this.callDestroyHook(); await this.callShutdownHook(signal); }), ); @@ -117,7 +118,7 @@ export class NestApplicationContext implements INestApplicationContext { * Calls the `onApplicationShutdown` function on the registered * modules and children. */ - protected async callShutdownHook(signal: string): Promise { + protected async callShutdownHook(signal?: string): Promise { const modulesContainer = this.container.getModules(); for (const module of [...modulesContainer.values()].reverse()) { await callAppShutdownHook(module, signal);