Options
All
  • Public
  • Public/Protected
  • All
Menu

ChevronJS

A TypeScript IoC library for lazy dependency injection.

Introduction

Chevron is a TypeScript IoC library for lazy dependency injection inspired by the AngularJS Module API and Spring's DI System. Unlike similar libraries like InversifyJS, Ana and others, Chevron does not depend on reflection metadata or compile time tooling; The trade-off by not using those is that less type validation and Quality of Life features are possible.

Not suitable for production, use InversifyJS instead of this.

Docs

Usage

npm install chevronjs

Basic usage:

import { Chevron } from "chevronjs";

// Create a new chevron instance.
const chevron = new Chevron<null>();

type LoggingNoop = () => void;

const myFunction: LoggingNoop = () => {
    console.log("Hello world!");
};

// Register the myFunction variable as a plain injectable.
chevron.registerInjectable<LoggingNoop, LoggingNoop>(myFunction);

// Retrieve injectable (could also be done using `chevron.getInjectableInstance("myFunction")`.
const myFunctionInstance = chevron.getInjectableInstance<LoggingNoop>(
    myFunction
);

Custom names can be set like this:

import { Chevron } from "chevronjs";

const chevron = new Chevron<null>();

type LoggingNoop = () => void;

const myFunction: LoggingNoop = () => {
    console.log("Hello world!");
};
chevron.registerInjectable<LoggingNoop, LoggingNoop>(myFunction, {
    // A custom name can either be a string or another nameable value like a function.
    name: "myCoolName",
});

const myFunctionInstance = chevron.getInjectableInstance<LoggingNoop>(
    "myCoolName"
);

Factories

A core feature are factories, which describe the process how injectable instances are created. The default factory for this is DefaultFactory.IDENTITY which means that values are not modified during instantiation. When dealing with dependencies though (Chapter "Dependencies"), it is essential to have a factory which allows usage of those dependencies: DefaultFactory.CLASS allows class-like injectables to receive their dependencies as constructor parameters and DefaultFactory.FUNCTION allows injectables that are plain functions to simply receive them as arguments.

import { Chevron, InjectableClassInitializer, DefaultFactory } from "chevronjs";

const chevron = new Chevron<null>();

class MyClass {
    private readonly modifier: number;

    public constructor() {
        this.modifier = 2;
    }

    public getDouble(n: number): number {
        return n * this.modifier;
    }
}

chevron.registerInjectable<MyClass, InjectableClassInitializer<MyClass, void>>(
    MyClass,
    {
        // Use the "CLASS" factory to instantiate the value as class
        factory: DefaultFactory.CLASS(),
    }
);

const myClassInstance = chevron.getInjectableInstance<MyClass>(MyClass);
import { Chevron, DefaultFactory } from "chevronjs";

const chevron = new Chevron<null>();

type MathUnaryOperation = (val: number) => number;
const multiply: MathUnaryOperation = (val: number) => val * 2;

const myFunction: () => MathUnaryOperation = () => multiply;
chevron.registerInjectable<MathUnaryOperation, () => MathUnaryOperation>(
    myFunction,
    {
        // Use the "FUNCTION" factory to instantiate the value as a function
        factory: DefaultFactory.FUNCTION(),
    }
);

const myFunctionInstance = chevron.getInjectableInstance<MathUnaryOperation>(
    myFunction
);

Custom factories can also be used to modify values during instantiation:

import { Chevron } from "chevronjs";

const chevron = new Chevron<null>();

const myInjectable = 16;
chevron.registerInjectable<number, number>(myInjectable, {
    factory: (val: number) => val * 2,
    name: "val",
});

const myFunctionInstance = chevron.getInjectableInstance<number>("val");

Dependencies

When an injectable relies on others in order to be instantiated, you can declare those as its dependencies:

import { Chevron, InjectableClassInitializer, DefaultFactory } from "chevronjs";

const chevron = new Chevron<null>();

type MathFn = (a: number) => number;
const doublingFn: MathFn = (a: number) => a * 2;

chevron.registerInjectable<MathFn, MathFn>(doublingFn);

class MyClass {
    public constructor(private readonly doublingFnAsDep: MathFn) {}

    public getDouble(n: number): number {
        return this.doublingFnAsDep(n);
    }
}

/*
 * Register injectable with dependency - we could also use `["doublingFn"]`.
 * We want MyClass to be instantiated by constructing it through the CLASS factory,
 * where we will have the dependencies as constructor parameters.
 */
