npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dandi/di-core

v1.0.0-alpha.12

Published

Dandi's dependency injection is heavily influenced by [Angular](https://angular.io)'s DI system.

Downloads

13

Readme

@dandi/di-core

Dandi's dependency injection is heavily influenced by Angular's DI system.

Concepts

  • Resolver - responsible for resolving and instantiating dependencies
  • Container - in @dandi, the main Resolver implementation, which also includes logic for discovering injectable services, as well as storing references of singleton dependencies
  • Provider - An object which describes how a request for a dependency is resolved by providing the value directly, or describing how to create the value (class constructor, factory function, etc)
  • Injection Token - A value that represents an injectable dependency. Can be a class constructor, or a Symbol value representing an interface or any other concept.

Describing Injectable Services

@Injectable() Decorator

The simplest method of describing an injectable service is to add the @Injectable() decorator to it. This tells the resolver that when it encounters a dependency of the decorated class, it will instantiate a new instance of that class:

import { Injectable } from '@dandi/di-core';

@Injectable()
class MyService {
}

The @Injectable() decorator can also be used to register a service for a different injection token, such as a token representing an interface:

import { InjectionToken, Provider, SymbolToken } from '@dandi/di-core';

export interface MyInterface {
}

export const MyInterface: InjectionToken<MyInterface> = SymbolToken.for<MyInterface>('MyInterface');

@Injectable(MyInterface)
export class MyService implements MyInterface {
}

Note: The above pattern takes advantage of TypeScript's declaration merging feature. Since interfaces are only types (and are not available at runtime), there needs to be a concrete value to represent them. Dandi uses a const of the same name to represent an interface's injection token, allowing for consistency when looking at code using the @Injectable() and @Inject() decorators.

Providers

Providers allow you to configure the resolver to map any kind of token to any value or implementation. They are most commonly used to register implementations of interfaces.

Value Providers

A value provider allows mapping an existing value to an injection token.

import { InjectionToken, Provider, SymbolToken } from '@dandi/di-core';

const SomeValue: InjectionToken<string> = SymbolToken.for<string>('SomeValue');

const SomeValueProvider: Provider<string> = {
    provide:  SomeValue,
    useValue: 'any-value-you-like-here',
};

Factory Providers

A factory provider allows mapping a factory function to an injection token. This can be helpful for making 3rd party classes injectable.

import { InjectionToken, Provider, SymbolToken } from '@dandi/di-core';
import { S3 } from 'aws-sdk';

export function s3Factory(): S3 {
    return new S3({ endpoint: 'http://local-dev-endpoint' });
}

export const S3Provider: Provider<S3> = {
    provide:    S3,
    useFactory: s3Factory,
};

Class Providers

A class provider allows mapping a class constructor to an injection token.

import { InjectionToken, Provider, SymbolToken } from '@dandi/di-core';

export interface MyInterface {
}

export const MyInterface: InjectionToken<MyInterface> = SymbolToken.for<MyInterface>('MyInterface');

export class MyService implements MyInterface {
}

export const MyInterfaceProvider: Provider<MyInterface> = {
    provide:  MyInterface,
    useClass: MyService,
};

In the above example, MyInterfaceProvider allows requests for MyInterface to be resolved as instances of MyService.

Describing Dependencies

Use the @Inject() decorator to describe dependencies in a constructor:

@Injectable()
class ServiceA {

    public getSomething(): Promise<Something> {
        ...
    }

}

@Injectable()
class ServiceB {

    constructor(
        @Inject(ServiceA) private serviceA: ServiceA,
    ) {}

    public async doSomething(): Promise<void> {
        const something = await this.serviceA.getSomething();
        console.log(something);
    }

}

The @Inject() decorator can also be used to describe dependencies for a function or method. While @dandi does not automatically wrap function calls, decorated functions can be invoked by a Resolver's invoke and invokeInContext methods:

// assigned elsewhere
declare const resolver: Resolver;

function doSomething(@Inject(MyService) myService: MyService): void {
}

resolver.invoke(null, doSomething); // returns a Promise

Optional Dependencies

Dependencies can be marked as option using the @Optional() decorator. Optional dependencies that cannot be resolved will be resolved with null.

class MyService {

    constructor(
        @Inject(MyDependency) @Optional() private myDep: MyDependency,
    ) {}

}

Service Discovery

Classes and providers that are used by an application must be passed to the container at startup:

import { Container } from '@dandi/di-core';

const appContainer = new Container({
    providers: [
        MyService,
        MyInterfaceProvider,
    ];
});

Values passed to the providers property can be class constructors, Provider instances, or arrays of either.

Additionally, Dandi includes a Scanner interface which allows implementations of automatic service discovery.

AmbientInjectableScanner will automatically register any services marked with @Injectable() that located in any module loaded by NodeJS.

FileSystemScanner can be used in conjunction with AmbientInjectableScanner to automatically load modules from paths defined in its configuration.

Application Startup and Bootstrapping

The container's start() method must be called to initialize the container and start the application.

import { Container } from '@dandi/di-core';

const appContainer = new Container({
    providers: [
        MyService,
        MyInterfaceProvider,
    ];
});

container.start();

Startup logic is defined by providing an implementation of the Bootstrapper interface:

import { Bootstrapper, Container, Inject, Injectable } from '@dandi/di-core';

@Injectable(Bootstrapper)
class AppBootstrapper implements Bootstrapper {

    constructor(
        @Inject(MyService) private myService: MyService,
    ) {}

    public start(): void {
        // start the app
        this.myService.listen();
    }

}

const appContainer = new Container({
    providers: [
        AppBootstrapper,
        MyService,
        MyInterfaceProvider,
    ];
});

container.start();