diff --git a/integration/hooks/e2e/before-app-shutdown.spec.ts b/integration/hooks/e2e/before-app-shutdown.spec.ts index 152c5efd491..d6e81b40ed5 100644 --- a/integration/hooks/e2e/before-app-shutdown.spec.ts +++ b/integration/hooks/e2e/before-app-shutdown.spec.ts @@ -58,4 +58,48 @@ describe('BeforeApplicationShutdown', () => { bb.beforeApplicationShutdown, ); }); + + it('should sort components within a single module by injection hierarchy - ASC order', async () => { + @Injectable() + class A implements BeforeApplicationShutdown { + beforeApplicationShutdown = Sinon.spy(); + } + + @Injectable() + class AHost implements BeforeApplicationShutdown { + constructor(private a: A) {} + beforeApplicationShutdown = Sinon.spy(); + } + + @Injectable() + class Composition implements BeforeApplicationShutdown { + constructor( + private a: A, + private host: AHost, + ) {} + beforeApplicationShutdown = Sinon.spy(); + } + + @Module({ + providers: [AHost, A, Composition], + }) + class AModule {} + + const module = await Test.createTestingModule({ + imports: [AModule], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + await app.close(); + + const child = module.get(A); + const parent = module.get(AHost); + const composition = module.get(Composition); + Sinon.assert.callOrder( + composition.beforeApplicationShutdown, + parent.beforeApplicationShutdown, + child.beforeApplicationShutdown, + ); + }); }); diff --git a/integration/hooks/e2e/on-app-boostrap.spec.ts b/integration/hooks/e2e/on-app-boostrap.spec.ts index a5aa6a25355..8e59cfbfd97 100644 --- a/integration/hooks/e2e/on-app-boostrap.spec.ts +++ b/integration/hooks/e2e/on-app-boostrap.spec.ts @@ -82,4 +82,48 @@ describe('OnApplicationBootstrap', () => { const instance = module.get(AA); expect(instance.field).to.equal('b-field_a-field'); }); + + it('should sort components within a single module by injection hierarchy - DESC order', async () => { + @Injectable() + class A implements OnApplicationBootstrap { + onApplicationBootstrap = Sinon.spy(); + } + + @Injectable() + class AHost implements OnApplicationBootstrap { + constructor(private a: A) {} + onApplicationBootstrap = Sinon.spy(); + } + + @Injectable() + class Composition implements OnApplicationBootstrap { + constructor( + private a: A, + private host: AHost, + ) {} + onApplicationBootstrap = Sinon.spy(); + } + + @Module({ + providers: [AHost, A, Composition], + }) + class AModule {} + + const module = await Test.createTestingModule({ + imports: [AModule], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + await app.close(); + + const child = module.get(A); + const parent = module.get(AHost); + const composition = module.get(Composition); + Sinon.assert.callOrder( + child.onApplicationBootstrap, + parent.onApplicationBootstrap, + composition.onApplicationBootstrap, + ); + }); }); diff --git a/integration/hooks/e2e/on-app-shutdown.spec.ts b/integration/hooks/e2e/on-app-shutdown.spec.ts index 67518878cbd..39cd0f9cebd 100644 --- a/integration/hooks/e2e/on-app-shutdown.spec.ts +++ b/integration/hooks/e2e/on-app-shutdown.spec.ts @@ -55,4 +55,48 @@ describe('OnApplicationShutdown', () => { const bb = module.get(BB); Sinon.assert.callOrder(aa.onApplicationShutdown, bb.onApplicationShutdown); }); + + it('should sort components within a single module by injection hierarchy - ASC order', async () => { + @Injectable() + class A implements OnApplicationShutdown { + onApplicationShutdown = Sinon.spy(); + } + + @Injectable() + class AHost implements OnApplicationShutdown { + constructor(private a: A) {} + onApplicationShutdown = Sinon.spy(); + } + + @Injectable() + class Composition implements OnApplicationShutdown { + constructor( + private a: A, + private host: AHost, + ) {} + onApplicationShutdown = Sinon.spy(); + } + + @Module({ + providers: [AHost, A, Composition], + }) + class AModule {} + + const module = await Test.createTestingModule({ + imports: [AModule], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + await app.close(); + + const child = module.get(A); + const parent = module.get(AHost); + const composition = module.get(Composition); + Sinon.assert.callOrder( + composition.onApplicationShutdown, + parent.onApplicationShutdown, + child.onApplicationShutdown, + ); + }); }); diff --git a/integration/hooks/e2e/on-module-destroy.spec.ts b/integration/hooks/e2e/on-module-destroy.spec.ts index 52468cebea6..63f1dbe30b2 100644 --- a/integration/hooks/e2e/on-module-destroy.spec.ts +++ b/integration/hooks/e2e/on-module-destroy.spec.ts @@ -76,4 +76,48 @@ describe('OnModuleDestroy', () => { const bb = module.get(BB); Sinon.assert.callOrder(aa.onModuleDestroy, bb.onModuleDestroy); }); + + it('should sort components within a single module by injection hierarchy - ASC order', async () => { + @Injectable() + class A implements OnModuleDestroy { + onModuleDestroy = Sinon.spy(); + } + + @Injectable() + class AHost implements OnModuleDestroy { + constructor(private a: A) {} + onModuleDestroy = Sinon.spy(); + } + + @Injectable() + class Composition implements OnModuleDestroy { + constructor( + private a: A, + private host: AHost, + ) {} + onModuleDestroy = Sinon.spy(); + } + + @Module({ + providers: [AHost, A, Composition], + }) + class AModule {} + + const module = await Test.createTestingModule({ + imports: [AModule], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + await app.close(); + + const child = module.get(A); + const parent = module.get(AHost); + const composition = module.get(Composition); + Sinon.assert.callOrder( + composition.onModuleDestroy, + parent.onModuleDestroy, + child.onModuleDestroy, + ); + }); }); diff --git a/integration/hooks/e2e/on-module-init.spec.ts b/integration/hooks/e2e/on-module-init.spec.ts index d35a22d6987..d36787e693f 100644 --- a/integration/hooks/e2e/on-module-init.spec.ts +++ b/integration/hooks/e2e/on-module-init.spec.ts @@ -111,4 +111,48 @@ describe('OnModuleInit', () => { const instance = module.get(AA); expect(instance.field).to.equal('c-field_b-field_a-field'); }); + + it('should sort components within a single module by injection hierarchy - DESC order', async () => { + @Injectable() + class A implements OnModuleInit { + onModuleInit = Sinon.spy(); + } + + @Injectable() + class AHost implements OnModuleInit { + constructor(private a: A) {} + onModuleInit = Sinon.spy(); + } + + @Injectable() + class Composition implements OnModuleInit { + constructor( + private a: A, + private host: AHost, + ) {} + onModuleInit = Sinon.spy(); + } + + @Module({ + providers: [AHost, A, Composition], + }) + class AModule {} + + const module = await Test.createTestingModule({ + imports: [AModule], + }).compile(); + + const app = module.createNestApplication(); + await app.init(); + await app.close(); + + const child = module.get(A); + const parent = module.get(AHost); + const composition = module.get(Composition); + Sinon.assert.callOrder( + child.onModuleInit, + parent.onModuleInit, + composition.onModuleInit, + ); + }); }); diff --git a/packages/core/hooks/before-app-shutdown.hook.ts b/packages/core/hooks/before-app-shutdown.hook.ts index 42fbed515ab..55be421be64 100644 --- a/packages/core/hooks/before-app-shutdown.hook.ts +++ b/packages/core/hooks/before-app-shutdown.hook.ts @@ -1,12 +1,9 @@ import { BeforeApplicationShutdown } from '@nestjs/common'; import { isFunction, isNil } from '@nestjs/common/utils/shared.utils'; import { iterate } from 'iterare'; -import { - getNonTransientInstances, - getTransientInstances, -} from '../injector/helpers/transient-instances'; -import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; +import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level'; +import { getSortedHierarchyLevels } from './utils/get-sorted-hierarchy-levels'; /** * Checks if the given instance has the `beforeApplicationShutdown` function @@ -24,10 +21,7 @@ function hasBeforeApplicationShutdownHook( /** * Calls the given instances */ -function callOperator( - instances: InstanceWrapper[], - signal?: string, -): Promise[] { +function callOperator(instances: unknown[], signal?: string): Promise[] { return iterate(instances) .filter(instance => !isNil(instance)) .filter(hasBeforeApplicationShutdownHook) @@ -43,27 +37,26 @@ function callOperator( * Calls the `beforeApplicationShutdown` function on the module and its children * (providers / controllers). * - * @param module The module which will be initialized + * @param moduleRef The module which will be initialized * @param signal The signal which caused the shutdown */ export async function callBeforeAppShutdownHook( - module: Module, + moduleRef: Module, signal?: string, ): Promise { - const providers = module.getNonAliasProviders(); + const providers = moduleRef.getNonAliasProviders(); const [_, moduleClassHost] = providers.shift()!; - const instances = [ - ...module.controllers, - ...providers, - ...module.injectables, - ...module.middlewares, - ]; - - const nonTransientInstances = getNonTransientInstances(instances); - await Promise.all(callOperator(nonTransientInstances, signal)); - const transientInstances = getTransientInstances(instances); - await Promise.all(callOperator(transientInstances, signal)); + const groupedInstances = getInstancesGroupedByHierarchyLevel( + moduleRef.controllers, + moduleRef.injectables, + moduleRef.middlewares, + providers, + ); + const levels = getSortedHierarchyLevels(groupedInstances, 'DESC'); + for (const level of levels) { + await Promise.all(callOperator(groupedInstances.get(level)!, signal)); + } const moduleClassInstance = moduleClassHost.instance; if ( moduleClassInstance && diff --git a/packages/core/hooks/on-app-bootstrap.hook.ts b/packages/core/hooks/on-app-bootstrap.hook.ts index 9ec8ca6ebdf..76c8d6045ef 100644 --- a/packages/core/hooks/on-app-bootstrap.hook.ts +++ b/packages/core/hooks/on-app-bootstrap.hook.ts @@ -1,12 +1,9 @@ import { OnApplicationBootstrap } from '@nestjs/common'; import { isFunction, isNil } from '@nestjs/common/utils/shared.utils'; import { iterate } from 'iterare'; -import { - getNonTransientInstances, - getTransientInstances, -} from '../injector/helpers/transient-instances'; -import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; +import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level'; +import { getSortedHierarchyLevels } from './utils/get-sorted-hierarchy-levels'; /** * Checks if the given instance has the `onApplicationBootstrap` function @@ -24,7 +21,7 @@ function hasOnAppBootstrapHook( /** * Calls the given instances */ -function callOperator(instances: InstanceWrapper[]): Promise[] { +function callOperator(instances: unknown[]): Promise[] { return iterate(instances) .filter(instance => !isNil(instance)) .filter(hasOnAppBootstrapHook) @@ -38,24 +35,24 @@ function callOperator(instances: InstanceWrapper[]): Promise[] { * Calls the `onApplicationBootstrap` function on the module and its children * (providers / controllers). * - * @param module The module which will be initialized + * @param moduleRef The module which will be initialized */ -export async function callModuleBootstrapHook(module: Module): Promise { - const providers = module.getNonAliasProviders(); +export async function callModuleBootstrapHook(moduleRef: Module): Promise { + const providers = moduleRef.getNonAliasProviders(); // Module (class) instance is the first element of the providers array // Lifecycle hook has to be called once all classes are properly initialized const [_, moduleClassHost] = providers.shift()!; - const instances = [ - ...module.controllers, - ...providers, - ...module.injectables, - ...module.middlewares, - ]; + const groupedInstances = getInstancesGroupedByHierarchyLevel( + moduleRef.controllers, + moduleRef.injectables, + moduleRef.middlewares, + providers, + ); - const nonTransientInstances = getNonTransientInstances(instances); - await Promise.all(callOperator(nonTransientInstances)); - const transientInstances = getTransientInstances(instances); - await Promise.all(callOperator(transientInstances)); + const levels = getSortedHierarchyLevels(groupedInstances); + for (const level of levels) { + await Promise.all(callOperator(groupedInstances.get(level)!)); + } // Call the instance itself const moduleClassInstance = moduleClassHost.instance; diff --git a/packages/core/hooks/on-app-shutdown.hook.ts b/packages/core/hooks/on-app-shutdown.hook.ts index 3c9b2b900ed..650b239610d 100644 --- a/packages/core/hooks/on-app-shutdown.hook.ts +++ b/packages/core/hooks/on-app-shutdown.hook.ts @@ -1,12 +1,9 @@ import { OnApplicationShutdown } from '@nestjs/common'; import { isFunction, isNil } from '@nestjs/common/utils/shared.utils'; import { iterate } from 'iterare'; -import { - getNonTransientInstances, - getTransientInstances, -} from '../injector/helpers/transient-instances'; -import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; +import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level'; +import { getSortedHierarchyLevels } from './utils/get-sorted-hierarchy-levels'; /** * Checks if the given instance has the `onApplicationShutdown` function @@ -22,10 +19,7 @@ function hasOnAppShutdownHook( /** * Calls the given instances */ -function callOperator( - instances: InstanceWrapper[], - signal?: string, -): Promise[] { +function callOperator(instances: unknown[], signal?: string): Promise[] { return iterate(instances) .filter(instance => !isNil(instance)) .filter(hasOnAppShutdownHook) @@ -39,29 +33,28 @@ function callOperator( * Calls the `onApplicationShutdown` function on the module and its children * (providers / controllers). * - * @param module The module which will be initialized + * @param moduleRef The module which will be initialized * @param signal */ export async function callAppShutdownHook( - module: Module, + moduleRef: Module, signal?: string, ): Promise { - const providers = module.getNonAliasProviders(); + const providers = moduleRef.getNonAliasProviders(); // Module (class) instance is the first element of the providers array // Lifecycle hook has to be called once all classes are properly initialized const [_, moduleClassHost] = providers.shift()!; - const instances = [ - ...module.controllers, - ...providers, - ...module.injectables, - ...module.middlewares, - ]; - - const nonTransientInstances = getNonTransientInstances(instances); - await Promise.all(callOperator(nonTransientInstances, signal)); - const transientInstances = getTransientInstances(instances); - await Promise.all(callOperator(transientInstances, signal)); + const groupedInstances = getInstancesGroupedByHierarchyLevel( + moduleRef.controllers, + moduleRef.injectables, + moduleRef.middlewares, + providers, + ); + const levels = getSortedHierarchyLevels(groupedInstances, 'DESC'); + for (const level of levels) { + await Promise.all(callOperator(groupedInstances.get(level)!, signal)); + } // Call the instance itself const moduleClassInstance = moduleClassHost.instance; if ( diff --git a/packages/core/hooks/on-module-destroy.hook.ts b/packages/core/hooks/on-module-destroy.hook.ts index 8c2692372a5..f4ca0aa1c79 100644 --- a/packages/core/hooks/on-module-destroy.hook.ts +++ b/packages/core/hooks/on-module-destroy.hook.ts @@ -1,12 +1,9 @@ import { OnModuleDestroy } from '@nestjs/common'; import { isFunction, isNil } from '@nestjs/common/utils/shared.utils'; import { iterate } from 'iterare'; -import { - getNonTransientInstances, - getTransientInstances, -} from '../injector/helpers/transient-instances'; -import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; +import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level'; +import { getSortedHierarchyLevels } from './utils/get-sorted-hierarchy-levels'; /** * Returns true or false if the given instance has a `onModuleDestroy` function @@ -22,7 +19,7 @@ function hasOnModuleDestroyHook( /** * Calls the given instances onModuleDestroy hook */ -function callOperator(instances: InstanceWrapper[]): Promise[] { +function callOperator(instances: unknown[]): Promise[] { return iterate(instances) .filter(instance => !isNil(instance)) .filter(hasOnModuleDestroyHook) @@ -36,25 +33,24 @@ function callOperator(instances: InstanceWrapper[]): Promise[] { * Calls the `onModuleDestroy` function on the module and its children * (providers / controllers). * - * @param module The module which will be initialized + * @param moduleRef The module which will be initialized */ -export async function callModuleDestroyHook(module: Module): Promise { - const providers = module.getNonAliasProviders(); +export async function callModuleDestroyHook(moduleRef: Module): Promise { + const providers = moduleRef.getNonAliasProviders(); // Module (class) instance is the first element of the providers array // Lifecycle hook has to be called once all classes are properly destroyed const [_, moduleClassHost] = providers.shift()!; - const instances = [ - ...module.controllers, - ...providers, - ...module.injectables, - ...module.middlewares, - ]; + const groupedInstances = getInstancesGroupedByHierarchyLevel( + moduleRef.controllers, + moduleRef.injectables, + moduleRef.middlewares, + providers, + ); - const nonTransientInstances = getNonTransientInstances(instances); - await Promise.all(callOperator(nonTransientInstances)); - - const transientInstances = getTransientInstances(instances); - await Promise.all(callOperator(transientInstances)); + const levels = getSortedHierarchyLevels(groupedInstances, 'DESC'); + for (const level of levels) { + await Promise.all(callOperator(groupedInstances.get(level)!)); + } // Call the module instance itself const moduleClassInstance = moduleClassHost.instance; diff --git a/packages/core/hooks/on-module-init.hook.ts b/packages/core/hooks/on-module-init.hook.ts index 029eaecadf3..258d63dd6fc 100644 --- a/packages/core/hooks/on-module-init.hook.ts +++ b/packages/core/hooks/on-module-init.hook.ts @@ -1,12 +1,9 @@ import { OnModuleInit } from '@nestjs/common'; import { isFunction, isNil } from '@nestjs/common/utils/shared.utils'; import { iterate } from 'iterare'; -import { - getNonTransientInstances, - getTransientInstances, -} from '../injector/helpers/transient-instances'; -import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; +import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level'; +import { getSortedHierarchyLevels } from './utils/get-sorted-hierarchy-levels'; /** * Returns true or false if the given instance has a `onModuleInit` function @@ -20,7 +17,7 @@ function hasOnModuleInitHook(instance: unknown): instance is OnModuleInit { /** * Calls the given instances */ -function callOperator(instances: InstanceWrapper[]): Promise[] { +function callOperator(instances: unknown[]): Promise[] { return iterate(instances) .filter(instance => !isNil(instance)) .filter(hasOnModuleInitHook) @@ -32,25 +29,25 @@ function callOperator(instances: InstanceWrapper[]): Promise[] { * Calls the `onModuleInit` function on the module and its children * (providers / controllers). * - * @param module The module which will be initialized + * @param moduleRef The module which will be initialized */ -export async function callModuleInitHook(module: Module): Promise { - const providers = module.getNonAliasProviders(); +export async function callModuleInitHook(moduleRef: Module): Promise { + const providers = moduleRef.getNonAliasProviders(); // Module (class) instance is the first element of the providers array // Lifecycle hook has to be called once all classes are properly initialized const [_, moduleClassHost] = providers.shift()!; - const instances = [ - ...module.controllers, - ...providers, - ...module.injectables, - ...module.middlewares, - ]; - const nonTransientInstances = getNonTransientInstances(instances); - await Promise.all(callOperator(nonTransientInstances)); + const groupedInstances = getInstancesGroupedByHierarchyLevel( + moduleRef.controllers, + moduleRef.injectables, + moduleRef.middlewares, + providers, + ); - const transientInstances = getTransientInstances(instances); - await Promise.all(callOperator(transientInstances)); + const levels = getSortedHierarchyLevels(groupedInstances); + for (const level of levels) { + await Promise.all(callOperator(groupedInstances.get(level)!)); + } // Call the instance itself const moduleClassInstance = moduleClassHost.instance; diff --git a/packages/core/hooks/utils/get-instances-grouped-by-hierarchy-level.ts b/packages/core/hooks/utils/get-instances-grouped-by-hierarchy-level.ts new file mode 100644 index 00000000000..272f1b5a982 --- /dev/null +++ b/packages/core/hooks/utils/get-instances-grouped-by-hierarchy-level.ts @@ -0,0 +1,40 @@ +import { InjectionToken } from '@nestjs/common'; +import { InstanceWrapper } from '../../injector/instance-wrapper'; + +export function getInstancesGroupedByHierarchyLevel( + ...collections: Array< + | Map + | Array<[InjectionToken, InstanceWrapper]> + > +): Map { + const groupedByHierarchyLevel = new Map(); + + for (const collection of collections) { + for (const [_, wrapper] of collection) { + if (!wrapper.isDependencyTreeStatic()) { + continue; + } + + const level = wrapper.hierarchyLevel; + if (!groupedByHierarchyLevel.has(level)) { + groupedByHierarchyLevel.set(level, []); + } + + const byHierarchyLevelGroup = groupedByHierarchyLevel.get(level); + if (wrapper.isTransient) { + const staticTransientInstances = wrapper + .getStaticTransientInstances() + .filter(i => !!i) + .map(i => i.instance); + byHierarchyLevelGroup!.push(...staticTransientInstances); + continue; + } + + if (wrapper.instance) { + byHierarchyLevelGroup!.push(wrapper.instance); + } + } + } + + return groupedByHierarchyLevel; +} diff --git a/packages/core/hooks/utils/get-sorted-hierarchy-levels.ts b/packages/core/hooks/utils/get-sorted-hierarchy-levels.ts new file mode 100644 index 00000000000..1a71f972b77 --- /dev/null +++ b/packages/core/hooks/utils/get-sorted-hierarchy-levels.ts @@ -0,0 +1,11 @@ +export function getSortedHierarchyLevels( + groups: Map, + order: 'ASC' | 'DESC' = 'ASC', +): number[] { + const comparator = + order === 'ASC' + ? (a: number, b: number) => a - b + : (a: number, b: number) => b - a; + const levels = Array.from(groups.keys()).sort(comparator); + return levels; +} diff --git a/packages/core/injector/helpers/transient-instances.ts b/packages/core/injector/helpers/transient-instances.ts deleted file mode 100644 index 4e7f4afd6cb..00000000000 --- a/packages/core/injector/helpers/transient-instances.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { InjectionToken } from '@nestjs/common'; -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 transient - */ -export function getTransientInstances( - instances: [InjectionToken, 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 transient - */ -export function getNonTransientInstances( - instances: [InjectionToken, InstanceWrapper][], -): InstanceWrapper[] { - return iterate(instances) - .filter( - ([key, wrapper]) => - wrapper.isDependencyTreeStatic() && !wrapper.isTransient, - ) - .map(([key, { instance }]) => instance) - .toArray() as InstanceWrapper[]; -} diff --git a/packages/core/injector/injector.ts b/packages/core/injector/injector.ts index d6b5d78a28a..637ba60dab5 100644 --- a/packages/core/injector/injector.ts +++ b/packages/core/injector/injector.ts @@ -145,7 +145,7 @@ export class Injector { } try { const t0 = this.getNowTimestamp(); - const callback = async (instances: unknown[]) => { + const callback = async (instances: unknown[], depth = 0) => { const properties = await this.resolveProperties( wrapper, moduleRef, @@ -162,6 +162,8 @@ export class Injector { inquirer, ); this.applyProperties(instance, properties); + + wrapper.hierarchyLevel = depth + 1; wrapper.initTime = this.getNowTimestamp() - t0; settlementSignal.complete(); }; @@ -272,7 +274,7 @@ export class Injector { wrapper: InstanceWrapper, moduleRef: Module, inject: InjectorDependency[] | undefined, - callback: (args: unknown[]) => void | Promise, + callback: (args: unknown[], depth?: number) => void | Promise, contextId = STATIC_CONTEXT, inquirer?: InstanceWrapper, parentInquirer?: InstanceWrapper, @@ -296,6 +298,7 @@ export class Injector { : this.getClassDependencies(wrapper); let isResolved = true; + let depth = 0; const resolveParam = async (param: unknown, index: number) => { try { if (this.isInquirer(param, parentInquirer)) { @@ -314,6 +317,10 @@ export class Injector { inquirer, index, ); + if (paramWrapper.hierarchyLevel > depth) { + depth = paramWrapper.hierarchyLevel; + } + const instanceHost = paramWrapper.getInstanceByContextId( this.getContextId(contextId, paramWrapper), inquirerId, @@ -331,7 +338,7 @@ export class Injector { } }; const instances = await Promise.all(dependencies.map(resolveParam)); - isResolved && (await callback(instances)); + isResolved && (await callback(instances, depth)); } public getClassDependencies( diff --git a/packages/core/injector/instance-wrapper.ts b/packages/core/injector/instance-wrapper.ts index 7a75095063d..b2836881e75 100644 --- a/packages/core/injector/instance-wrapper.ts +++ b/packages/core/injector/instance-wrapper.ts @@ -82,6 +82,15 @@ export class InstanceWrapper { | undefined; private isTreeStatic: boolean | undefined; private isTreeDurable: boolean | undefined; + private _hierarchyLevel = 0; + + get hierarchyLevel(): number { + return this._hierarchyLevel; + } + + set hierarchyLevel(level: number) { + this._hierarchyLevel = level; + } constructor( metadata: Partial> & Partial> = {},