diff --git a/package.json b/package.json index 9027efb..5096fdd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@technove/inject", "description": "Dependency injection for TypeScript", - "version": "0.1.7", + "version": "0.1.8", "author": "PaulBGD", "license": "MIT", "scripts": { diff --git a/src/container.spec.ts b/src/container.spec.ts index d3a1c9b..5adc9de 100644 --- a/src/container.spec.ts +++ b/src/container.spec.ts @@ -6,6 +6,7 @@ import { Container } from "./container"; import { Key } from "./key"; import { Service } from "./decorators/service"; import { Inject } from "./decorators/inject"; +import { PostInjection } from "./decorators/post-injection"; describe("Container", () => { it("should be able to create a container", () => { @@ -186,4 +187,53 @@ describe("Container", () => { } expect(() => container.get(B)).to.throw("found missing a @Inject"); }); + + it("calls after initialization", () => { + let ran = false; + + class A { + @PostInjection() + private afterInit() { + ran = true; + } + } + + const container = new Container(); + container.get(A); + expect(ran).to.be.equal(true); + + class Dependency { + public val = 5; + } + + let retrievedValue = 0; + class B { + @Inject() + private dep!: Dependency; + + @PostInjection() + private afterInit() { + retrievedValue = this.dep.val; + } + } + + container.get(B); + expect(retrievedValue).to.be.equal(5); + }); + + it("handles after initialization with promise", async () => { + let val = 0; + class A { + @PostInjection() + private async init() { + await new Promise((resolve) => setTimeout(resolve, 5)); + val = await Promise.resolve(5); + } + } + + const container = new Container(); + await container.load(A); + + expect(val).to.be.equal(5); + }); }); diff --git a/src/container.ts b/src/container.ts index 468cc22..e779c6c 100644 --- a/src/container.ts +++ b/src/container.ts @@ -119,11 +119,27 @@ export class Container { return val; }); - if (!anyPromises) { + const postInitialization = () => { + let anyPromises = false; + + const mapped = injectData.postInjection.map((func) => { + const val = func.call(service); + anyPromises ||= val instanceof Promise; + return val; + }); + + if (anyPromises) { + return Promise.all(mapped).then(() => service); + } + return service; + }; + + if (!anyPromises) { + return postInitialization(); } - return Promise.all(mapped).then(() => service); + return Promise.all(mapped).then(() => postInitialization()); }; return create(mappedParams); diff --git a/src/decorators/post-injection.ts b/src/decorators/post-injection.ts new file mode 100644 index 0000000..6326354 --- /dev/null +++ b/src/decorators/post-injection.ts @@ -0,0 +1,22 @@ +import { getInjectedData, InjectedData } from "../injector"; + +export const PostInjection: () => MethodDecorator = + () => (target, propertyKey) => { + if (typeof propertyKey !== "string") { + throw new Error("Only accepts string keys"); + } + + const data: InjectedData = getInjectedData(target); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const func = (target as any)[propertyKey]; + + if (typeof func !== "function") { + throw new Error("Expected function"); + } + + if (func.length !== 0) { + throw new Error("Expected function to have 0 parameters"); + } + + data.postInjection.push(func); + }; diff --git a/src/injector.ts b/src/injector.ts index 4b6fdd4..2861552 100644 --- a/src/injector.ts +++ b/src/injector.ts @@ -3,6 +3,7 @@ import { ParameterProvider, Provider } from "./types"; export interface InjectedData { properties: Provider[]; parameters: ParameterProvider[]; + postInjection: (() => Promise | unknown)[]; } const INJECT_KEY = "__inject__"; @@ -15,6 +16,7 @@ export function getInjectedData(Service: Object): InjectedData { const data: InjectedData = { properties: [], parameters: new Array(Service.constructor.length), + postInjection: [], }; Reflect.defineMetadata(INJECT_KEY, data, Service); return data;