chevron.registerInjectable<MyClass, InjectableClassInitializer<MyClass>>(
    MyClass,
    {
        dependencies: [doublingFn],
        factory: DefaultFactory.CLASS(),
    }
);

// When retrieving, all dependencies will be resolved first.
const myClassInstance = chevron.getInjectableInstance<MyClass>(MyClass);

All dependencies and sub-dependencies will be resolved and instantiated if needed when retrieving an injectable that needs to be instantiated.

Scopes

Injectables can have scopes defining when new instances are created and reused; By default, DefaultScopes.SINGLETON is used, meaning only a single instance of each injectable will be created which will be reused for every retrieval. There is also DefaultScopes.PROTOTYPE which is the opposite, creating a new instance for every single request.

import {
    Chevron,
    InjectableClassInitializer,
    DefaultFactory,
    DefaultScope,
} from "./src/main";

const chevron = new Chevron<null>();

class MyClass {}

chevron.registerInjectable<MyClass, InjectableClassInitializer<MyClass, void>>(
    MyClass,
    {
        factory: DefaultFactory.CLASS(),
        scope: DefaultScope.PROTOTYPE(),
    }
);

const myClassInstance1 = chevron.getInjectableInstance<MyClass>(MyClass);
const myClassInstance2 = chevron.getInjectableInstance<MyClass>(MyClass);

Scopes can be also be used to provide for example session based instances:

import {
    Chevron,
    DefaultFactory,
    InjectableClassInitializer,
} from "./src/main";

interface SessionContext {
    sessionId: string;
}

const chevron = new Chevron<SessionContext>();

class MySession {}

chevron.registerInjectable<
    MySession,
    InjectableClassInitializer<MySession, void>
>(MySession, {
    factory: DefaultFactory.CLASS(),
    // Define a custom scope to create scopes based on the property `sessionId` of the context.
    scope: (context: SessionContext | null) => {
        if (context == null) {
            return "DEFAULT";
        }
        return context.sessionId;
    },
});

// Injectable retrieval can pass optional context data to influence scoping.
const mySessionInstanceFoo = chevron.getInjectableInstance<MySession>(
    MySession,
    {
        sessionId: "123",
    }
);
const mySessionInstanceBar = chevron.getInjectableInstance<MySession>(
    MySession,
    {
        sessionId: "987",
    }
);
const mySessionInstanceBarAgain = chevron.getInjectableInstance<MySession>(
    MySession,
    { sessionId: "987" }
);

Note that if a scope function returns null, a new instance, that will not be re-used, will be created.

TypeScript Decorators

Chevron provides also provides TypeScript Decorators for its API. Keep in mind that decorators are an experimental TypeScript feature and might not be fully stabilized yet.

import { Chevron, Injectable } from "./src/main";

const chevron = new Chevron<null>();

// Same as chevron.registerInjectable(Foo, { factory: DefaultFactory.CLASS() });
@Injectable<Foo>(chevron)
class Foo {
    public getFoo(): string {
        return "foo";
    }
}

@Injectable<FooBar>(chevron, {
    dependencies: [Foo],
})
class FooBar {
    public constructor(private readonly foo: Foo) {}

    public getFooBar(): string {
        return this.foo.getFoo() + "bar";
    }
}

const fooBarInstance = chevron.getInjectableInstance<FooBar>(FooBar);

Index

Type aliases

Factory

Factory<TInstance, UInitializer, VDependency, WContext>: (initializer: UInitializer, dependencies: VDependency[], context: WContext, injectableEntryName: string) => TInstance

Function interface for a factory function. Instantiation takes place when creating an injectable instance from its initializer, e.g. by constructing a class (DefaultFactory.CLASS) or executing a factory function (DefaultFactory.FUNCTION).

The factory function has access to the initializer and its initialized dependencies (in the order they were defined in), as well as the name and injectable that is to be instantiated, and should return an instantiated value for them.

Type parameters

  • TInstance

  • UInitializer

  • VDependency

  • WContext

Type declaration

    • (initializer: UInitializer, dependencies: VDependency[], context: WContext, injectableEntryName: string): TInstance
    • Parameters

      • initializer: UInitializer
      • dependencies: VDependency[]
      • context: WContext
      • injectableEntryName: string

      Returns TInstance

InjectableFunctionInitializer

InjectableFunctionInitializer<TInstance, VDependency>: (...args: VDependency[]) => TInstance

Interface representing a function which can be constructed.

Type parameters

  • TInstance

  • VDependency

Type declaration

    • (...args: VDependency[]): TInstance
    • Parameters

      • Rest ...args: VDependency[]

      Returns TInstance

