Dependency Injection Container Compiler
The last Dependency Injection solution for TypeScript you will ever use.
No @decorators
or any other bloat needed: works off of TypeScript types.
The only place in your code you will ever import { anything } from 'dicc'
will be inside a handful of resource files.
- type-based autowiring, doesn't care about type or argument names
- supports multiple services of the same type
- first-class support for async services (that is, services which need to be created asynchronously)
- supports scoped services private to a given asynchronous execution context, as well as fully private services
- supports dynamic services which are known to the container, but must be registered manually in order to be available as dependencies to other services
- supports auto-generated service factories from interface declarations and abstract classes
- supports service decorators (not the same thing as
@decorators
) which allow some modifications to service definitions without needing to alter the definitions themselves - supports merging multiple containers, such that public services from a merged container are available for injection into services in the parent container
- compiles to regular TypeScript which you can easily examine to see what's going on under the hood
- cyclic dependency checks run on compile time, preventing possible deadlocks at runtime
- no special compiler flags, no
reflect-metadata
, no junk in your source code, minimal runtime footprint
DICC is split into two packages, because the compiler depends on TypeScript and ts-morph, which are probably both something you want to be able to prune from your production node modules. The runtime package is tiny and doesn't have any other dependencies.
# Compile-time dependency:
npm i --save-dev dicc-cli
# Runtime dependency:
npm i --save dicc
Writing services and specifying dependencies:
// services.ts
import type { ServiceDefinition } from 'dicc';
// simple service with no dependencies:
export class ServiceOne {
}
// similar, but with an async factory:
export class ServiceTwo {
static async create(): Promise<ServiceTwo> {
return new ServiceTwo();
}
}
// service with dependencies:
export class ServiceThree {
constructor(
private readonly one: ServiceOne,
private readonly two: ServiceTwo,
) {}
}
class Entrypoint {
constructor(
readonly one: ServiceOne,
readonly two: ServiceTwo,
readonly three: ServiceThree,
) {}
}
export const entrypoint = Entrypoint satisfies ServiceDefinition<Entrypoint>;
Compiled container generated by running dicc
with the previous code snippet
as its input:
import { Container, type ServiceType } from 'dicc';
import * as services0 from './services.ts';
export interface PublicServices {
entrypoint: ServiceType<typeof services0.entrypoint>;
}
export interface AnonymousServices {
'#ServiceOne0.0': services0.ServiceOne;
'#ServiceTwo0.0': Promise<services0.ServiceTwo>;
'#ServiceThree0.0': Promise<services0.ServiceThree>;
}
export class AppContainer extends Container<PublicServices, {}, AnonymousServices> {
constructor() {
super({
'entrypoint': {
factory: async (di) => new services0.entrypoint(
di.get('#ServiceOne0.0'),
await di.get('#ServiceTwo0.0'),
await di.get('#ServiceThree0.0'),
),
},
'#ServiceOne0.0': {
factory: () => new services0.ServiceOne(),
},
'#ServiceTwo0.0': {
factory: async () => services0.ServiceTwo.create(),
async: true,
},
'#ServiceThree0.0': {
factory: async (di) => new services0.ServiceThree(
di.get('#ServiceOne0.0'),
await di.get('#ServiceTwo0.0'),
),
async: true,
},
});
}
}
The DICC compiler actually uses DICC itself, so you can look at its source code to see a simple real-world example of its configuration, some explicit service definitions and the resulting compiled container, as well as of how the container is used.
If you find a bug, please feel free to file an issue, even if you can't provide a pull request with a fix! Nobody will be shamed here for not having the time to invest into fixing other people's code. I set this boat out to sea, so it's my responsibility to keep it floating.
That said, I do welcome pull requests as well - whether they be bug fixes or new features. There's no formal code style, if I have an issue with your indentation or something, I'll just fix it.