diff --git a/README.md b/README.md index 3721172..26b4006 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,55 @@ class Shuriken implements Weapon { } ``` +### Using @provideFluent multiple times + +If you try to apply `@provideFluent` multiple times: + +```ts +let container = new Container(); +let provideFluent = fluentProvide(container); + +const provideSingleton = (identifier: any) => { + return provideFluent(identifier) + .inSingletonScope() + .done(); +}; + +function shouldThrow() { + @provideSingleton("Ninja") + @provideSingleton("SilentNinja") + class Ninja {} + return Ninja; +} +``` + +The library will throw an exception: + +> Cannot apply @provideFluent decorator multiple times but is has been used multiple times in Ninja Please use @done(true) if you are trying to declare multiple bindings! + +We throw an exception to ensure that you are are not trying to apply `@fluentProvide` multiple times by mistake. + +You can overcome this by passing the `force` argument to `done()`: + +```ts +let container = new Container(); +let provideFluent = fluentProvide(container); + +const provideSingleton = (identifier: any) => { + return provideFluent(identifier) + .inSingletonScope() + .done(true); // IMPORTANT! +}; + +function shouldThrow() { + @provideSingleton("Ninja") + @provideSingleton("SilentNinja") + class Ninja {} + return Ninja; +} +``` + + ## The auto provide utility This library includes a small utility apply to add the default `@provide` decorator to all the public properties of a module: diff --git a/package.json b/package.json index b5a4ee0..334527e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inversify-binding-decorators", - "version": "3.1.0", + "version": "3.2.0", "description": "An utility that allows developers to declare InversifyJS bindings using ES2016 decorators", "main": "lib/index.js", "jsnext:main": "es/index.js", diff --git a/src/decorator/provide.ts b/src/decorator/provide.ts index 922fed8..d6fce25 100644 --- a/src/decorator/provide.ts +++ b/src/decorator/provide.ts @@ -20,8 +20,11 @@ function provide(container: interfaces.Container) { try { decorate(injectable(), target); } catch (e) { - throw new Error(`${e.message} ` + - "Please use @provide(ID, true) if you are trying to declare multiple bindings!"); + throw new Error( + "Cannot apply @provide decorator multiple times but is has been used " + + `multiple times in ${target.name} ` + + "Please use @provide(ID, true) if you are trying to declare multiple bindings!" + ); } } diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 6e50d4c..0738fae 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -3,7 +3,7 @@ import { interfaces as inversifyInterfaces } from "inversify"; namespace interfaces { export interface ProvideDoneSyntax { - done(): (target: any) => any; + done(force?: boolean): (target: any) => any; } export interface ProvideInSyntax extends ProvideDoneSyntax { diff --git a/src/syntax/provide_done_syntax.ts b/src/syntax/provide_done_syntax.ts index 10c410b..cd6b1d2 100644 --- a/src/syntax/provide_done_syntax.ts +++ b/src/syntax/provide_done_syntax.ts @@ -1,6 +1,6 @@ import interfaces from "../interfaces/interfaces"; import { decorate, injectable } from "inversify"; -import { interfaces as inversifyInterfaces } from "inversify"; +import { interfaces as inversifyInterfaces, METADATA_KEY } from "inversify"; class ProvideDoneSyntax implements interfaces.ProvideDoneSyntax { @@ -10,10 +10,30 @@ class ProvideDoneSyntax implements interfaces.ProvideDoneSyntax { this._binding = binding; } - public done() { - return (target: any) => { - decorate(injectable(), target); - this._binding.implementationType = target; + public done(force?: boolean) { + const that = this; + return function (target: any) { + + const isAlreadyDecorated = Reflect.hasOwnMetadata(METADATA_KEY.PARAM_TYPES, target); + const redecorateWithInject = force === true; + + if (redecorateWithInject === true && isAlreadyDecorated === false) { + decorate(injectable(), target); + } else if (redecorateWithInject === true && isAlreadyDecorated === true) { + // Do nothing + } else { + try { + decorate(injectable(), target); + } catch (e) { + throw new Error( + "Cannot apply @provideFluent decorator multiple times but is has been used " + + `multiple times in ${target.name} ` + + "Please use @done(true) if you are trying to declare multiple bindings!" + ); + } + } + + that._binding.implementationType = target; return target; }; } diff --git a/src/syntax/provide_in_syntax.ts b/src/syntax/provide_in_syntax.ts index 7537c2b..534e443 100644 --- a/src/syntax/provide_in_syntax.ts +++ b/src/syntax/provide_in_syntax.ts @@ -32,10 +32,10 @@ class ProvideInSyntax implements interfaces.ProvideInSyntax { return new ProvideWhenOnSyntax(provideWhenSyntax, provideOnSyntax); } - public done() { + public done(force?: boolean) { let binding: inversifyInterfaces.Binding = (this._bindingInSyntax)._binding; let provideDoneSyntax = new ProvideDoneSyntax(binding); - return provideDoneSyntax.done(); + return provideDoneSyntax.done(force); } } diff --git a/src/syntax/provide_in_when_on_syntax.ts b/src/syntax/provide_in_when_on_syntax.ts index ab35b50..84fd226 100644 --- a/src/syntax/provide_in_when_on_syntax.ts +++ b/src/syntax/provide_in_when_on_syntax.ts @@ -85,8 +85,8 @@ class ProvideInWhenOnSyntax implements interfaces.ProvideInWhenOnSyntax { return this._provideInSyntax.inTransientScope(); } - public done() { - return this._provideInSyntax.done(); + public done(force?: boolean) { + return this._provideInSyntax.done(force); } } diff --git a/src/syntax/provide_on_syntax.ts b/src/syntax/provide_on_syntax.ts index dc7c4ea..1a3bf13 100644 --- a/src/syntax/provide_on_syntax.ts +++ b/src/syntax/provide_on_syntax.ts @@ -20,8 +20,8 @@ class ProvideOnSyntax implements interfaces.ProvideOnSyntax { return new ProvideWhenSyntax(bindingWhenSyntax, this._provideDoneSyntax); } - public done() { - return this._provideDoneSyntax.done(); + public done(force?: boolean) { + return this._provideDoneSyntax.done(force); } } diff --git a/src/syntax/provide_when_on_syntax.ts b/src/syntax/provide_when_on_syntax.ts index 67ce2d4..11f92b0 100644 --- a/src/syntax/provide_when_on_syntax.ts +++ b/src/syntax/provide_when_on_syntax.ts @@ -74,8 +74,8 @@ class ProvideWhenOnSyntax implements interfaces.ProvideWhenOnSyntax { return this._provideOnSyntax.onActivation(fn); } - public done() { - return this._provideWhenSyntax.done(); + public done(force?: boolean) { + return this._provideWhenSyntax.done(force); } } diff --git a/src/syntax/provide_when_syntax.ts b/src/syntax/provide_when_syntax.ts index 737e6f7..634e7db 100644 --- a/src/syntax/provide_when_syntax.ts +++ b/src/syntax/provide_when_syntax.ts @@ -85,8 +85,8 @@ class ProvideWhenSyntax implements interfaces.ProvideWhenSyntax { return new ProvideOnSyntax(bindingOnSyntax, this._provideDoneSyntax); } - public done() { - return this._provideDoneSyntax.done(); + public done(force?: boolean) { + return this._provideDoneSyntax.done(force); } } diff --git a/test/decorator/fluent_provide.test.ts b/test/decorator/fluent_provide.test.ts index e1bee21..55e8c71 100644 --- a/test/decorator/fluent_provide.test.ts +++ b/test/decorator/fluent_provide.test.ts @@ -5,17 +5,14 @@ import "reflect-metadata"; describe("fluentProvide", () => { - let container = new Container(); - it("Should return a configurable decorator", () => { - + let container = new Container(); let provide = fluentProvide(container); expect(typeof provide).eqls("function"); - }); it("Should return an instance of ProvideInWhenOnSyntax once it is configured", () => { - + let container = new Container(); let provide = fluentProvide(container); let provideInWhenOnSyntax = provide("SomeTypeID"); expect((provideInWhenOnSyntax)._provideInSyntax).not.to.be.eqls(null); @@ -24,6 +21,53 @@ describe("fluentProvide", () => { expect((provideInWhenOnSyntax)._provideInSyntax).not.to.be.eqls(undefined); expect((provideInWhenOnSyntax)._provideWhenSyntax).not.to.be.eqls(undefined); expect((provideInWhenOnSyntax)._provideOnSyntax).not.to.be.eqls(undefined); + }); + + it("Should throw if @fluentProvide is applied more than once without force flag", () => { + + let container = new Container(); + let provideFluent = fluentProvide(container); + + const provideSingleton = (identifier: any) => { + return provideFluent(identifier) + .inSingletonScope() + .done(); + }; + + function shouldThrow() { + @provideSingleton("Ninja") + @provideSingleton("SilentNinja") + class Ninja {} + return Ninja; + } + + expect(shouldThrow).to.throw( + "Cannot apply @provideFluent decorator multiple times but is has been used " + + "multiple times in Ninja " + + "Please use @done(true) if you are trying to declare multiple bindings!" + ); + + }); + + it("Should work if @provide is applied more than once with force flag", () => { + + let container = new Container(); + let provideFluent = fluentProvide(container); + + const provideSingleton = (identifier: any) => { + return provideFluent(identifier) + .inSingletonScope() + .done(true); // IMPORTANT! + }; + + function shouldThrow() { + @provideSingleton("Ninja") + @provideSingleton("SilentNinja") + class Ninja {} + return Ninja; + } + + expect(shouldThrow).not.to.throw(); }); diff --git a/test/decorator/provide.test.ts b/test/decorator/provide.test.ts index 701f3c4..0df40cc 100644 --- a/test/decorator/provide.test.ts +++ b/test/decorator/provide.test.ts @@ -29,7 +29,7 @@ describe("provide", () => { }); - it("Should throw if applied more than once without force flag", () => { + it("Should throw if @provide is applied more than once without force flag", () => { const myContainer = new Container(); const provide = _provide(myContainer); @@ -42,12 +42,14 @@ describe("provide", () => { } expect(shouldThrow).to.throw( - "Cannot apply @injectable decorator multiple times. " + - "Please use @provide(ID, true) if you are trying to declare multiple bindings!"); + "Cannot apply @provide decorator multiple times but is has been used " + + "multiple times in Ninja " + + "Please use @provide(ID, true) if you are trying to declare multiple bindings!" + ); }); - it("Should work if applied more than once with force flag", () => { + it("Should work if @provide is applied more than once with force flag", () => { const myContainer = new Container(); const provide = _provide(myContainer);