microsoft / tsyringe

Lightweight dependency injection container for JavaScript/TypeScript
MIT License
5.18k stars 172 forks source link

Typechecking for token-based injection #239

Open chad-fossa opened 7 months ago

chad-fossa commented 7 months ago

Is your feature request related to a problem? Please describe. Could we somehow have a way to get typechecking for token-based injection? Right now, if I run @inject('someToken') protected fieldName: SomeType and then later do container.register('someToken', {useValue: ThingThatIsNotSomeType}), I do not get any feedback from tsyringe at all. This means that I cannot specify Types or Interfaces as my fieldNames without losing type safety.

Description

type SomeThingRuntimeConfig  = {
    purpleValue: string;
}

class SomeThing {
  constructor(protected runtimeConfig: SomeThingRuntimeConfig) {}
  function purple(): string { return this.runtimeConfig.purpleValue: }
}

@registry([
{ token: 'MyThing', useValue: SomeThing}
])
class MyClass {
  constructor(@inject('MyThing') protected someThingType: typeof SomeThing) {}

  function runtimeBlah(configThing: SomeThingRuntimeConfig) {
      const someThing = new this.someThingType(configThing);
      someThing.purple()
  }
}

...

container.register('MyThing', NotSomeThing) // knows there's a conflict or throws a runtime error

Alternate solutions

Building a factory and injecting that "works", and so does moving the run-time param to a method call. Both of these force a design constraint on users, with the factory adding to boilerplate code and increasing complexity and with the run-time parameters requiring potentially overly burdensome refactoring depending on existing class structure.

Similarly, when I want to inject a simple config parameter, creating a ConfigProvider class that returns a typed config object and then injecting the class vs being able to inject a simple object which matches a type is also burdensome.

Additional context

The lack of type safety on string tokens requires either sacrificing type safety when using one of the leading DI tools for TypeScript or forces specific design patterns which increase complexity simply to enable using TSyringe for DI while maintaining the ability to typecheck dependencies. This tradeoff adds friction to adoption of TSyringe.