dicc
TypeScript icon, indicating that this package has built-in type declarations

0.0.28 • Public • Published

DICC

Dependency Injection Container Compiler

Documentation

About

This is a project to end all current TypeScript DI implementations. I mean it. All of them. With extreme prejudice.

Why? Because they are all based on decorators. (Well, there is one exception, but that one doesn't - and cannot, properly - support async services, hence this project.) Don't get me wrong - decorators are awesome! I love decorators. I've built a pretty big library based entirely on decorators.

But - and I cannot stress this enough - decorator-based dependency injection breaks one of the most sacred dependency injection principles: your code should almost NEVER know or care that dependency injection even exists, and it most certainly shouldn't know anything about the specifics of the DI implementation - in other words, your code should not depend on your preferred dependency injection solution. Because if it does, then it's not portable, you can't just easily extract parts of it into a shared library, you cannot test it independently of the DI framework, you're simply locked in, you're alone in the dark, you're locked in and there are drums in the deep and they are coming and you cannot get out and you don't have Gandalf and

</rant>

<zen>

DICC can't do the pointy hat trick, but it does offer an alternative solution to dependency injection. Using a simple YAML config file, you specify one or more resource files, which are regular TypeScript files inside your project. From these resource files you export some classes, interfaces, and possibly some constant expressions, and then you point the DICC CLI to your config file and DICC will produce a compiled file, which exports a fully typed and autowired dependency injection container.

The only place in your code you will ever import { anything } from 'dicc' will be inside the resource file (or files).

Highlights

  • type-based autowiring, doesn't care about type or parameter 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 service decorators (not the same thing as @Decorators) which allow some modifications to service definitions without needing to alter the definitions themselves
  • 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

// 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,
  ) {}
}

Compiled container generated by running dicc with the previous code snippet as its input:

import { Container } from 'dicc';
import * as defs0 from './services.ts';

export interface Services {
  '#ServiceOne.0': defs0.ServiceOne,
  '#ServiceTwo.0': defs0.ServiceTwo,
  '#ServiceThree.0': defs0.ServiceThree,
}

export const container = new Container<Services>({
  '#ServiceOne.0': {
    factory: () => new defs0.ServiceOne(),
  },
  '#ServiceTwo.0': {
    async: true,
    factory: async () => defs0.ServiceTwo.create(),
  },
  'ServiceThree.0': {
    async: true,
    factory: async (di) => new defs0.ServiceThree(
      di.get('#ServiceOne.0'),
      await di.get('#ServiceTwo.0'),
    ),
  },
});

The DICC compiler actually uses DICC itself, so you can look at its source code to see a simple real-world example of 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.

Package Sidebar

Install

npm i dicc

Weekly Downloads

0

Version

0.0.28

License

MIT

Unpacked Size

23.6 kB

Total Files

12

Last publish

Collaborators

  • jahudka