Skip to content
/ dicc Public

Dependency Injection Container Compiler for TypeScript

Notifications You must be signed in to change notification settings

cdn77/dicc

Repository files navigation

DICC

Dependency Injection Container Compiler

About

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.

Highlights

  • 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

Installation

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

Example usage

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.

Contributing

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.

About

Dependency Injection Container Compiler for TypeScript

Resources

Stars

Watchers

Forks

Packages

No packages published