From 6f7da897f1b0be13195f100848157762c21d710a Mon Sep 17 00:00:00 2001 From: Konstantin Burov Date: Tue, 22 Oct 2024 23:44:51 +1100 Subject: [PATCH 1/2] Make ClassInjectable infer dependency types. Update docs with an example that compiles. --- src/Container.ts | 9 ++++++--- src/Injectable.ts | 34 ++++++++++++++++++++++------------ src/PartialContainer.ts | 5 ++--- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Container.ts b/src/Container.ts index ba10659..a96d7c6 100644 --- a/src/Container.ts +++ b/src/Container.ts @@ -512,9 +512,12 @@ export class Container { ConcatInjectable(fn.token, () => this.providesService(fn).get(fn.token)) ) as Container; - private providesService[], Service>( - fn: InjectableFunction - ): Container> { + private providesService< + Token extends TokenType, + Tokens extends readonly ValidTokens[], + Service, + Dependencies, + >(fn: InjectableFunction): Container> { const token = fn.token; const dependencies: readonly any[] = fn.dependencies; // If the service depends on itself, e.g. in the multi-binding case, where we call append multiple times with diff --git a/src/Injectable.ts b/src/Injectable.ts index 01e5311..4b85d8f 100644 --- a/src/Injectable.ts +++ b/src/Injectable.ts @@ -106,20 +106,23 @@ export function Injectable( * @example * ```ts * class InjectableClassService { - * static dependencies = ["service"] as const; - * constructor(public service: string) {} - * public print(): string { - * console.log(this.service); - * } + * static dependencies = ["service"] as const; + * constructor(public service: string) {} + * public print(): void { + * console.log(this.service); + * } * } * - * let container = Container.provides("service", "service value") - * .provides(ClassInjectable("classService", InjectableClassService)); + * const container = Container.providesValue("service", "service value").provides( + * ClassInjectable("classService", InjectableClassService) + * ); * * container.get("classService").print(); // prints "service value" + * ``` * - * // prefer using Container's provideClass method. Above is the equivalent of: - * container = Container.provides("service", "service value") + * Prefer using Container's provideClass method. Above is the equivalent of: + * ```ts + * const container = Container.provides("service", "service value") * .providesClass("classService", InjectableClassService); * * container.get("classService").print(); // prints "service value" @@ -128,10 +131,15 @@ export function Injectable( * @param token Token identifying the Service. * @param cls InjectableClass to instantiate. */ -export function ClassInjectable( +export function ClassInjectable< + Class extends InjectableClass, + Dependencies extends ConstructorParameters, + Token extends TokenType, + Tokens extends Class["dependencies"], +>( token: Token, - cls: InjectableClass -): InjectableFunction; + cls: Class +): InjectableFunction, Tokens, Token, ConstructorReturnType>; export function ClassInjectable( token: TokenType, @@ -230,3 +238,5 @@ export function ConcatInjectable( factory.dependencies = [token, ...dependencies]; return factory; } + +export type ConstructorReturnType = T extends new (...args: any) => infer C ? C : any; diff --git a/src/PartialContainer.ts b/src/PartialContainer.ts index 8799eda..ab69297 100644 --- a/src/PartialContainer.ts +++ b/src/PartialContainer.ts @@ -1,6 +1,6 @@ import { entries } from "./entries"; -import { memoize } from "./memoize"; import type { Memoized } from "./memoize"; +import { memoize } from "./memoize"; import type { Container } from "./Container"; import type { AddService, @@ -10,10 +10,9 @@ import type { TokenType, ValidTokens, } from "./types"; +import type { ConstructorReturnType } from "./Injectable"; import { ClassInjectable, Injectable } from "./Injectable"; -type ConstructorReturnType = T extends new (...args: any) => infer C ? C : any; - // Using a conditional type forces TS language services to evaluate the type -- so when showing e.g. type hints, we // will see the mapped type instead of the AddDependencies type alias. This produces better hints. type AddDependencies = ParentDependencies extends any From 9f3114fe26722c67042684db174b6e81abc14b04 Mon Sep 17 00:00:00 2001 From: Mikalai Silivonik Date: Tue, 22 Oct 2024 11:04:20 -0400 Subject: [PATCH 2/2] updated the example code --- src/Injectable.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Injectable.ts b/src/Injectable.ts index 4b85d8f..4d95894 100644 --- a/src/Injectable.ts +++ b/src/Injectable.ts @@ -105,27 +105,27 @@ export function Injectable( * * @example * ```ts - * class InjectableClassService { - * static dependencies = ["service"] as const; - * constructor(public service: string) {} - * public print(): void { - * console.log(this.service); + * class Logger { + * static dependencies = ["config"] as const; + * constructor(private config: string) {} + * public print() { + * console.log(this.config); * } * } * - * const container = Container.providesValue("service", "service value").provides( - * ClassInjectable("classService", InjectableClassService) - * ); + * const container = Container + * .providesValue("config", "value") + * .provides(ClassInjectable("logger", Logger)); * - * container.get("classService").print(); // prints "service value" + * container.get("logger").print(); // prints "value" * ``` * - * Prefer using Container's provideClass method. Above is the equivalent of: + * It is recommended to use the `Container.provideClass()` method. The example above is equivalent to: * ```ts - * const container = Container.provides("service", "service value") - * .providesClass("classService", InjectableClassService); - * - * container.get("classService").print(); // prints "service value" + * const container = Container + * .providesValue("config", "value") + * .providesClass("logger", Logger); + * container.get("logger").print(); // prints "value" * ``` * * @param token Token identifying the Service.