2.1.0 • Published 6 months ago

@inversifyjs/strongly-typed v2.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

Test coverage npm version

@inversifyjs/strongly-typed

Type definitions for adding strong typing to the Container and @inject() decorator.

Getting started

This library can be used in a couple of ways, depending on your preference.

All usages wind up with you having a strongly-typed TypedContainer, which is a generic class that accepts a binding map as a type argument, which forms the contract that your container will adhere to:

import { TypedContainer } from '@inversifyjs/strongly-typed';

interface Foo { foo: string }
interface Bar { bar: string }

interface BindingMap {
  foo: Foo;
  bar: Bar;
}

const container = new TypedContainer<BindingMap>();

// Bindings are now strongly typed:

container.bind('foo').toConstantValue({foo: 'abc'}); // ok
container.rebind('foo').toConstantValue({unknown: 'uh-oh'}) // compilation error

let foo: Foo = container.get('foo') // ok
foo = container.get('bar') // compilation error
foo = container.get('unknown-identifier') // compilation error

Instantiation

The simplest way to use the library is to directly construct a TypedContainer:

import { TypedContainer } from '@inversifyjs/strongly-typed';

const container = new TypedContainer<BindingMap>();

This class is actually just a re-typed re-export of the vanilla Container, so shares all underlying functionality.

Type assertion

If you'd prefer to keep this library out of your final dependency tree, you can just import the types and perform a type assertion:

import { Container } from 'inversify';
// The type import will be stripped during transpilation
import type { TypedContainer } from '@inversifyjs/strongly-typed';

const container = new Container() as TypedContainer<BindingMap>;

Injection

The library also exposes a TypedInject type that will leverage the BindingMap you've used to strongly type your Container.

You'll need to re-export the inject decorator with a type assertion:

import { inject, multiInject } from 'inversify';
import type { TypedInject, TypedMultiInject } from '@inversifyjs/strongly-typed';

export const $inject = inject as TypedInject<BindingMap>;
export const $multiInject = multiInject as TypedMultiInject<BindingMap>;

You can now use this to strongly type injected constructor parameters, or public properties:

@injectable()
class B {
  public constructor(
    @$inject('foo') // ok
    foo: Foo,

    @$inject('foo') // compilation error
    bar: Bar,
  ) {}
}

@injectable()
class A {
  @$inject('foo') // ok
  public foo: Foo;

  @$inject('foo') // compilation error
  public bar: Bar;
}

Advanced usage

Promise bindings

inversify allows binding Promises, but these must be retrieved using getAsync() trying to resolve them using get() will throw.

The type signatures will help to enforce this if you show an async binding in the binding map with a Promise:

interface BindingMap {
  number: number;
  asyncNumber: Promise<number>;
}

const container = new TypedContainer<BindingMap>();

container.get('number') // number
container.get('asyncNumber') // compiler error
container.getAsync('asyncNumber') // Promise<number>

Container hierarchies

If setting a parent, types will need to be manually merged:

type ParentMap  = {...};
type ChildMap = {...};

const child = new TypedContainer<ParentMap & ChildMap>({
  parent,
});

Known issues / limitations

Strongly-typing private properties

Note that because the decorator types can't "see" private properties, we can't strongly type them:

@injectable()
class A {
  @$inject('foo')
  private foo: Foo; // fails :'(
}

Work around this by either:

  1. Making the property public (and perhaps prefixing it with an underscore to remind consumers it should be private)
  2. Using the weakly-typed @inject() directly from 'inversify'

Confusing compiler errors for constructor injection

There's no custom compiler errors for TypeScript, so we can't signal particularly usefully that a constructor parameter is wrong, apart from the fact that the compilation fails.

The error will look something like:

Unable to resolve signature of parameter decorator when called as an expression.
  Argument of type '2' is not assignable to parameter of type 'undefined'. (ts1239)

This actually means something like this:

The injected constructor parameter at index 2 is of the wrong type.

2.1.0

6 months ago

2.0.1

7 months ago

2.0.0

7 months ago

1.2.3

9 months ago

1.2.2

10 months ago

1.2.1

11 months ago

1.2.0

11 months ago

1.1.1

11 months ago

1.1.0

11 months ago