diff --git a/.gitignore b/.gitignore index 6d040bb53..8248d8e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,8 @@ typings dist +src/*.js src/**/*.js +test/*.js test/**/*.js type_definitions/*.js diff --git a/.travis.yml b/.travis.yml index 0a40d3b77..eb97c3f12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,12 @@ language: node_js node_js: - stable -- 4.2.3 -- 4.2.0 -- 4.1.2 -- 4.1.0 -- 4.0.0 +- 5.4.1 +- 5.4.0 +- 5.3.0 +- 5.2.0 +- 5.1.1 - '0.12' -- '0.10' before_install: - npm install -g typings - typings install \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 6f09f126b..10c029c7f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,7 +9,7 @@ var gulp = require("gulp"), buffer = require("vinyl-buffer"), tslint = require("gulp-tslint"), tsc = require("gulp-typescript"), - coveralls = require('gulp-coveralls'), + coveralls = require("gulp-coveralls"), uglify = require("gulp-uglify"), typedoc = require("gulp-typedoc"), rename = require("gulp-rename"), @@ -46,7 +46,7 @@ gulp.task("build-source", function() { "node_modules/reflect-metadata/reflect-metadata.d.ts" ]) .pipe(tsc(tsProject)) - .on('error', function (err) { + .on("error", function (err) { process.exit(1); }) .js.pipe(gulp.dest("src/")); @@ -61,7 +61,7 @@ gulp.task("build-test", function() { "node_modules/reflect-metadata/reflect-metadata.d.ts" ]) .pipe(tsc(tsTestProject)) - .on('error', function (err) { + .on("error", function (err) { process.exit(1); }) .js.pipe(gulp.dest("test/")); @@ -72,7 +72,7 @@ var tsTypeDefinitionsProject = tsc.createProject("tsconfig.json"); gulp.task("build-type-definitions", function() { return gulp.src("type_definitions/**/*.ts") .pipe(tsc(tsTypeDefinitionsProject)) - .on('error', function (err) { + .on("error", function (err) { process.exit(1); }) .js.pipe(gulp.dest("type_definitions/")); @@ -114,7 +114,7 @@ gulp.task("document", function () { gulp.task("bundle", function () { var b = browserify({ - standalone : 'inversify', + standalone : "inversify", entries: "src/inversify.js", debug: true }); @@ -131,15 +131,15 @@ gulp.task("bundle", function () { //****************************************************************************** gulp.task("mocha", function() { return gulp.src([ - 'node_modules/reflect-metadata/Reflect.js', - 'test/**/*.test.js' + "node_modules/reflect-metadata/Reflect.js", + "test/**/*.test.js" ]) - .pipe(mocha({ui: 'bdd'})) + .pipe(mocha({ui: "bdd"})) .pipe(istanbul.writeReports()); }); gulp.task("istanbul:hook", function() { - return gulp.src(['src/**/*.js']) + return gulp.src(["src/**/*.js"]) // Covering files .pipe(istanbul()) // Force `require` to return covered files @@ -168,7 +168,7 @@ gulp.task("compress", function() { return gulp.src("bundled/src/inversify.js") .pipe(uglify({ preserveComments : false })) .pipe(rename({ - extname: '.min.js' + extname: ".min.js" })) .pipe(gulp.dest("dist/")) }); diff --git a/package.json b/package.json index 0dc2cf2e8..6faea28a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inversify", - "version": "2.0.0-alpha.1", + "version": "2.0.0-alpha.2", "description": "A lightweight IoC container written in TypeScript.", "main": "dist/inversify.js", "typings": "type_definitions/inversify-npm.d.ts", @@ -32,10 +32,9 @@ }, "homepage": "http://inversify.io", "engines": {}, - "dependencies": { - "reflect-metadata": "^0.1.3" - }, + "dependencies": {}, "devDependencies": { + "bluebird": "^3.3.3", "browserify": "^13.0.0", "chai": "^3.4.1", "gulp": "^3.9.0", @@ -50,6 +49,7 @@ "gulp-uglify": "^1.5.1", "istanbul": "^0.4.2", "mocha": "^2.3.4", + "reflect-metadata": "^0.1.3", "run-sequence": "^1.1.5", "sinon": "^1.17.3", "tslint": "^3.2.2", diff --git a/src/activation/decorator_utils.ts b/src/activation/decorator_utils.ts index f897b2033..38c57f9a4 100644 --- a/src/activation/decorator_utils.ts +++ b/src/activation/decorator_utils.ts @@ -1,7 +1,5 @@ /// -import "reflect-metadata"; - function tagParameter(target: any, targetKey: string, index: number, metadata: IMetadata) { let metadataKey = "inversify:tagged"; let paramsMetadata: Object = null; diff --git a/src/bindings/binding.ts b/src/bindings/binding.ts index 38f4a1161..0dc4a8a76 100644 --- a/src/bindings/binding.ts +++ b/src/bindings/binding.ts @@ -16,7 +16,7 @@ class Binding implements IBinding { public runtimeIdentifier: string; // The constructor of a class which must implement T - public implementationType: { new(): T; }; + public implementationType: INewable; // Cache used to allow singleton scope and BindingType.Value bindings public cache: T; @@ -28,15 +28,18 @@ class Binding implements IBinding { public type: BindingType; // A factory method used in BindingType.Factory bindings - public factory: (context) => T; + public factory: IFactoryCreator; + + // An async factory method used in BindingType.Provider bindings + public provider: IProviderCreator; constructor(runtimeIdentifier: string) { this.runtimeIdentifier = runtimeIdentifier; - this.type = BindingType.Instance; + this.scope = BindingScope.Transient; + this.type = BindingType.Invalid; this.implementationType = null; this.cache = null; this.factory = null; - this.scope = BindingScope.Transient; } } diff --git a/src/bindings/binding_type.ts b/src/bindings/binding_type.ts index 789e3b4e2..b0878e050 100644 --- a/src/bindings/binding_type.ts +++ b/src/bindings/binding_type.ts @@ -1,10 +1,12 @@ /// enum BindingType { + Invalid, Instance, Value, Constructor, - Factory + Factory, + Provider } export default BindingType; diff --git a/src/constants/error_msgs.ts b/src/constants/error_msgs.ts index 4e5bcb922..64c532632 100644 --- a/src/constants/error_msgs.ts +++ b/src/constants/error_msgs.ts @@ -8,4 +8,5 @@ export const AMBIGUOUS_MATCH = "Ambiguous match found for service:"; export const CANNOT_UNBIND = "Could not unbind service:"; export const NOT_REGISTERED = "No bindigns found for service:"; export const CIRCULAR_DEPENDENCY = "Circular dependency found between services:"; -export const NOT_IMPLEMENTED = "Sorry, this feature is not fully implemented yet"; +export const NOT_IMPLEMENTED = "Sorry, this feature is not fully implemented yet."; +export const INVALID_BINDING_TYPE = "Invalid binding type:"; diff --git a/src/interfaces/bindings/binding.d.ts b/src/interfaces/bindings/binding.d.ts index 807e93e0b..92a7fc094 100644 --- a/src/interfaces/bindings/binding.d.ts +++ b/src/interfaces/bindings/binding.d.ts @@ -1,7 +1,10 @@ +/// + interface IBinding { runtimeIdentifier: string; - implementationType: { new(): T; }; - factory: (context) => T; + implementationType: INewable; + factory: IFactoryCreator; + provider: IProviderCreator; cache: T; scope: number; // BindingScope type: number; // BindingType diff --git a/src/interfaces/bindings/factory.d.ts b/src/interfaces/bindings/factory.d.ts new file mode 100644 index 000000000..47aec0545 --- /dev/null +++ b/src/interfaces/bindings/factory.d.ts @@ -0,0 +1,5 @@ +/// + +interface IFactory extends Function { + (): T; +} diff --git a/src/interfaces/bindings/factory_creator.d.ts b/src/interfaces/bindings/factory_creator.d.ts new file mode 100644 index 000000000..d3f10e064 --- /dev/null +++ b/src/interfaces/bindings/factory_creator.d.ts @@ -0,0 +1,5 @@ +/// + +interface IFactoryCreator extends Function { + (context: IContext): IFactory; +} diff --git a/src/interfaces/bindings/newable.d.ts b/src/interfaces/bindings/newable.d.ts new file mode 100644 index 000000000..40ab73c67 --- /dev/null +++ b/src/interfaces/bindings/newable.d.ts @@ -0,0 +1,5 @@ +/// + +interface INewable { + new(...args: any[]): T; +} diff --git a/src/interfaces/bindings/provider.d.ts b/src/interfaces/bindings/provider.d.ts new file mode 100644 index 000000000..bffba00f5 --- /dev/null +++ b/src/interfaces/bindings/provider.d.ts @@ -0,0 +1,5 @@ +/// + +interface IProvider extends Function { + (): Promise; +} diff --git a/src/interfaces/bindings/provider_creator.d.ts b/src/interfaces/bindings/provider_creator.d.ts new file mode 100644 index 000000000..85a1c5463 --- /dev/null +++ b/src/interfaces/bindings/provider_creator.d.ts @@ -0,0 +1,5 @@ +/// + +interface IProviderCreator extends Function { + (context: IContext): IProvider; +} diff --git a/src/interfaces/interfaces.d.ts b/src/interfaces/interfaces.d.ts index 26bf18e20..100dfd92c 100644 --- a/src/interfaces/interfaces.d.ts +++ b/src/interfaces/interfaces.d.ts @@ -1,26 +1,38 @@ + +// KERNEL /// /// /// /// /// +// PLANNING /// /// +/// +/// +/// +/// +// RESOLUTION /// +// BINDINGS /// /// +/// +/// +/// +/// +/// +// ACTIVATION /// -/// -/// -/// -/// - +// MIDDLEWARE /// +// SYNTAX /// /// /// diff --git a/src/interfaces/syntax/binding_to_syntax.d.ts b/src/interfaces/syntax/binding_to_syntax.d.ts index 1f3530d9d..091f0450e 100644 --- a/src/interfaces/syntax/binding_to_syntax.d.ts +++ b/src/interfaces/syntax/binding_to_syntax.d.ts @@ -4,6 +4,7 @@ interface IBindingToSyntax { to(constructor: { new(...args: any[]): T; }): IBindingInSyntax; toValue(value: T): IBindingWhenSyntax; - toConstructor(constructor: { new(...args: any[]): T; }): IBindingWhenSyntax; - toFactory(factory: (context) => T): IBindingWhenSyntax; + toConstructor(constructor: INewable): IBindingWhenSyntax; + toFactory(factory: IFactoryCreator): IBindingWhenSyntax; + toProvider(provider: IProviderCreator): IBindingWhenSyntax; } diff --git a/src/inversify.js b/src/inversify.js deleted file mode 100644 index 645894c50..000000000 --- a/src/inversify.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; -var kernel_1 = require("./kernel/kernel"); -exports.Kernel = kernel_1.default; -var inject_1 = require("./activation/inject"); -exports.Inject = inject_1.default; -var tagged_1 = require("./activation/tagged"); -exports.Tagged = tagged_1.default; -var named_1 = require("./activation/named"); -exports.Named = named_1.default; -var paramnames_1 = require("./activation/paramnames"); -exports.ParamNames = paramnames_1.default; -var decorator_utils_1 = require("./activation/decorator_utils"); -exports.decorate = decorator_utils_1.decorate; diff --git a/src/planning/planner.ts b/src/planning/planner.ts index c4e1406e0..f6a286758 100644 --- a/src/planning/planner.ts +++ b/src/planning/planner.ts @@ -1,12 +1,12 @@ /// -import "reflect-metadata"; import Plan from "./plan"; import Context from "./context"; import Request from "./request"; import Target from "./target"; import * as METADATA_KEY from "../constants/metadata_keys"; import * as ERROR_MSGS from "../constants/error_msgs"; +import BindingType from "../bindings/binding_type"; class Planner implements IPlanner { @@ -58,16 +58,19 @@ class Planner implements IPlanner { } else { - // TODO 2.0.0-alpha.2 handle value, factory, etc here + // Use the only active binding to create a child request let binding = bindings[0]; - let childRequest = parentRequest.addChildRequest(target.service.value(), binding, target); - let subDependencies = this._getDependencies(binding.implementationType); + // Only try to plan sub-dependencies when binding type is BindingType.Instance + if (binding.type === BindingType.Instance) { - subDependencies.forEach((d, index) => { - this._createSubRequest(childRequest, d); - }); + // Create child requests for sub-dependencies if any + let subDependencies = this._getDependencies(binding.implementationType); + subDependencies.forEach((d, index) => { + this._createSubRequest(childRequest, d); + }); + } } } catch (error) { if (error instanceof RangeError) { diff --git a/src/resolution/resolver.ts b/src/resolution/resolver.ts index 61492c207..50ed3b635 100644 --- a/src/resolution/resolver.ts +++ b/src/resolution/resolver.ts @@ -1,6 +1,8 @@ /// import BindingScope from "../bindings/binding_scope"; +import BindingType from "../bindings/binding_type"; +import * as ERROR_MSGS from "../constants/error_msgs"; class Resolver implements IResolver { @@ -12,31 +14,53 @@ class Resolver implements IResolver { public resolve(context: IContext): Service { let rootRequest = context.plan.rootRequest; - return this._construct(rootRequest); + return this._inject(rootRequest); } - private _construct(request) { + private _inject(request: IRequest) { let childRequests = request.childRequests; let binding = request.bindings[0]; // TODO handle multi-injection - let constr = binding.implementationType; - let isSingleton = binding.scope === BindingScope.Singleton; - if (isSingleton && binding.cache !== null) { - return binding.cache; - } + switch (binding.type) { + case BindingType.Value: + return binding.cache; + + case BindingType.Constructor: + return binding.implementationType; + + case BindingType.Factory: + return binding.factory(request.parentContext); + + case BindingType.Provider: + return binding.provider(request.parentContext); + + case BindingType.Instance: + let constr = binding.implementationType; + let isSingleton = binding.scope === BindingScope.Singleton; + + if (isSingleton && binding.cache !== null) { + return binding.cache; + } + + if (childRequests.length > 0) { + let injections = childRequests.map((childRequest) => { + return this._inject(childRequest); + }); + let instance = this._createInstance(constr, injections); + if (isSingleton) { binding.cache = instance; } + return instance; + } else { + let instance = new constr(); + if (isSingleton) { binding.cache = instance; } + return instance; + } - if (childRequests.length > 0) { - let injections = childRequests.map((childRequest) => { - return this._construct(childRequest); - }); - let instance = this._createInstance(constr, injections); - if (isSingleton) { binding.cache = instance; } - return instance; - } else { - let instance = new constr(); - if (isSingleton) { binding.cache = instance; } - return instance; + case BindingType.Invalid: + default: + // The user probably created a binding but didn't finish it + // e.g. kernel.bind("ISomething"); missing BindingToSyntax + throw new Error(`${ERROR_MSGS.INVALID_BINDING_TYPE} ${request.service}`); } } diff --git a/src/syntax/binding_to_syntax.ts b/src/syntax/binding_to_syntax.ts index a050e80e8..41c824f89 100644 --- a/src/syntax/binding_to_syntax.ts +++ b/src/syntax/binding_to_syntax.ts @@ -24,15 +24,21 @@ class BindingToSyntax implements IBindingToSyntax { return new BindingWhenSyntax(this._binding); } - public toConstructor(constructor: { new(...args: any[]): T; }): IBindingWhenSyntax { + public toConstructor(constructor: INewable): IBindingWhenSyntax { this._binding.type = BindingType.Constructor; - this._binding.implementationType = constructor; + this._binding.implementationType = constructor; return new BindingWhenSyntax(this._binding); } - public toFactory(factory: (context) => T): IBindingWhenSyntax { + public toFactory(factory: IFactoryCreator): IBindingWhenSyntax { this._binding.type = BindingType.Factory; - this._binding.factory = factory; + this._binding.factory = factory; + return new BindingWhenSyntax(this._binding); + } + + public toProvider(provider: IProviderCreator) { + this._binding.type = BindingType.Provider; + this._binding.provider = provider; return new BindingWhenSyntax(this._binding); } diff --git a/test/planning/planner.test.ts b/test/planning/planner.test.ts index 8345db20e..d7fda31cf 100644 --- a/test/planning/planner.test.ts +++ b/test/planning/planner.test.ts @@ -1,6 +1,7 @@ /// import { expect } from "chai"; +import * as sinon from "sinon"; import Planner from "../../src/planning/planner"; import Context from "../../src/planning/context"; import Kernel from "../../src/kernel/kernel"; @@ -13,6 +14,16 @@ import * as ERROR_MSGS from "../../src/constants/error_msgs"; describe("Planner", () => { + let sandbox: Sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + it("Should be able to create instances of Context", () => { let kernel = new Kernel(); @@ -40,7 +51,8 @@ describe("Planner", () => { public handler: IKatanaHandler; public blade: IKatanaBlade; public constructor(handler: IKatanaHandler, blade: IKatanaBlade) { - // DO NOTHING + this.handler = handler; + this.blade = blade; } } @@ -55,7 +67,8 @@ describe("Planner", () => { public katana: IKatana; public shuriken: IShuriken; public constructor(katana: IKatana, shuriken: IShuriken) { - // DO NOTHING + this.katana = katana; + this.shuriken = shuriken; } } @@ -184,7 +197,8 @@ describe("Planner", () => { public katana: IWeapon; public shuriken: IWeapon; public constructor(katana: IWeapon, shuriken: IWeapon) { - // DO NOTHING + this.katana = katana; + this.shuriken = shuriken; } } @@ -258,4 +272,75 @@ describe("Planner", () => { }); + it("Should only plan sub-dependencies when binding type is BindingType.Instance", () => { + + interface IKatanaBlade {} + class KatanaBlade implements IKatanaBlade {} + + interface IKatanaHandler {} + class KatanaHandler implements IKatanaHandler {} + + interface IKatana {} + + @Inject("IKatanaHandler", "IKatanaBlade") + @ParamNames("handler", "blade") + class Katana implements IKatana { + public handler: IKatanaHandler; + public blade: IKatanaBlade; + public constructor(handler: IKatanaHandler, blade: IKatanaBlade) { + this.handler = handler; + this.blade = blade; + } + } + + interface IShuriken {} + class Shuriken implements IShuriken {} + + interface INinja {} + + @Inject("IFactory", "IShuriken") + @ParamNames("katanaFactory", "shuriken") + class Ninja implements INinja { + public katanaFactory: IFactory; + public shuriken: IShuriken; + public constructor(katanaFactory: IFactory, shuriken: IShuriken) { + this.katanaFactory = katanaFactory; + this.shuriken = shuriken; + } + } + + let ninjaId = "INinja"; + let shurikenId = "IShuriken"; + let katanaId = "IKatana"; + let katanaHandlerId = "IKatanaHandler"; + let katanaBladeId = "IKatanaBlade"; + let katanaFactoryId = "IFactory"; + + let kernel = new Kernel(); + kernel.bind(ninjaId).to(Ninja); + kernel.bind(shurikenId).to(Shuriken); + kernel.bind(katanaBladeId).to(Katana); + kernel.bind(katanaBladeId).to(KatanaBlade); + kernel.bind(katanaHandlerId).to(KatanaHandler); + kernel.bind>(katanaFactoryId).toFactory((context) => { + return () => { + return context.kernel.get(katanaId); + }; + }); + + let _kernel: any = kernel; + let ninjaBinding = _kernel._bindingDictionary.get(ninjaId)[0]; + let planner = new Planner(); + let context = planner.createContext(kernel); + let actualPlan = planner.createPlan(context, ninjaBinding); + + expect(actualPlan.rootRequest.service).eql(ninjaId); + expect(actualPlan.rootRequest.childRequests[0].service).eql(katanaFactoryId); + expect(actualPlan.rootRequest.childRequests[0].childRequests.length).eql(0); // IMPORTANT! + expect(actualPlan.rootRequest.childRequests[1].service).eql(shurikenId); + expect(actualPlan.rootRequest.childRequests[1].childRequests.length).eql(0); + expect(actualPlan.rootRequest.childRequests[2]).eql(undefined); + + }); + }); diff --git a/test/resolution/resolver.test.ts b/test/resolution/resolver.test.ts index 92689c6a2..a5fb592ee 100644 --- a/test/resolution/resolver.test.ts +++ b/test/resolution/resolver.test.ts @@ -10,6 +10,8 @@ import Plan from "../../src/planning/plan"; import Target from "../../src/planning/target"; import Inject from "../../src/activation/inject"; import ParamNames from "../../src/activation/paramnames"; +import * as ERROR_MSGS from "../../src/constants/error_msgs"; +import BindingType from "../../src/bindings/binding_type"; describe("Resolver", () => { @@ -23,7 +25,7 @@ describe("Resolver", () => { sandbox.restore(); }); - it("Should be able to resolve a basic plan", () => { + it("Should be able to resolve BindingType.Instance bindings", () => { interface IKatanaBlade {} class KatanaBlade implements IKatanaBlade {} @@ -221,4 +223,409 @@ describe("Resolver", () => { }); + it("Should throw when an invalid BindingType is detected", () => { + + interface IKatana {} + class Katana implements IKatana {} + + interface IShuriken {} + class Shuriken implements IShuriken {} + + interface INinja { + katana: IKatana; + shuriken: IShuriken; + } + + @Inject("IKatana", "IShuriken") + @ParamNames("katana", "shuriken") + class Ninja implements INinja { + public katana: IKatana; + public shuriken: IShuriken; + public constructor(katana: IKatana, shuriken: IShuriken) { + this.katana = katana; + this.shuriken = shuriken; + } + } + + // kernel and bindings + let ninjaId = "INinja"; + let kernel = new Kernel(); + let _kernel: any = kernel; + kernel.bind(ninjaId); // IMPORTAN! (Invalid binding) + let ninjaBinding = _kernel._bindingDictionary.get(ninjaId)[0]; + + // context and plan + let planner = new Planner(); + let context = planner.createContext(kernel); + let ninjaRequest = new Request(ninjaId, context, null, ninjaBinding, null); + let plan = new Plan(context, ninjaRequest); + context.addPlan(plan); + + // resolver + let resolver = new Resolver([]); + let _resolver: any = resolver; + let _inject = _resolver._inject; + + let throwFunction = () => { + _inject(ninjaRequest); + }; + + expect(ninjaRequest.bindings[0].type).eql(BindingType.Invalid); + expect(throwFunction).to.throw(`${ERROR_MSGS.INVALID_BINDING_TYPE} ${ninjaId}`); + + }); + + it("Should be able to resolve BindingType.Value bindings", () => { + + interface IKatanaBlade {} + class KatanaBlade implements IKatanaBlade {} + + interface IKatanaHandler {} + class KatanaHandler implements IKatanaHandler {} + + interface IKatana { + handler: IKatanaHandler; + blade: IKatanaBlade; + } + + class Katana implements IKatana { + public handler: IKatanaHandler; + public blade: IKatanaBlade; + public constructor(handler: IKatanaHandler, blade: IKatanaBlade) { + this.handler = handler; + this.blade = blade; + } + } + + interface IShuriken {} + class Shuriken implements IShuriken {} + + interface INinja { + katana: IKatana; + shuriken: IShuriken; + } + + @Inject("IKatana", "IShuriken") + @ParamNames("katana", "shuriken") + class Ninja implements INinja { + public katana: IKatana; + public shuriken: IShuriken; + public constructor(katana: IKatana, shuriken: IShuriken) { + this.katana = katana; + this.shuriken = shuriken; + } + } + + let ninjaId = "INinja"; + let shurikenId = "IShuriken"; + let katanaId = "IKatana"; + + let kernel = new Kernel(); + kernel.bind(ninjaId).to(Ninja); + kernel.bind(shurikenId).to(Shuriken); + kernel.bind(katanaId).toValue(new Katana(new KatanaHandler(), new KatanaBlade())); // IMPORTANT! + + let _kernel: any = kernel; + let ninjaBinding = _kernel._bindingDictionary.get(ninjaId)[0]; + let katanaBinding = _kernel._bindingDictionary.get(katanaId)[0]; + let shurikenBinding = _kernel._bindingDictionary.get(shurikenId)[0]; + + let planner = new Planner(); + let context = planner.createContext(kernel); + + /* + * Plan (request tree): + * + * Ninja (target "null", no metadata) + * -- Katana (target "katama", no metadata) + * -- Shuriken (target "shuriken", no metadata) + */ + let ninjaRequest = new Request(ninjaId, context, null, ninjaBinding, null); + let plan = new Plan(context, ninjaRequest); + plan.rootRequest.addChildRequest(katanaId, katanaBinding, new Target("katana", katanaId)); + plan.rootRequest.addChildRequest(shurikenId, shurikenBinding, new Target("shuriken", shurikenId)); + context.addPlan(plan); + + let resolver = new Resolver(); + let ninja = resolver.resolve(context); + + expect(ninja instanceof Ninja).eql(true); + expect(ninja.katana instanceof Katana).eql(true); + expect(ninja.katana.handler instanceof KatanaHandler).eql(true); + expect(ninja.katana.blade instanceof KatanaBlade).eql(true); + expect(ninja.shuriken instanceof Shuriken).eql(true); + + }); + + it("Should be able to resolve BindingType.Constructor bindings", () => { + + interface IKatanaBlade {} + class KatanaBlade implements IKatanaBlade {} + + interface IKatanaHandler {} + class KatanaHandler implements IKatanaHandler {} + + interface IKatana { + handler: IKatanaHandler; + blade: IKatanaBlade; + } + + @Inject("IKatanaHandler", "IKatanaBlade") + @ParamNames("handler", "blade") + class Katana implements IKatana { + public handler: IKatanaHandler; + public blade: IKatanaBlade; + public constructor(handler: IKatanaHandler, blade: IKatanaBlade) { + this.handler = handler; + this.blade = blade; + } + } + + interface IShuriken {} + class Shuriken implements IShuriken {} + + interface INinja { + katana: IKatana; + shuriken: IShuriken; + } + + @Inject("IKatana", "IShuriken") + @ParamNames("katana", "shuriken") + class Ninja implements INinja { + public katana: IKatana; + public shuriken: IShuriken; + public constructor(Katana: INewable, shuriken: IShuriken) { + this.katana = new Katana(new KatanaHandler(), new KatanaBlade()); // IMPORTANT! + this.shuriken = shuriken; + } + } + + let ninjaId = "INinja"; + let shurikenId = "IShuriken"; + let newableKatanaId = "INewable"; + + let kernel = new Kernel(); + kernel.bind(ninjaId).to(Ninja); + kernel.bind(shurikenId).to(Shuriken); + kernel.bind>(newableKatanaId).toConstructor(Katana); // IMPORTANT! + + let _kernel: any = kernel; + let ninjaBinding = _kernel._bindingDictionary.get(ninjaId)[0]; + let newableKatanaBinding = _kernel._bindingDictionary.get(newableKatanaId)[0]; + let shurikenBinding = _kernel._bindingDictionary.get(shurikenId)[0]; + + let planner = new Planner(); + let context = planner.createContext(kernel); + + let ninjaRequest = new Request(ninjaId, context, null, ninjaBinding, null); + let plan = new Plan(context, ninjaRequest); + plan.rootRequest.addChildRequest(newableKatanaId, newableKatanaBinding, new Target("Katana", newableKatanaId)); + plan.rootRequest.addChildRequest(shurikenId, shurikenBinding, new Target("shuriken", shurikenId)); + context.addPlan(plan); + + let resolver = new Resolver(); + let ninja = resolver.resolve(context); + + expect(ninja instanceof Ninja).eql(true); + expect(ninja.katana instanceof Katana).eql(true); + expect(ninja.katana.handler instanceof KatanaHandler).eql(true); + expect(ninja.katana.blade instanceof KatanaBlade).eql(true); + expect(ninja.shuriken instanceof Shuriken).eql(true); + + }); + + it("Should be able to resolve BindingType.Factory bindings", () => { + + interface IKatanaBlade {} + class KatanaBlade implements IKatanaBlade {} + + interface IKatanaHandler {} + class KatanaHandler implements IKatanaHandler {} + + interface IKatana { + handler: IKatanaHandler; + blade: IKatanaBlade; + } + + interface IKatanaFactory extends Function { + (): IKatana; + } + + @Inject("IKatanaHandler", "IKatanaBlade") + @ParamNames("handler", "blade") + class Katana implements IKatana { + public handler: IKatanaHandler; + public blade: IKatanaBlade; + public constructor(handler: IKatanaHandler, blade: IKatanaBlade) { + this.handler = handler; + this.blade = blade; + } + } + + interface IShuriken {} + class Shuriken implements IShuriken {} + + interface INinja { + katana: IKatana; + shuriken: IShuriken; + } + + @Inject("IKatana", "IShuriken") + @ParamNames("katana", "shuriken") + class Ninja implements INinja { + public katana: IKatana; + public shuriken: IShuriken; + public constructor(makeKatana: IKatanaFactory, shuriken: IShuriken) { + this.katana = makeKatana(); // IMPORTANT! + this.shuriken = shuriken; + } + } + + let ninjaId = "INinja"; + let shurikenId = "IShuriken"; + let katanaFactoryId = "IFactory"; + let katanaId = "IKatana"; + let katanaHandlerId = "IKatanaHandler"; + let katanaBladeId = "IKatanaBlade"; + + let kernel = new Kernel(); + kernel.bind(ninjaId).to(Ninja); + kernel.bind(shurikenId).to(Shuriken); + kernel.bind(katanaId).to(Katana); + kernel.bind(katanaBladeId).to(KatanaBlade); + kernel.bind(katanaHandlerId).to(KatanaHandler); + + kernel.bind>(katanaFactoryId).toFactory((context: IContext) => { + return () => { + return context.kernel.get(katanaId); + }; + }); + + let _kernel: any = kernel; + let ninjaBinding = _kernel._bindingDictionary.get(ninjaId)[0]; + let katanaFactoryBinding = _kernel._bindingDictionary.get(katanaFactoryId)[0]; + let shurikenBinding = _kernel._bindingDictionary.get(shurikenId)[0]; + + let planner = new Planner(); + let context = planner.createContext(kernel); + + let ninjaRequest = new Request(ninjaId, context, null, ninjaBinding, null); + let plan = new Plan(context, ninjaRequest); + plan.rootRequest.addChildRequest(katanaFactoryId, katanaFactoryBinding, new Target("makeKatana", katanaFactoryId)); + plan.rootRequest.addChildRequest(shurikenId, shurikenBinding, new Target("shuriken", shurikenId)); + context.addPlan(plan); + + let resolver = new Resolver(); + let ninja = resolver.resolve(context); + + expect(ninja instanceof Ninja).eql(true); + expect(ninja.katana instanceof Katana).eql(true); + expect(ninja.katana.handler instanceof KatanaHandler).eql(true); + expect(ninja.katana.blade instanceof KatanaBlade).eql(true); + expect(ninja.shuriken instanceof Shuriken).eql(true); + + }); + + it("Should be able to resolve BindingType.Provider bindings", (done) => { + + interface IKatanaBlade {} + class KatanaBlade implements IKatanaBlade {} + + interface IKatanaHandler {} + class KatanaHandler implements IKatanaHandler {} + + interface IKatana { + handler: IKatanaHandler; + blade: IKatanaBlade; + } + + interface IKatanaFactory extends Function { + (): IKatana; + } + + @Inject("IKatanaHandler", "IKatanaBlade") + @ParamNames("handler", "blade") + class Katana implements IKatana { + public handler: IKatanaHandler; + public blade: IKatanaBlade; + public constructor(handler: IKatanaHandler, blade: IKatanaBlade) { + this.handler = handler; + this.blade = blade; + } + } + + interface IShuriken {} + class Shuriken implements IShuriken {} + + interface INinja { + katana: IKatana; + katanaProvider: IProvider; + shuriken: IShuriken; + } + + @Inject("IKatana", "IShuriken") + @ParamNames("katana", "shuriken") + class Ninja implements INinja { + public katana: IKatana; + public katanaProvider: IProvider; + public shuriken: IShuriken; + public constructor(katanaProvider: IProvider, shuriken: IShuriken) { + this.katana = null; + this.katanaProvider = katanaProvider; + this.shuriken = shuriken; + } + } + + let ninjaId = "INinja"; + let shurikenId = "IShuriken"; + let katanaFactoryId = "IFactory"; + let katanaId = "IKatana"; + let katanaHandlerId = "IKatanaHandler"; + let katanaBladeId = "IKatanaBlade"; + + let kernel = new Kernel(); + kernel.bind(ninjaId).to(Ninja); + kernel.bind(shurikenId).to(Shuriken); + kernel.bind(katanaId).to(Katana); + kernel.bind(katanaBladeId).to(KatanaBlade); + kernel.bind(katanaHandlerId).to(KatanaHandler); + + kernel.bind>(katanaFactoryId).toProvider((context: IContext) => { + return () => { + return new Promise((resolve) => { + // Using setTimeout to simulate complex initialization + setTimeout(() => { resolve(context.kernel.get(katanaId)); }, 100); + }); + }; + }); + + let _kernel: any = kernel; + let ninjaBinding = _kernel._bindingDictionary.get(ninjaId)[0]; + let katanaFactoryBinding = _kernel._bindingDictionary.get(katanaFactoryId)[0]; + let shurikenBinding = _kernel._bindingDictionary.get(shurikenId)[0]; + + let planner = new Planner(); + let context = planner.createContext(kernel); + + let ninjaRequest = new Request(ninjaId, context, null, ninjaBinding, null); + let plan = new Plan(context, ninjaRequest); + plan.rootRequest.addChildRequest(katanaFactoryId, katanaFactoryBinding, new Target("makeKatana", katanaFactoryId)); + plan.rootRequest.addChildRequest(shurikenId, shurikenBinding, new Target("shuriken", shurikenId)); + context.addPlan(plan); + + let resolver = new Resolver(); + let ninja = resolver.resolve(context); + + expect(ninja instanceof Ninja).eql(true); + expect(ninja.shuriken instanceof Shuriken).eql(true); + ninja.katanaProvider().then((katana) => { + ninja.katana = katana; + expect(ninja.katana instanceof Katana).eql(true); + expect(ninja.katana.handler instanceof KatanaHandler).eql(true); + expect(ninja.katana.blade instanceof KatanaBlade).eql(true); + done(); + }); + + }); + }); diff --git a/test/syntax/binding_to_syntax.test.ts b/test/syntax/binding_to_syntax.test.ts index 7249e0531..719011c20 100644 --- a/test/syntax/binding_to_syntax.test.ts +++ b/test/syntax/binding_to_syntax.test.ts @@ -31,22 +31,39 @@ describe("BindingToSyntax", () => { let binding = new Binding(ninjaIdentifier); let bindingToSyntax = new BindingToSyntax(binding); - expect(binding.type).eql(BindingType.Instance); + expect(binding.type).eql(BindingType.Invalid); bindingToSyntax.to(Ninja); expect(binding.type).eql(BindingType.Instance); + expect(binding.implementationType).not.to.eql(null); bindingToSyntax.toValue(new Ninja()); expect(binding.type).eql(BindingType.Value); + expect(binding.cache instanceof Ninja).eql(true); - bindingToSyntax.toConstructor(Ninja); + bindingToSyntax.toConstructor(Ninja); expect(binding.type).eql(BindingType.Constructor); + expect(binding.implementationType).not.to.eql(null); - bindingToSyntax.toConstructor(Ninja); - expect(binding.type).eql(BindingType.Constructor); + bindingToSyntax.toFactory((context) => { + return () => { + return new Ninja(); + }; + }); - bindingToSyntax.toFactory((context) => { return new Ninja(); }); expect(binding.type).eql(BindingType.Factory); + expect(binding.factory).not.to.eql(null); + + bindingToSyntax.toProvider((context) => { + return () => { + return new Promise((resolve) => { + resolve(new Ninja()); + }); + }; + }); + + expect(binding.type).eql(BindingType.Provider); + expect(binding.provider).not.to.eql(null); }); diff --git a/type_definitions/inversify-global-test.ts b/type_definitions/inversify-global-test.ts index 4b7617caf..b782b001b 100644 --- a/type_definitions/inversify-global-test.ts +++ b/type_definitions/inversify-global-test.ts @@ -75,4 +75,25 @@ module inversify_global_test { let ninja2 = kernel2.get("INinja"); console.log(ninja2); + // binding types + kernel2.bind("IKatana").to(Katana); + kernel2.bind("IKatana").toValue(new Katana()); + + kernel2.bind<__inversify.INewable>("IKatana").toConstructor(Katana); + + kernel2.bind<__inversify.IFactory>("IKatana").toFactory((context) => { + return () => { + return kernel2.get("IKatana"); + }; + }); + + kernel2.bind<__inversify.IProvider>("IKatana").toProvider((context) => { + return () => { + return new Promise((resolve) => { + let katana = kernel2.get("IKatana"); + resolve(katana); + }); + }; + }); + } diff --git a/type_definitions/inversify-global.d.ts b/type_definitions/inversify-global.d.ts index 570d3bcd8..9817daec7 100644 --- a/type_definitions/inversify-global.d.ts +++ b/type_definitions/inversify-global.d.ts @@ -1,8 +1,10 @@ -// Type definitions for inversify 1.2.2 +// Type definitions for inversify 2.0.0-alpha.2 // Project: https://github.com/inversify/InversifyJS // Definitions by: inversify // Definitions: https://github.com/borisyankov/DefinitelyTyped +/// + declare namespace inversify { export interface IMiddleware extends Function { @@ -34,11 +36,32 @@ declare namespace inversify { when(constraint: Constraint): void; } + interface IFactoryCreator extends Function { + (context: IContext): IFactory; + } + + interface IProviderCreator extends Function { + (context: IContext): IProvider; + } + + export interface IFactory extends Function { + (): T; + } + + export interface IProvider extends Function { + (): Promise; + } + + export interface INewable { + new(...args: any[]): T; + } + interface IBindingToSyntax { to(constructor: { new(...args: any[]): T; }): IBindingInSyntax; toValue(value: T): IBindingWhenSyntax; - toConstructor(constructor: { new(...args: any[]): T; }): IBindingWhenSyntax; - toFactory(factory: (context) => T): IBindingWhenSyntax; + toConstructor(constructor: INewable): IBindingWhenSyntax; + toFactory(factory: IFactoryCreator): IBindingWhenSyntax; + toProvider(provider: IProviderCreator): IBindingWhenSyntax; } interface IBindingInSyntax { @@ -49,7 +72,8 @@ declare namespace inversify { export interface IBinding { runtimeIdentifier: string; implementationType: { new(): T; }; - factory: (context) => T; + factory: IFactoryCreator; + provider: IProviderCreator; cache: T; scope: number; // BindingScope type: number; // BindingType diff --git a/type_definitions/inversify-npm.d.ts b/type_definitions/inversify-npm.d.ts index 426c09397..ffa43911f 100644 --- a/type_definitions/inversify-npm.d.ts +++ b/type_definitions/inversify-npm.d.ts @@ -1,8 +1,10 @@ -// Type definitions for inversify 1.2.2 +// Type definitions for inversify 2.0.0-alpha.2 // Project: https://github.com/inversify/InversifyJS // Definitions by: inversify // Definitions: https://github.com/borisyankov/DefinitelyTyped +/// + interface IMiddleware extends Function { (...args: any[]): any; } @@ -32,11 +34,32 @@ interface IBindingWhenSyntax { when(constraint: Constraint): void; } +interface IFactoryCreator extends Function { + (context: IContext): IFactory; +} + +interface IProviderCreator extends Function { + (context: IContext): IProvider; +} + +interface IFactory extends Function { + (): T; +} + +interface IProvider extends Function { + (): Promise; +} + +interface INewable { + new(...args: any[]): T; +} + interface IBindingToSyntax { to(constructor: { new(...args: any[]): T; }): IBindingInSyntax; toValue(value: T): IBindingWhenSyntax; - toConstructor(constructor: { new(...args: any[]): T; }): IBindingWhenSyntax; - toFactory(factory: (context) => T): IBindingWhenSyntax; + toConstructor(constructor: INewable): IBindingWhenSyntax; + toFactory(factory: IFactoryCreator): IBindingWhenSyntax; + toProvider(provider: IProviderCreator): IBindingWhenSyntax; } interface IBindingInSyntax { @@ -47,7 +70,8 @@ interface IBindingInSyntax { interface IBinding { runtimeIdentifier: string; implementationType: { new(): T; }; - factory: (context) => T; + factory: IFactoryCreator; + provider: IProviderCreator; cache: T; scope: number; // BindingScope type: number; // BindingType diff --git a/type_definitions/inversify-test.ts b/type_definitions/inversify-test.ts index 8c7d26f47..17ace77b0 100644 --- a/type_definitions/inversify-test.ts +++ b/type_definitions/inversify-test.ts @@ -1,6 +1,6 @@ /// -import { Kernel, Inject, IKernel, IKernelOptions, IKernelModule } from "inversify"; +import { Kernel, Inject, IKernel, IKernelOptions, INewable, IKernelModule, IFactory, IProvider } from "inversify"; module inversify_external_module_test { @@ -73,4 +73,25 @@ module inversify_external_module_test { let ninja2 = kernel2.get("INinja"); console.log(ninja2); + // binding types + kernel2.bind("IKatana").to(Katana); + kernel2.bind("IKatana").toValue(new Katana()); + + kernel2.bind>("IKatana").toConstructor(Katana); + + kernel2.bind>("IKatana").toFactory((context) => { + return () => { + return kernel2.get("IKatana"); + }; + }); + + kernel2.bind>("IKatana").toProvider((context) => { + return () => { + return new Promise((resolve) => { + let katana = kernel2.get("IKatana"); + resolve(katana); + }); + }; + }); + } diff --git a/type_definitions/inversify.d.ts b/type_definitions/inversify.d.ts index ea304678a..a361fccfa 100644 --- a/type_definitions/inversify.d.ts +++ b/type_definitions/inversify.d.ts @@ -1,8 +1,10 @@ -// Type definitions for inversify 1.2.2 +// Type definitions for inversify 2.0.0-alpha.2 // Project: https://github.com/inversify/InversifyJS // Definitions by: inversify // Definitions: https://github.com/borisyankov/DefinitelyTyped +/// + declare namespace __inversify { export interface IMiddleware extends Function { @@ -34,11 +36,32 @@ declare namespace __inversify { when(constraint: Constraint): void; } + interface IFactoryCreator extends Function { + (context: IContext): IFactory; + } + + interface IProviderCreator extends Function { + (context: IContext): IProvider; + } + + export interface IFactory extends Function { + (): T; + } + + export interface IProvider extends Function { + (): Promise; + } + + export interface INewable { + new(...args: any[]): T; + } + interface IBindingToSyntax { to(constructor: { new(...args: any[]): T; }): IBindingInSyntax; toValue(value: T): IBindingWhenSyntax; - toConstructor(constructor: { new(...args: any[]): T; }): IBindingWhenSyntax; - toFactory(factory: (context) => T): IBindingWhenSyntax; + toConstructor(constructor: INewable): IBindingWhenSyntax; + toFactory(factory: IFactoryCreator): IBindingWhenSyntax; + toProvider(provider: IProviderCreator): IBindingWhenSyntax; } interface IBindingInSyntax { @@ -49,7 +72,8 @@ declare namespace __inversify { export interface IBinding { runtimeIdentifier: string; implementationType: { new(): T; }; - factory: (context) => T; + factory: IFactoryCreator; + provider: IProviderCreator; cache: T; scope: number; // BindingScope type: number; // BindingType diff --git a/typings.json b/typings.json index 52d066398..d7f3ddaa0 100644 --- a/typings.json +++ b/typings.json @@ -3,6 +3,7 @@ "dependencies": {}, "devDependencies": {}, "ambientDependencies": { + "bluebird": "github:DefinitelyTyped/DefinitelyTyped/bluebird/bluebird.d.ts#dd328830dddffbe19e9addd7cf8532cbd3600816", "chai": "github:DefinitelyTyped/DefinitelyTyped/chai/chai.d.ts#1914a00b3c740348dae407ee0d2be89b0b58ad7f", "mocha": "github:DefinitelyTyped/DefinitelyTyped/mocha/mocha.d.ts#d6dd320291705694ba8e1a79497a908e9f5e6617", "sinon": "github:DefinitelyTyped/DefinitelyTyped/sinon/sinon.d.ts#6e7d83167d86d857817bd492786f8304f5c4a0e4" diff --git a/wallaby.js b/wallaby.js index c219b359c..eafc03ac3 100644 --- a/wallaby.js +++ b/wallaby.js @@ -12,7 +12,8 @@ module.exports = function (wallaby) { files : [ { pattern: "src/**/*.ts", load: false }, { pattern: "typings/browser.d.ts", load: false }, - { pattern: "node_modules/reflect-metadata/reflect-metadata.d.ts", load: false } + { pattern: "node_modules/reflect-metadata/reflect-metadata.d.ts", load: false }, + { pattern: "node_modules/reflect-metadata/Reflect.js", load: true } ], tests: [ { pattern: "test/**/*.ts", load: false },