diff --git a/packages/container/libraries/core/src/resolution/actions/resolveBindingActivations.spec.ts b/packages/container/libraries/core/src/resolution/actions/resolveBindingActivations.spec.ts new file mode 100644 index 00000000..29096616 --- /dev/null +++ b/packages/container/libraries/core/src/resolution/actions/resolveBindingActivations.spec.ts @@ -0,0 +1,263 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +import { ServiceIdentifier } from '@inversifyjs/common'; + +import { BindingActivation } from '../../binding/models/BindingActivation'; +import { ResolutionParams } from '../models/ResolutionParams'; +import { resolveBindingActivations } from './resolveBindingActivations'; + +describe(resolveBindingActivations.name, () => { + describe('having a non promise value', () => { + let paramsMock: jest.Mocked; + let serviceIdentifierFixture: ServiceIdentifier; + let valueFixture: unknown; + + beforeAll(() => { + paramsMock = { + getActivations: jest.fn(), + } as Partial< + jest.Mocked + > as jest.Mocked; + serviceIdentifierFixture = 'service-id'; + valueFixture = Symbol(); + }); + + describe('when called, and params.getActivations() returns undefined', () => { + let result: unknown; + + beforeAll(() => { + result = resolveBindingActivations( + paramsMock, + serviceIdentifierFixture, + valueFixture, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call params.getActivations', () => { + expect(paramsMock.getActivations).toHaveBeenCalledTimes(1); + expect(paramsMock.getActivations).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + + it('should return value', () => { + expect(result).toBe(valueFixture); + }); + }); + + describe('when called, and params.getActivations() returns sync activations', () => { + let activationMock: jest.Mock; + let activationResult: unknown; + + let result: unknown; + + beforeAll(() => { + activationResult = Symbol('activation-result'); + + activationMock = jest.fn().mockReturnValueOnce(activationResult); + + paramsMock.getActivations.mockReturnValueOnce([activationMock]); + + result = resolveBindingActivations( + paramsMock, + serviceIdentifierFixture, + valueFixture, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call params.getActivations', () => { + expect(paramsMock.getActivations).toHaveBeenCalledTimes(1); + expect(paramsMock.getActivations).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + + it('should call activation', () => { + expect(activationMock).toHaveBeenCalledTimes(1); + expect(activationMock).toHaveBeenCalledWith(valueFixture); + }); + + it('should return value', () => { + expect(result).toBe(activationResult); + }); + }); + + describe('when called, and params.getActivations() returns async activations', () => { + let activationMock: jest.Mock; + let activationResult: unknown; + + let result: unknown; + + beforeAll(async () => { + activationResult = Symbol('activation-result'); + + activationMock = jest + .fn() + .mockReturnValueOnce(Promise.resolve(activationResult)); + + paramsMock.getActivations.mockReturnValueOnce([activationMock]); + + result = await resolveBindingActivations( + paramsMock, + serviceIdentifierFixture, + valueFixture, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call params.getActivations', () => { + expect(paramsMock.getActivations).toHaveBeenCalledTimes(1); + expect(paramsMock.getActivations).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + + it('should call activation', () => { + expect(activationMock).toHaveBeenCalledTimes(1); + expect(activationMock).toHaveBeenCalledWith(valueFixture); + }); + + it('should return value', () => { + expect(result).toBe(activationResult); + }); + }); + }); + + describe('having a promise value', () => { + let paramsMock: jest.Mocked; + let serviceIdentifierFixture: ServiceIdentifier; + let valueFixture: unknown; + + beforeAll(() => { + paramsMock = { + getActivations: jest.fn(), + } as Partial< + jest.Mocked + > as jest.Mocked; + serviceIdentifierFixture = 'service-id'; + valueFixture = Symbol(); + }); + + describe('when called, and params.getActivations() returns undefined', () => { + let result: unknown; + + beforeAll(async () => { + result = await resolveBindingActivations( + paramsMock, + serviceIdentifierFixture, + Promise.resolve(valueFixture), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call params.getActivations', () => { + expect(paramsMock.getActivations).toHaveBeenCalledTimes(1); + expect(paramsMock.getActivations).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + + it('should return value', () => { + expect(result).toBe(valueFixture); + }); + }); + + describe('when called, and params.getActivations() returns sync activations', () => { + let activationMock: jest.Mock; + let activationResult: unknown; + + let result: unknown; + + beforeAll(async () => { + activationResult = Symbol('activation-result'); + + activationMock = jest.fn().mockReturnValueOnce(activationResult); + + paramsMock.getActivations.mockReturnValueOnce([activationMock]); + + result = await resolveBindingActivations( + paramsMock, + serviceIdentifierFixture, + Promise.resolve(valueFixture), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call params.getActivations', () => { + expect(paramsMock.getActivations).toHaveBeenCalledTimes(1); + expect(paramsMock.getActivations).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + + it('should call activation', () => { + expect(activationMock).toHaveBeenCalledTimes(1); + expect(activationMock).toHaveBeenCalledWith(valueFixture); + }); + + it('should return value', () => { + expect(result).toBe(activationResult); + }); + }); + + describe('when called, and params.getActivations() returns async activations', () => { + let activationMock: jest.Mock; + let activationResult: unknown; + + let result: unknown; + + beforeAll(async () => { + activationResult = Symbol('activation-result'); + + activationMock = jest + .fn() + .mockReturnValueOnce(Promise.resolve(activationResult)); + + paramsMock.getActivations.mockReturnValueOnce([activationMock]); + + result = await resolveBindingActivations( + paramsMock, + serviceIdentifierFixture, + Promise.resolve(valueFixture), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call params.getActivations', () => { + expect(paramsMock.getActivations).toHaveBeenCalledTimes(1); + expect(paramsMock.getActivations).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + + it('should call activation', () => { + expect(activationMock).toHaveBeenCalledTimes(1); + expect(activationMock).toHaveBeenCalledWith(valueFixture); + }); + + it('should return value', () => { + expect(result).toBe(activationResult); + }); + }); + }); +}); diff --git a/packages/container/libraries/core/src/resolution/actions/resolveBindingActivations.ts b/packages/container/libraries/core/src/resolution/actions/resolveBindingActivations.ts index a54df235..e3ceb801 100644 --- a/packages/container/libraries/core/src/resolution/actions/resolveBindingActivations.ts +++ b/packages/container/libraries/core/src/resolution/actions/resolveBindingActivations.ts @@ -3,7 +3,7 @@ import { ServiceIdentifier } from '@inversifyjs/common'; import { BindingActivation } from '../../binding/models/BindingActivation'; import { isPromise } from '../../common/calculations/isPromise'; import { ResolutionParams } from '../models/ResolutionParams'; -import { Resolved } from '../models/Resolved'; +import { Resolved, SyncResolved } from '../models/Resolved'; export function resolveBindingActivations( params: ResolutionParams, @@ -13,28 +13,28 @@ export function resolveBindingActivations( const activations: Iterable> | undefined = params.getActivations(serviceIdentifier); - if (activations !== undefined) { - if (isPromise(value)) { - return resolveBindingActivationsFromIteratorAsync( - value, - activations[Symbol.iterator](), - ); - } else { - return resolveBindingActivationsFromIterator( - value, - activations[Symbol.iterator](), - ); - } + if (activations === undefined) { + return value; + } + + if (isPromise(value)) { + return resolveBindingActivationsFromIteratorAsync( + value, + activations[Symbol.iterator](), + ); } - return value; + return resolveBindingActivationsFromIterator( + value, + activations[Symbol.iterator](), + ); } function resolveBindingActivationsFromIterator( - value: Awaited, + value: SyncResolved, activationsIterator: Iterator>, ): Resolved { - let activatedValue: Awaited = value; + let activatedValue: SyncResolved = value; let activationIteratorResult: IteratorResult> = activationsIterator.next(); @@ -61,8 +61,8 @@ function resolveBindingActivationsFromIterator( async function resolveBindingActivationsFromIteratorAsync( value: Promise, activationsIterator: Iterator>, -): Promise> { - let activatedValue: Awaited = await value; +): Promise> { + let activatedValue: SyncResolved = await value; let activationIteratorResult: IteratorResult> = activationsIterator.next(); diff --git a/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.spec.ts b/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.spec.ts index 4e7096b9..afa8efeb 100644 --- a/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.spec.ts +++ b/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.spec.ts @@ -1,5 +1,7 @@ import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; +jest.mock('./resolvePostConstruct'); + import { bindingScopeValues } from '../../binding/models/BindingScope'; import { bindingTypeValues } from '../../binding/models/BindingType'; import { InstanceBinding } from '../../binding/models/InstanceBinding'; @@ -8,7 +10,9 @@ import { BindingNodeParent } from '../../planning/models/BindingNodeParent'; import { InstanceBindingNode } from '../../planning/models/InstanceBindingNode'; import { PlanServiceNode } from '../../planning/models/PlanServiceNode'; import { ResolutionParams } from '../models/ResolutionParams'; +import { Resolved, SyncResolved } from '../models/Resolved'; import { resolveInstanceBindingNodeFromConstructorParams } from './resolveInstanceBindingNodeFromConstructorParams'; +import { resolvePostConstruct } from './resolvePostConstruct'; describe(resolveInstanceBindingNodeFromConstructorParams.name, () => { let setInstancePropertiesMock: jest.Mock< @@ -44,13 +48,27 @@ describe(resolveInstanceBindingNodeFromConstructorParams.name, () => { serviceIdentifier: 'service-id', type: bindingTypeValues.Instance, }, - classMetadata: Symbol() as unknown as jest.Mocked, + classMetadata: { + constructorArguments: [], + lifecycle: { + postConstructMethodName: 'post-construct-method-name', + preDestroyMethodName: undefined, + }, + } as Partial> as jest.Mocked, constructorParams: [], parent: Symbol() as unknown as jest.Mocked, propertyParams: new Map() as jest.Mocked< Map >, }; + + ( + resolvePostConstruct as jest.Mock + ).mockImplementation( + ( + instance: SyncResolved & Record, + ): Resolved => instance, + ); }); describe('when called, and setInstanceProperties() returns undefined', () => { @@ -96,6 +114,15 @@ describe(resolveInstanceBindingNodeFromConstructorParams.name, () => { ); }); + it('should call resolvePostConstructor()', () => { + expect(resolvePostConstruct).toHaveBeenCalledTimes(1); + expect(resolvePostConstruct).toHaveBeenCalledWith( + expect.any(Object), + nodeMock.binding, + nodeMock.classMetadata.lifecycle.postConstructMethodName, + ); + }); + it('should return expected result', () => { const expectedResultProperties: Record = { [expectedResultProperty]: expectedResultValue, @@ -150,6 +177,15 @@ describe(resolveInstanceBindingNodeFromConstructorParams.name, () => { ); }); + it('should call resolvePostConstructor()', () => { + expect(resolvePostConstruct).toHaveBeenCalledTimes(1); + expect(resolvePostConstruct).toHaveBeenCalledWith( + expect.any(Object), + nodeMock.binding, + nodeMock.classMetadata.lifecycle.postConstructMethodName, + ); + }); + it('should return expected result', () => { const expectedResultProperties: Record = { [expectedResultProperty]: expectedResultValue, diff --git a/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.ts b/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.ts index 05d618d5..d605f497 100644 --- a/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.ts +++ b/packages/container/libraries/core/src/resolution/actions/resolveInstanceBindingNodeFromConstructorParams.ts @@ -3,6 +3,7 @@ import { isPromise } from '../../common/calculations/isPromise'; import { InstanceBindingNode } from '../../planning/models/InstanceBindingNode'; import { ResolutionParams } from '../models/ResolutionParams'; import { Resolved, SyncResolved } from '../models/Resolved'; +import { resolvePostConstruct } from './resolvePostConstruct'; export function resolveInstanceBindingNodeFromConstructorParams< TActivated, @@ -32,9 +33,20 @@ export function resolveInstanceBindingNodeFromConstructorParams< setInstanceProperties(params, instance, node); if (isPromise(propertiesAssignmentResult)) { - return propertiesAssignmentResult.then(() => instance); - } else { - return instance; + return propertiesAssignmentResult.then( + (): Resolved => + resolvePostConstruct( + instance, + node.binding, + node.classMetadata.lifecycle.postConstructMethodName, + ), + ); } + + return resolvePostConstruct( + instance, + node.binding, + node.classMetadata.lifecycle.postConstructMethodName, + ); }; } diff --git a/packages/container/libraries/core/src/resolution/actions/resolvePostConstruct.spec.ts b/packages/container/libraries/core/src/resolution/actions/resolvePostConstruct.spec.ts new file mode 100644 index 00000000..b0702350 --- /dev/null +++ b/packages/container/libraries/core/src/resolution/actions/resolvePostConstruct.spec.ts @@ -0,0 +1,334 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +import { bindingScopeValues } from '../../binding/models/BindingScope'; +import { bindingTypeValues } from '../../binding/models/BindingType'; +import { InstanceBinding } from '../../binding/models/InstanceBinding'; +import { InversifyCoreError } from '../../error/models/InversifyCoreError'; +import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind'; +import { resolvePostConstruct } from './resolvePostConstruct'; + +type ActivatedTest = Record; + +describe(resolvePostConstruct.name, () => { + describe('having undefined postConstructMethodName', () => { + let instanceFixture: ActivatedTest; + let bindingFixture: InstanceBinding; + let postConstructMethodNameFixture: undefined; + + beforeAll(() => { + instanceFixture = {}; + bindingFixture = Symbol() as unknown as InstanceBinding; + postConstructMethodNameFixture = undefined; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + result = resolvePostConstruct( + instanceFixture, + bindingFixture, + postConstructMethodNameFixture, + ); + }); + + it('should return instance', () => { + expect(result).toBe(instanceFixture); + }); + }); + }); + + describe('having instance with no properties and postConstructMethodName', () => { + let instanceFixture: ActivatedTest; + let bindingFixture: InstanceBinding; + let postConstructMethodNameFixture: string; + + beforeAll(() => { + instanceFixture = {}; + bindingFixture = { + cache: { + isRight: false, + value: undefined, + }, + id: 1, + implementationType: class Foo implements ActivatedTest { + [key: string | symbol]: unknown; + }, + isSatisfiedBy: () => true, + moduleId: undefined, + onActivation: undefined, + onDeactivation: undefined, + scope: bindingScopeValues.Singleton, + serviceIdentifier: 'service-id', + type: bindingTypeValues.Instance, + }; + postConstructMethodNameFixture = 'post-construct-name-fixture'; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + try { + void resolvePostConstruct( + instanceFixture, + bindingFixture, + postConstructMethodNameFixture, + ); + } catch (error: unknown) { + result = error; + } + }); + + it('should throw an InversifyCoreError', () => { + const expectedErrorProperties: Partial = { + kind: InversifyCoreErrorKind.resolution, + message: `Expecting a "${postConstructMethodNameFixture.toString()}" property when resolving "${bindingFixture.implementationType.name}" class @postConstruct decorated method, none found.`, + }; + + expect(result).toBeInstanceOf(InversifyCoreError); + expect(result).toStrictEqual( + expect.objectContaining(expectedErrorProperties), + ); + }); + }); + }); + + describe('having instance with no method properties and postConstructMethodName', () => { + let instanceFixture: ActivatedTest; + let bindingFixture: InstanceBinding; + let postConstructMethodNameFixture: string; + + beforeAll(() => { + bindingFixture = { + cache: { + isRight: false, + value: undefined, + }, + id: 1, + implementationType: class Foo implements ActivatedTest { + [key: string | symbol]: unknown; + }, + isSatisfiedBy: () => true, + moduleId: undefined, + onActivation: undefined, + onDeactivation: undefined, + scope: bindingScopeValues.Singleton, + serviceIdentifier: 'service-id', + type: bindingTypeValues.Instance, + }; + postConstructMethodNameFixture = 'post-construct-name-fixture'; + + instanceFixture = { + [postConstructMethodNameFixture]: 'non-method-value', + }; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + try { + void resolvePostConstruct( + instanceFixture, + bindingFixture, + postConstructMethodNameFixture, + ); + } catch (error: unknown) { + result = error; + } + }); + + it('should throw an InversifyCoreError', () => { + const expectedErrorProperties: Partial = { + kind: InversifyCoreErrorKind.resolution, + message: `Expecting a "${postConstructMethodNameFixture.toString()}" method when resolving "${bindingFixture.implementationType.name}" class @postConstruct decorated method, a non function property was found instead.`, + }; + + expect(result).toBeInstanceOf(InversifyCoreError); + expect(result).toStrictEqual( + expect.objectContaining(expectedErrorProperties), + ); + }); + }); + }); + + describe('having instance with method properties and postConstructMethodName', () => { + let instanceFixture: ActivatedTest; + let bindingFixture: InstanceBinding; + let postConstructMethodNameFixture: string; + + let postConstructMethodMock: jest.Mock<() => void | Promise>; + + beforeAll(() => { + bindingFixture = { + cache: { + isRight: false, + value: undefined, + }, + id: 1, + implementationType: class Foo implements ActivatedTest { + [key: string | symbol]: unknown; + }, + isSatisfiedBy: () => true, + moduleId: undefined, + onActivation: undefined, + onDeactivation: undefined, + scope: bindingScopeValues.Singleton, + serviceIdentifier: 'service-id', + type: bindingTypeValues.Instance, + }; + postConstructMethodNameFixture = 'post-construct-name-fixture'; + + postConstructMethodMock = jest.fn(); + + instanceFixture = { + [postConstructMethodNameFixture]: postConstructMethodMock, + }; + }); + + describe('when called, and postConstructMethod returns undefined', () => { + let result: unknown; + + beforeAll(() => { + result = resolvePostConstruct( + instanceFixture, + bindingFixture, + postConstructMethodNameFixture, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call post construct method', () => { + expect(postConstructMethodMock).toHaveBeenCalledTimes(1); + expect(postConstructMethodMock).toHaveBeenCalledWith(); + }); + + it('should return instance', () => { + expect(result).toBe(instanceFixture); + }); + }); + + describe('when called, and postConstructMethod throws an error', () => { + let errorFixture: Error; + + let result: unknown; + + beforeAll(() => { + errorFixture = new Error('Error fixture'); + + postConstructMethodMock.mockImplementationOnce((): never => { + throw errorFixture; + }); + + try { + void resolvePostConstruct( + instanceFixture, + bindingFixture, + postConstructMethodNameFixture, + ); + } catch (error: unknown) { + result = error; + } + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call post construct method', () => { + expect(postConstructMethodMock).toHaveBeenCalledTimes(1); + expect(postConstructMethodMock).toHaveBeenCalledWith(); + }); + + it('should throw an InversifyCoreError', () => { + const expectedErrorProperties: Partial = { + kind: InversifyCoreErrorKind.resolution, + message: `Unexpected error found when calling "${postConstructMethodNameFixture.toString()}" @postConstruct decorated method on class "${bindingFixture.implementationType.name}"`, + }; + + expect(result).toBeInstanceOf(InversifyCoreError); + expect(result).toStrictEqual( + expect.objectContaining(expectedErrorProperties), + ); + }); + }); + + describe('when called, and postConstructMethod returns a promise', () => { + let result: unknown; + + beforeAll(async () => { + postConstructMethodMock.mockReturnValueOnce(Promise.resolve(undefined)); + + result = await resolvePostConstruct( + instanceFixture, + bindingFixture, + postConstructMethodNameFixture, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call post construct method', () => { + expect(postConstructMethodMock).toHaveBeenCalledTimes(1); + expect(postConstructMethodMock).toHaveBeenCalledWith(); + }); + + it('should return instance', () => { + expect(result).toBe(instanceFixture); + }); + }); + + describe('when called, and postConstructMethod returns a rejected promise', () => { + let errorFixture: Error; + + let result: unknown; + + beforeAll(async () => { + errorFixture = new Error('Error fixture'); + + postConstructMethodMock.mockImplementationOnce( + async (): Promise => { + throw errorFixture; + }, + ); + + try { + await resolvePostConstruct( + instanceFixture, + bindingFixture, + postConstructMethodNameFixture, + ); + } catch (error: unknown) { + result = error; + } + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call post construct method', () => { + expect(postConstructMethodMock).toHaveBeenCalledTimes(1); + expect(postConstructMethodMock).toHaveBeenCalledWith(); + }); + + it('should throw an InversifyCoreError', () => { + const expectedErrorProperties: Partial = { + kind: InversifyCoreErrorKind.resolution, + message: `Unexpected error found when calling "${postConstructMethodNameFixture.toString()}" @postConstruct decorated method on class "${bindingFixture.implementationType.name}"`, + }; + + expect(result).toBeInstanceOf(InversifyCoreError); + expect(result).toStrictEqual( + expect.objectContaining(expectedErrorProperties), + ); + }); + }); + }); +}); diff --git a/packages/container/libraries/core/src/resolution/actions/resolvePostConstruct.ts b/packages/container/libraries/core/src/resolution/actions/resolvePostConstruct.ts new file mode 100644 index 00000000..8f934cbd --- /dev/null +++ b/packages/container/libraries/core/src/resolution/actions/resolvePostConstruct.ts @@ -0,0 +1,88 @@ +import { InstanceBinding } from '../../binding/models/InstanceBinding'; +import { isPromise } from '../../common/calculations/isPromise'; +import { InversifyCoreError } from '../../error/models/InversifyCoreError'; +import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind'; +import { Resolved, SyncResolved } from '../models/Resolved'; + +export function resolvePostConstruct( + instance: SyncResolved & Record, + binding: InstanceBinding, + postConstructMethodName: string | symbol | undefined, +): Resolved { + const postConstructResult: void | Promise = invokePostConstruct( + instance, + binding, + postConstructMethodName, + ); + + if (isPromise(postConstructResult)) { + return postConstructResult.then(() => instance); + } + + return instance; +} + +function invokePostConstruct( + instance: SyncResolved & Record, + binding: InstanceBinding, + postConstructMethodName: string | symbol | undefined, +): void | Promise { + if (postConstructMethodName === undefined) { + return; + } + + if (postConstructMethodName in instance) { + if (typeof instance[postConstructMethodName] === 'function') { + let postConstructResult: unknown; + + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + postConstructResult = instance[postConstructMethodName](); + } catch (error: unknown) { + throw new InversifyCoreError( + InversifyCoreErrorKind.resolution, + `Unexpected error found when calling "${postConstructMethodName.toString()}" @postConstruct decorated method on class "${binding.implementationType.name}"`, + { + cause: error, + }, + ); + } + + if (isPromise(postConstructResult)) { + return invokePostConstructAsync( + binding, + postConstructMethodName, + postConstructResult, + ); + } + } else { + throw new InversifyCoreError( + InversifyCoreErrorKind.resolution, + `Expecting a "${postConstructMethodName.toString()}" method when resolving "${binding.implementationType.name}" class @postConstruct decorated method, a non function property was found instead.`, + ); + } + } else { + throw new InversifyCoreError( + InversifyCoreErrorKind.resolution, + `Expecting a "${postConstructMethodName.toString()}" property when resolving "${binding.implementationType.name}" class @postConstruct decorated method, none found.`, + ); + } +} + +async function invokePostConstructAsync( + binding: InstanceBinding, + postConstructMethodName: string | symbol, + postConstructResult: Promise, +): Promise { + try { + await postConstructResult; + } catch (error: unknown) { + throw new InversifyCoreError( + InversifyCoreErrorKind.resolution, + `Unexpected error found when calling "${postConstructMethodName.toString()}" @postConstruct decorated method on class "${binding.implementationType.name}"`, + { + cause: error, + }, + ); + } +}