Skip to content

Commit

Permalink
Async container modules (#804)
Browse files Browse the repository at this point in the history
* WIP async container modules support

* Implemented async module support

* Implemented backwards compatible async modules

* rollback tslint config change

* rollback change in test
  • Loading branch information
remojansen committed Feb 26, 2018
1 parent 947c925 commit 4d9f2fc
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 41 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
105 changes: 68 additions & 37 deletions src/container/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>) => {
const bindingToSyntax = this.bind.call(this, serviceIdentifier);
setModuleId(bindingToSyntax, moduleId);
return bindingToSyntax;
};
for (const currentModule of modules) {

const getUnbindFunction = (moduleId: string) =>
(serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
const _unbind = this.unbind.bind(this);
_unbind(serviceIdentifier);
};
const containerModuleHelpers = getHelpers(currentModule.guid);

const getIsboundFunction = (moduleId: string) =>
(serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
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<any>) => {
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
);

});
}

}

Expand Down Expand Up @@ -278,6 +266,49 @@ class Container implements interfaces.Container {
return tempContainer.get<T>(constructorFunction);
}

private _getContainerModuleHelpersFactory() {

const setModuleId = (bindingToSyntax: any, moduleId: string) => {
bindingToSyntax._binding.moduleId = moduleId;
};

const getBindFunction = (moduleId: string) =>
(serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
const _bind = this.bind.bind(this);
const bindingToSyntax = _bind(serviceIdentifier);
setModuleId(bindingToSyntax, moduleId);
return bindingToSyntax;
};

const getUnbindFunction = (moduleId: string) =>
(serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
const _unbind = this.unbind.bind(this);
_unbind(serviceIdentifier);
};

const getIsboundFunction = (moduleId: string) =>
(serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
const _isBound = this.isBound.bind(this);
return _isBound(serviceIdentifier);
};

const getRebindFunction = (moduleId: string) =>
(serviceIdentifier: interfaces.ServiceIdentifier<any>) => {
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
Expand Down
14 changes: 12 additions & 2 deletions src/container/container_module.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}

}
13 changes: 13 additions & 0 deletions src/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ namespace interfaces {
getAll<T>(serviceIdentifier: ServiceIdentifier<T>): T[];
resolve<T>(constructorFunction: interfaces.Newable<T>): T;
load(...modules: ContainerModule[]): void;
loadAsync(...modules: AsyncContainerModule[]): Promise<void>;
unload(...modules: ContainerModule[]): void;
applyCustomMetadataReader(metadataReader: MetadataReader): void;
applyMiddleware(...middleware: Middleware[]): void;
Expand All @@ -198,13 +199,25 @@ namespace interfaces {
registry: ContainerModuleCallBack;
}

export interface AsyncContainerModule {
guid: string;
registry: AsyncContainerModuleCallBack;
}

export type ContainerModuleCallBack = (
bind: interfaces.Bind,
unbind: interfaces.Unbind,
isBound: interfaces.IsBound,
rebind: interfaces.Rebind
) => void;

export type AsyncContainerModuleCallBack = (
bind: interfaces.Bind,
unbind: interfaces.Unbind,
isBound: interfaces.IsBound,
rebind: interfaces.Rebind
) => Promise<void>;

export interface ContainerSnapshot {
bindings: Lookup<Binding<any>>;
middleware: Next | null;
Expand Down
27 changes: 26 additions & 1 deletion test/container/container_module.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down Expand Up @@ -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<number>((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);

});

});
27 changes: 27 additions & 0 deletions wiki/container_modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>("Ninja").to(Ninja);
Expand All @@ -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>("Ninja").toConstantValue(ninja);
});

let weapons = new AsyncContainerModule(
(
bind: interfaces.Bind,
unbind: interfaces.Unbind,
isBound: interfaces.IsBound,
rebind: interfaces.Rebind
) => {
bind<Katana>("Katana").to(Katana);
bind<Shuriken>("Shuriken").to(Shuriken);
}
);

let container = new Container();
await container.loadAsync(warriors, weapons);
container.unload(warriors);
```

0 comments on commit 4d9f2fc

Please sign in to comment.