@inversifyjs/strongly-typed
v1.2.2
Published
InversifyJs strong type definitions
Downloads
614
Maintainers
Readme
@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 Promise
s, 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
A strongly-typed child container can be created from a parent container.
This strong typing is optional for the child:
const parent = new TypedContainer<BindingMap>();
const child = parent.createChild(); // weakly typed
If the child is strongly-typed, its binding will automatically be adjusted to include the parent types:
const parent = new TypedContainer<{foo: Foo}>();
const child = parent.createChild<{bar: Bar}>();
child.get('bar') // ok
child.get('foo') // ok
A child may also override a parent's bindings with a completely unrelated type:
const parent = new TypedContainer<{foo: Foo}>();
const child = parent.createChild<{foo: Bar}>();
const resolved: Bar = child.get('foo');
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:
- Making the property
public
(and perhaps prefixing it with an underscore to remind consumers it should be private) - 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.