Scope

Scope<TContext>: (context: TContext, injectableEntryName: string) => string | null

Function interface for a scope function. Scoping takes place when attempting to retrieve an instance, deciding if an existing instance should be reused, and if, which one. Each injectable can have a number of instances which were instantiated by the same initializer using scopes. E.g. for a singleton injectable for which only a single instance should exist at all times, {@link DefaultScope#SINGLETON} is used.

The scope function takes user defined context data (e.g. for session handling), the injectable name and its entry, and returns a string identifier (or null, more on that later). for each unique identifier, the same instance will be reused. If null is returned ({@link DefaultScope#PROTOTYPE}), a new instance will be created for every request, and it will not be stored as instance of the injectable for re-use.

Type parameters

  • TContext

Type declaration

    • (context: TContext, injectableEntryName: string): string | null
    • Parameters

      • context: TContext
      • injectableEntryName: string

      Returns string | null

Functions

Const Injectable

  • Registers a new injectable on a container. See {@link Chevron#registerInjectable} for details.

    Decorator function for use with TypeScript. Use this decorator on a variable or function/class expression.

    Note that, as decorators only work for classes and class related constructs, the factory defaults to DefaultFactory.CLASS.

    throws

    Error when an injectable with the requested name is already registered.

    throws

    TypeError when no name can be determined for this injectable or any of its dependencies.

    Type parameters

    • TInstance

      type a constructed instance will have.

    • UDependency

      should not be set explicitly usually. Type of the dependencies used by this injectable.

    • VContext

      should not be set explicitly usually. Type of the context used for scoping.

    Parameters

    • instance: Chevron<VContext | null>

      Chevron instance to register the injectable on.

    • Default value options: InjectableOptions<TInstance, InjectableClassInitializer<TInstance, UDependency>, UDependency, VContext | null> = {}

      Options for this injectable. See {@link Chevron#registerInjectable} for details.

    Returns (Anonymous function)

Const classFactoryFactory

  • classFactoryFactory<TInstance, UInitializer, VDependency, WContext>(): Factory<TInstance, InjectableClassInitializer<TInstance, VDependency>, VDependency, WContext>
  • Creates a Factory which constructs the initializer with the dependencies as parameters.

    throws

    TypeError when used with a non-function initializer.

    Type parameters

    • TInstance

    • UInitializer

    • VDependency

    • WContext

    Returns Factory<TInstance, InjectableClassInitializer<TInstance, VDependency>, VDependency, WContext>

Const functionFactoryFactory

  • Creates a Factory which returns a function executing the initializer with the dependencies as parameters.

    throws

    TypeError when used with a non-function initializer.

    Type parameters

    • TInstance

    • UInitializer

    • VDependency

    • WContext

    Returns Factory<TInstance, InjectableFunctionInitializer<TInstance, VDependency>, VDependency, WContext>

Const identityFactoryFactory

  • identityFactoryFactory<TInstance, UInitializer, VDependency, WContext>(): Factory<TInstance, TInstance, VDependency, WContext>
  • Creates a Factory which immediately returns the initializer. This is useful for injectables which do not require any other initialization. Note that by using this factory, no usage of dependencies for this value is possible.

    Type parameters

    • TInstance

    • UInitializer

    • VDependency

    • WContext

    Returns Factory<TInstance, TInstance, VDependency, WContext>

Const prototypeScopeFactory

  • prototypeScopeFactory<TScope>(): Scope<TScope>
  • Creates a Scope which forces instantiation of a new instance every time the injectable is requested.

    Type parameters

    • TScope

    Returns Scope<TScope>

Const singletonScopeFactory

  • singletonScopeFactory<TScope>(): Scope<TScope>
  • Creates a Scope which forces usage of a single instance for every request.

    Type parameters

    • TScope

    Returns Scope<TScope>

Object literals

Const DefaultFactory

DefaultFactory: object

Pseudo-enum of built-in Factorys.

CLASS

CLASS: classFactoryFactory = classFactoryFactory

FUNCTION

FUNCTION: functionFactoryFactory = functionFactoryFactory

IDENTITY

IDENTITY: identityFactoryFactory = identityFactoryFactory

Const DefaultScope

DefaultScope: object

Pseudo-enum of built-in Scopes.

PROTOTYPE

PROTOTYPE: prototypeScopeFactory = prototypeScopeFactory

SINGLETON

SINGLETON: singletonScopeFactory = singletonScopeFactory

Legend

  • Constructor
  • Property
  • Constructor
  • Method

Generated using TypeDoc