diff --git a/docs/docs/assets/hooks-in-sequence.png b/docs/docs/assets/hooks-in-sequence.png new file mode 100644 index 0000000..7d9df94 Binary files /dev/null and b/docs/docs/assets/hooks-in-sequence.png differ diff --git a/docs/docs/assets/hooks-in-sequence.svg b/docs/docs/assets/hooks-in-sequence.svg new file mode 100644 index 0000000..bc10381 --- /dev/null +++ b/docs/docs/assets/hooks-in-sequence.svg @@ -0,0 +1,16 @@ + + + + + + + PlatformBuilder.bootstrap()load injectorload routesPlatformBuilder.listen()$onInit()init sync providers$beforeInit()$afterInit()resolve configurationinit async providers$beforeRoutesInit()$onRoutesInit()mount all controllers$afterRoutesInit()StepsHooksGroup steps$beforeListen()bind Http/Https Servers$afterListen()$onReady()Ts.ED application LifecyclePlatformBuilder.stop()$onDestroy()close connections \ No newline at end of file diff --git a/docs/docs/hooks.md b/docs/docs/hooks.md index e69de29..286018e 100644 --- a/docs/docs/hooks.md +++ b/docs/docs/hooks.md @@ -0,0 +1,205 @@ +# Hooks + +## Introduction + +Ts.ED emits different events during its initialization +phase (lifecycle). These lifecycle hooks provide visibility into these key life moments and the ability to act +when they occur. + +This schema resume the order of hooks regard to the providers: + +![hook in sequence](./assets/hooks-in-sequence.png) + +Here is the related code described by the previous schema: + +```typescript +async function bootstrap() { + try { + const platform = await PlatformExpress.bootstrap(Server); + await platform.listen(); + + process.on("SIGINT", () => { + platform.stop(); + }); + } catch (error) { + $log.error({event: "SERVER_BOOTSTRAP_ERROR", message: error.message, stack: error.stack}); + } +} + +bootstrap(); +``` + +All providers are called by the emitted `event` and any provider can also emit his own `event`. + +::: tip +By convention + +- A hook is always prefixed by `$`, +- A hook is emitted from a module, +- A module subscribe to a hook. + +::: + +## Subscribe to a hook + +### Server + +You can subscribe to a hook in your Server: + +```typescript +import {BeforeInit, Configuration} from "@tsed/common"; + +@Configuration({}) +class Server implements BeforeInit { + async $beforeInit(): Promise {} +} +``` + +### Module / Service + +You can subscribe to a hook in your @@Module@@ or @@Service@@: + +```typescript +import {Module, OnInit} from "@tsed/common"; + +@Module() +export class MyModule implements OnInit { + async $onInit(): Promise {} +} +``` + +::: tip Note +Database connection can be performed with Asynchronous Provider. See [custom providers](/docs/custom-providers) +::: + +### $onRequest/$onResponse + +Ts.ED provide a way to intercept the request and response event. You can listen these hooks by implementing a `$onRequest` and `$onResponse` methods +on an injectable service: + + +```typescript +import {Module} from "@tsed/di"; +import {PlatformContext} from "@tsed/common"; + +@Module() +class CustomContextModule { + $onRequest($ctx: PlatformContext) { + // do something + } + $onResponse($ctx: PlatformContext) { + // do something + } +} +``` + +### Custom provider + +Since `v6.110.0`, it's also possible to subscribe to a hook in a [custom provider](/docs/custom-providers): + +```typescript +import {Configuration, registerProvider} from "@tsed/di"; +import {DatabaseConnection} from "connection-lib"; + +export const CONNECTION = Symbol.for("CONNECTION"); + +registerProvider({ + provide: CONNECTION, + deps: [Configuration], + useFactory(configuration: Configuration) { + const options = configuration.get("myOptions"); + + return new DatabaseConnection(options); + }, + hooks: { + $onDestroy(connection) { + // called when provider instance is destroyed + return connection.close(); + } + } +}); +``` + +It's now easy to close database connection through the `hooks` property! + +## Emit event + +Emit event let the developers subscribe and implement his tasks. + +```ts +import {Inject, Module, InjectorService} from "@tsed/di"; + +export interface OnEvent { + $myEvent(value: string): Promise; +} + +@Module() +export class ModuleEmitter { + @Inject() + protected injector: InjectorService; + + async initSomething() { + // do something before + + await this.injector.emit("$myEvent"); // emit accept extra parameters forwarded to subscribers + + // do something after + } +} +``` + +A subscriber: + +```typescript +import {Module} from "@tsed/di"; +import {OnEvent} from "module-emitter"; + +@Module() +export class ModuleSubscriber extends OnEvent { + $alterEvent() { + // do something + } +} +``` + +## Alterable value event + +This feature let you emit an event with a value. All providers who subscribe to it can modify the value passed as a parameter and return a new value which will be passed to the next provider. + +```ts +// module-emitter +import {Inject, Module, InjectorService} from "@tsed/di"; + +export interface AlterEvent { + $alterEvent(value: string): Promise; +} + +@Module() +export class ModuleEmitter { + @Inject() + protected injector: InjectorService; + + async initSomething() { + // do something before + + const value = this.injector.alterAsync("$alterEvent", "hello"); // alterAsync and alter accept extra parameters forwarded to subscribers + + console.log(value); // "hello-world" + // do something after + } +} +``` + +A subscriber: + +```typescript +import {Module} from "@tsed/di"; +import {AlterEvent} from "module-emitter"; + +@Module() +export class ModuleSubscriber extends AlterEvent { + $alterEvent(value: any) { + return value + " world"; + } +} +```