karambit-inject
    Preparing search index...

    karambit-inject

    Karambit

    NPM version NPM License Build Docs

    A compile-time and type-safe dependency injector for TypeScript.

    Karambit is different from other TypeScript dependency injection libraries in several key ways:

    • It is a fully compile-time code generation framework. Karambit generates plain-old TypeScript code and there's no runtime dependency.
    • Karambit is fully type-safe. It is not possible to register a provider with the wrong type.[^1]
    • There is no need to mark or annotate most parameters, including interfaces, with "tokens". Karambit supports binding interfaces to concrete types directly.
    • The dependency graph is fully validated during compilation, so if your project builds then you can be certain the graph is valid. This includes detecting missing bindings, dependency cycles, and other common error sources.
    • Creating a graph is fully declarative; you never need to imperatively register a dependency to a container, and there's no runtime initialization cost.
    • Karambit is fully transparent to your application code. Because of this, it plays nicely with other libraries and frameworks. If you wanted to stop using Karambit later, you could simply commit the generated code to your repository and remove Karambit entirely.

    Karambit is heavily inspired by Dagger, and if you're familiar with that then you'll be right at home using Karambit.

    [^1]: Of course, you can still use TypeScript features like any or as to break anything you want :)

    This project is available as a package in NPM.

    $ npm install --save-dev karambit-inject
    

    Karambit works by generating based on JSDoc tags (or, alternatively, decorators).

    Karambit works using a simple CLI-based tool for generating code. Once the code is generated, you import it just like any other TypeScript code.

    For a minimal example project, check out the Hello World sample.

    The CLI has a simple command to run code generation.

    $ karambit path/to/your/tsconfig.json -o output-file.ts
    

    A simple build script would look like:

    {
    "scripts": {
    "prebuild": "karambit",
    "build": "tsc"
    }
    }

    Fundamentally, Karambit works by generating an implementation of each interface or abstract class marked @component.

    The Component is what ultimately hosts a dependency graph, and how you expose that graph to other parts of your application. You can think of the Component as the entry-point into your graph. In the Hello World sample, the Component looks like this:

    /**
    * @component
    * @includeModule {@link HelloWorldModule}
    */
    export interface HelloWorldComponent {
    readonly greeter: Greeter
    }

    This Component exposes a single type, the Greeter. The implementation of HelloWorldComponent will be generated by Karambit.

    The next step is to satisfy the dependency graph of the Component. Karambit isn't magic; you need to specify how to get an instance of each type in the graph.

    There are several ways to do this, but the simplest is to mark a class with @inject. This makes the constructor of that class available to Karambit, and Karambit will call the constructor to provide an instance of that type.

    In this sample, the Greeter class is marked @inject, and this type is available in the graph.

    /** @inject */
    export class Greeter {
    constructor(private readonly greeting: string) { }
    greet(): string {
    return `${this.greeting}, World!`
    }
    }

    The constructor of Greeter depends on one other type: string. However, we can't simply mark string's constructor with @inject. This is where Modules come in to play.

    A module is a collection of methods marked with @provides and each Component can install many Modules. These provider methods work just like @inject constructors; they can have arguments and will be used by Karambit to provide an instance of their return type.

    In our example, the string type is provided in the HelloWorldModule:

    export const HelloWorldModule = {
    /** @provides */
    provideGreeting(): string {
    return "Hello"
    },
    }

    You can think of Modules as the private implementation of a Component, which itself is sort of a public interface. The component defines what Karambit should construct, and the modules define how to construct them.

    Modules are installed in Components via the @includeModule tag on the component declaration.

    /**
    * @component
    * @includeModule {@link HelloWorldModule}
    */

    By providing the string type into our graph, all the required types are now satisfied with providers and Karambit can generate our Component implementation.

    You can instantiate a Component by instantiating the generated class:

    import {KarambitHelloWorldComponent} from "./gen/karambit"
    const component = new KarambitHelloWorldComponent()
    console.log(component.greeter.greet()) // "Hello, World!"

    When running Karambit, it will generate this implementation:

    import * as component_1 from "../component";
    export class KarambitHelloWorldComponent implements component_1.HelloWorldComponent {
    get greeter(): component_1.HelloWorldComponent["greeter"] { return this.getGreeter_1(); }
    private getGreeter_1() { return new component_1.Greeter(this.getString_1()); }
    private getString_1() { return component_1.HelloWorldModule.provideGreeting(); }
    }

    While this example is a bit contrived, you should be able to see how simple it can be to add new types to a graph and build much more complex dependency structures.

    This is only scratching the surface of what Karambit is capable of, so check out the feature guide for a more in-depth look at everything it has to offer. For a more real-world example, check out Karambit's Component declarations.

    Copyright 2022-2025 Devin Price
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
       http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.