From f278cf580488af85e31b55bb5315493ef2012c4f Mon Sep 17 00:00:00 2001 From: Ivan Sadovyi Date: Thu, 13 Apr 2023 15:54:24 +0300 Subject: [PATCH 1/2] Add .toDynamicValueWithDeps into BindingToSyntax toDynamicValueWithDeps method binds an abstraction to a dynamic value with required dependencies from the container in a declarative way. --- CHANGELOG.md | 1 + src/interfaces/interfaces.ts | 18 ++++++++++++++++++ src/syntax/binding_to_syntax.ts | 12 ++++++++++++ test/container/container.test.ts | 30 ++++++++++++++++++++++++++++++ wiki/value_injection.md | 10 ++++++++++ 5 files changed, 71 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba30949c6..058cd9c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Added `.toDynamicValueWithDeps` to create a dynamic value with declaratively listed dependencies ### Changed diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 773833c1d..36f45be29 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -340,6 +340,13 @@ namespace interfaces { toSelf(): BindingInWhenOnSyntax; toConstantValue(value: T): BindingWhenOnSyntax; toDynamicValue(func: DynamicValue): BindingInWhenOnSyntax; + toDynamicValueWithDeps( + dependencies: Deps, + func: ( + dependencies: ResolvedDeps, + context: interfaces.Context + ) => T + ): interfaces.BindingInWhenOnSyntax; toConstructor(constructor: Newable): BindingWhenOnSyntax; toFactory( factory: FactoryCreator): BindingWhenOnSyntax; @@ -369,6 +376,17 @@ namespace interfaces { userGeneratedMetadata: MetadataMap; } + export type ResolvedDeps = { + [P in keyof Deps]: Deps[P] extends string + ? unknown + : Deps[P] extends symbol + ? unknown + : Deps[P] extends interfaces.Newable + ? R1 + : Deps[P] extends interfaces.Abstract + ? R2 + : unknown; + }; } export { interfaces }; diff --git a/src/syntax/binding_to_syntax.ts b/src/syntax/binding_to_syntax.ts index cfcf9ca95..2f300a07f 100644 --- a/src/syntax/binding_to_syntax.ts +++ b/src/syntax/binding_to_syntax.ts @@ -46,6 +46,18 @@ class BindingToSyntax implements interfaces.BindingToSyntax { return new BindingInWhenOnSyntax(this._binding); } + public toDynamicValueWithDeps( + deps: Deps, + func: (dependencies: interfaces.ResolvedDeps, context: interfaces.Context) => T + ): interfaces.BindingInWhenOnSyntax { + return this.toDynamicValue((context) => { + const resolvedDeps = deps.map((identifier) => + context.container.get(identifier as interfaces.ServiceIdentifier) + ) as unknown as interfaces.ResolvedDeps; + return func(resolvedDeps, context) + }) + } + public toConstructor(constructor: interfaces.Newable): interfaces.BindingWhenOnSyntax { this._binding.type = BindingTypeEnum.Constructor; this._binding.implementationType = constructor as unknown as T; diff --git a/test/container/container.test.ts b/test/container/container.test.ts index 6b0f9b3d1..e733f7c6e 100644 --- a/test/container/container.test.ts +++ b/test/container/container.test.ts @@ -1172,4 +1172,34 @@ describe('Container', () => { expect(() => myContainer.resolve(CompositionRoot)).not.to.throw; }) + it('should be able to resolve all dependencies using toDynamicValueWithDeps', () => { + abstract class AbstractShuriken {} + + abstract class AbstractKatana {} + + @injectable() + class Shuriken implements AbstractShuriken {} + + @injectable() + class Katana implements AbstractKatana {} + + @injectable() + class Ninja { + public constructor(public shuriken: AbstractShuriken, public katana: AbstractKatana) {} + } + + + const container = new Container() + container.bind(AbstractShuriken).to(Shuriken) + container.bind(AbstractKatana).to(Katana) + container.bind(Ninja).toDynamicValueWithDeps( + [AbstractShuriken, AbstractKatana] as const, + ([shuriken, katana]) => new Ninja(shuriken, katana) + ) + + const ninja = container.get(Ninja) + expect(ninja.shuriken).to.be.instanceOf(Shuriken) + expect(ninja.katana).to.be.instanceOf(Katana) + }) + }); diff --git a/wiki/value_injection.md b/wiki/value_injection.md index 3e8b8c12a..575a22389 100644 --- a/wiki/value_injection.md +++ b/wiki/value_injection.md @@ -9,3 +9,13 @@ container.bind("Katana").toDynamicValue((context: interfaces.Context) => // a dynamic value can return a promise that will resolve to the value container.bind("Katana").toDynamicValue((context: interfaces.Context) => { return Promise.resolve(new Katana()); }); ``` + +Binds an abstraction to a dynamic value with required dependencies from the container in a declarative way. +```ts +container.bind(AbstractShuriken).to(Shuriken) +container.bind(AbstractKatana).to(Katana) +container.bind(Ninja).toDynamicValueWithDeps( + [AbstractShuriken, AbstractKatana] as const, + ([shuriken, katana]) => new Ninja(shuriken, katana) +) +``` From 063340426ac259401b7433f09988355d26c4a6ce Mon Sep 17 00:00:00 2001 From: Ivan Sadovyi Date: Tue, 18 Apr 2023 12:49:18 +0300 Subject: [PATCH 2/2] toDynamicValueWithDeps: improve generic constraints; Update container tests --- src/interfaces/interfaces.ts | 4 ++-- src/syntax/binding_to_syntax.ts | 4 ++-- test/container/container.test.ts | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 36f45be29..b73082cec 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -340,7 +340,7 @@ namespace interfaces { toSelf(): BindingInWhenOnSyntax; toConstantValue(value: T): BindingWhenOnSyntax; toDynamicValue(func: DynamicValue): BindingInWhenOnSyntax; - toDynamicValueWithDeps( + toDynamicValueWithDeps( dependencies: Deps, func: ( dependencies: ResolvedDeps, @@ -376,7 +376,7 @@ namespace interfaces { userGeneratedMetadata: MetadataMap; } - export type ResolvedDeps = { + export type ResolvedDeps = { [P in keyof Deps]: Deps[P] extends string ? unknown : Deps[P] extends symbol diff --git a/src/syntax/binding_to_syntax.ts b/src/syntax/binding_to_syntax.ts index 2f300a07f..b158820b8 100644 --- a/src/syntax/binding_to_syntax.ts +++ b/src/syntax/binding_to_syntax.ts @@ -46,13 +46,13 @@ class BindingToSyntax implements interfaces.BindingToSyntax { return new BindingInWhenOnSyntax(this._binding); } - public toDynamicValueWithDeps( + public toDynamicValueWithDeps( deps: Deps, func: (dependencies: interfaces.ResolvedDeps, context: interfaces.Context) => T ): interfaces.BindingInWhenOnSyntax { return this.toDynamicValue((context) => { const resolvedDeps = deps.map((identifier) => - context.container.get(identifier as interfaces.ServiceIdentifier) + context.container.get(identifier) ) as unknown as interfaces.ResolvedDeps; return func(resolvedDeps, context) }) diff --git a/test/container/container.test.ts b/test/container/container.test.ts index e733f7c6e..3d81e1ceb 100644 --- a/test/container/container.test.ts +++ b/test/container/container.test.ts @@ -1183,12 +1183,10 @@ describe('Container', () => { @injectable() class Katana implements AbstractKatana {} - @injectable() class Ninja { public constructor(public shuriken: AbstractShuriken, public katana: AbstractKatana) {} } - const container = new Container() container.bind(AbstractShuriken).to(Shuriken) container.bind(AbstractKatana).to(Katana)