diff --git a/package.json b/package.json index 9b25dd8fb..027e98366 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inversify", - "version": "4.10.0", + "version": "4.11.0", "description": "A powerful and lightweight inversion of control container for JavaScript and Node.js apps powered by TypeScript.", "main": "lib/inversify.js", "jsnext:main": "es/inversify.js", diff --git a/src/container/container.ts b/src/container/container.ts index a86235ad6..a542fec9b 100644 --- a/src/container/container.ts +++ b/src/container/container.ts @@ -91,53 +91,41 @@ class Container implements interfaces.Container { this._metadataReader = new MetadataReader(); } - public load(...modules: interfaces.ContainerModule[]): void { + public load(...modules: interfaces.ContainerModule[]) { - const setModuleId = (bindingToSyntax: any, moduleId: string) => { - bindingToSyntax._binding.moduleId = moduleId; - }; + const getHelpers = this._getContainerModuleHelpersFactory(); - const getBindFunction = (moduleId: string) => - (serviceIdentifier: interfaces.ServiceIdentifier) => { - const bindingToSyntax = this.bind.call(this, serviceIdentifier); - setModuleId(bindingToSyntax, moduleId); - return bindingToSyntax; - }; + for (const currentModule of modules) { - const getUnbindFunction = (moduleId: string) => - (serviceIdentifier: interfaces.ServiceIdentifier) => { - const _unbind = this.unbind.bind(this); - _unbind(serviceIdentifier); - }; + const containerModuleHelpers = getHelpers(currentModule.guid); - const getIsboundFunction = (moduleId: string) => - (serviceIdentifier: interfaces.ServiceIdentifier) => { - const _isBound = this.isBound.bind(this); - return _isBound(serviceIdentifier); - }; + currentModule.registry( + containerModuleHelpers.bindFunction, + containerModuleHelpers.unbindFunction, + containerModuleHelpers.isboundFunction, + containerModuleHelpers.rebindFunction + ); - const getRebindFunction = (moduleId: string) => - (serviceIdentifier: interfaces.ServiceIdentifier) => { - const bindingToSyntax = this.rebind.call(this, serviceIdentifier); - setModuleId(bindingToSyntax, moduleId); - return bindingToSyntax; - }; + } - modules.forEach((module) => { + } + + public async loadAsync(...modules: interfaces.AsyncContainerModule[]) { + + const getHelpers = this._getContainerModuleHelpersFactory(); - const bindFunction = getBindFunction(module.guid); - const unbindFunction = getUnbindFunction(module.guid); - const isboundFunction = getIsboundFunction(module.guid); - const rebindFunction = getRebindFunction(module.guid); + for (const currentModule of modules) { - module.registry( - bindFunction, - unbindFunction, - isboundFunction, - rebindFunction + const containerModuleHelpers = getHelpers(currentModule.guid); + + await currentModule.registry( + containerModuleHelpers.bindFunction, + containerModuleHelpers.unbindFunction, + containerModuleHelpers.isboundFunction, + containerModuleHelpers.rebindFunction ); - }); + } } @@ -278,6 +266,49 @@ class Container implements interfaces.Container { return tempContainer.get(constructorFunction); } + private _getContainerModuleHelpersFactory() { + + const setModuleId = (bindingToSyntax: any, moduleId: string) => { + bindingToSyntax._binding.moduleId = moduleId; + }; + + const getBindFunction = (moduleId: string) => + (serviceIdentifier: interfaces.ServiceIdentifier) => { + const _bind = this.bind.bind(this); + const bindingToSyntax = _bind(serviceIdentifier); + setModuleId(bindingToSyntax, moduleId); + return bindingToSyntax; + }; + + const getUnbindFunction = (moduleId: string) => + (serviceIdentifier: interfaces.ServiceIdentifier) => { + const _unbind = this.unbind.bind(this); + _unbind(serviceIdentifier); + }; + + const getIsboundFunction = (moduleId: string) => + (serviceIdentifier: interfaces.ServiceIdentifier) => { + const _isBound = this.isBound.bind(this); + return _isBound(serviceIdentifier); + }; + + const getRebindFunction = (moduleId: string) => + (serviceIdentifier: interfaces.ServiceIdentifier) => { + const _rebind = this.rebind.bind(this); + const bindingToSyntax = _rebind(serviceIdentifier); + setModuleId(bindingToSyntax, moduleId); + return bindingToSyntax; + }; + + return (mId: string) => ({ + bindFunction: getBindFunction(mId), + isboundFunction: getIsboundFunction(mId), + rebindFunction: getRebindFunction(mId), + unbindFunction: getUnbindFunction(mId) + }); + + } + // Prepares arguments required for resolution and // delegates resolution to _middleware if available // otherwise it delegates resolution to _planAndResolve diff --git a/src/container/container_module.ts b/src/container/container_module.ts index 54b2c3a7a..04cf54013 100644 --- a/src/container/container_module.ts +++ b/src/container/container_module.ts @@ -1,7 +1,7 @@ import { interfaces } from "../interfaces/interfaces"; import { guid } from "../utils/guid"; -class ContainerModule implements interfaces.ContainerModule { +export class ContainerModule implements interfaces.ContainerModule { public guid: string; public registry: interfaces.ContainerModuleCallBack; @@ -13,4 +13,14 @@ class ContainerModule implements interfaces.ContainerModule { } -export { ContainerModule }; +export class AsyncContainerModule implements interfaces.AsyncContainerModule { + + public guid: string; + public registry: interfaces.AsyncContainerModuleCallBack; + + public constructor(registry: interfaces.AsyncContainerModuleCallBack) { + this.guid = guid(); + this.registry = registry; + } + +} diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index f7eb1d1d2..53cca9f60 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -177,6 +177,7 @@ namespace interfaces { getAll(serviceIdentifier: ServiceIdentifier): T[]; resolve(constructorFunction: interfaces.Newable): T; load(...modules: ContainerModule[]): void; + loadAsync(...modules: AsyncContainerModule[]): Promise; unload(...modules: ContainerModule[]): void; applyCustomMetadataReader(metadataReader: MetadataReader): void; applyMiddleware(...middleware: Middleware[]): void; @@ -198,6 +199,11 @@ namespace interfaces { registry: ContainerModuleCallBack; } + export interface AsyncContainerModule { + guid: string; + registry: AsyncContainerModuleCallBack; + } + export type ContainerModuleCallBack = ( bind: interfaces.Bind, unbind: interfaces.Unbind, @@ -205,6 +211,13 @@ namespace interfaces { rebind: interfaces.Rebind ) => void; + export type AsyncContainerModuleCallBack = ( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ) => Promise; + export interface ContainerSnapshot { bindings: Lookup>; middleware: Next | null; diff --git a/test/container/container_module.test.ts b/test/container/container_module.test.ts index 8939efbc4..3c730ccd1 100644 --- a/test/container/container_module.test.ts +++ b/test/container/container_module.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { Container } from "../../src/container/container"; -import { ContainerModule } from "../../src/container/container_module"; +import { AsyncContainerModule, ContainerModule } from "../../src/container/container_module"; import { interfaces } from "../../src/interfaces/interfaces"; describe("ContainerModule", () => { @@ -93,4 +93,29 @@ describe("ContainerModule", () => { }); + it("Should be able use await async functions in container modules", async () => { + + const container = new Container(); + const someAsyncFactory = () => new Promise((res) => setTimeout(() => res(1), 100)); + const A = Symbol.for("A"); + const B = Symbol.for("B"); + + const moduleOne = new AsyncContainerModule(async (bind) => { + const val = await someAsyncFactory(); + bind(A).toConstantValue(val); + }); + + const moduleTwo = new AsyncContainerModule(async (bind) => { + bind(B).toConstantValue(2); + }); + + await container.loadAsync(moduleOne, moduleTwo); + + const AIsBound = container.isBound(A); + expect(AIsBound).to.eq(true); + const a = container.get(A); + expect(a).to.eq(1); + + }); + }); diff --git a/wiki/container_modules.md b/wiki/container_modules.md index 02f4179ad..217ba2aff 100644 --- a/wiki/container_modules.md +++ b/wiki/container_modules.md @@ -2,6 +2,8 @@ Container modules can help you to manage the complexity of your bindings in very large applications. +## Synchronous container modules + ```ts let warriors = new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind) => { bind("Ninja").to(Ninja); @@ -23,3 +25,28 @@ let container = new Container(); container.load(warriors, weapons); container.unload(warriors); ``` + +## Asynchronous container modules + +```ts +let warriors = new AsyncContainerModule(async (bind: interfaces.Bind, unbind: interfaces.Unbind) => { + const ninja = await getNinja(); + bind("Ninja").toConstantValue(ninja); +}); + +let weapons = new AsyncContainerModule( + ( + bind: interfaces.Bind, + unbind: interfaces.Unbind, + isBound: interfaces.IsBound, + rebind: interfaces.Rebind + ) => { + bind("Katana").to(Katana); + bind("Shuriken").to(Shuriken); + } +); + +let container = new Container(); +await container.loadAsync(warriors, weapons); +container.unload(warriors); +```