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

karambit-inject

v3.1.1

Published

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

Downloads

194

Readme

Karambit

NPM version NPM License Build

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 additional runtime logic for injection.
  • There is no need to mark or annotate most parameters, including interfaces, with "tokens". Karambit supports binding interfaces to concrete types directly.
  • Karambit is fully type-safe. It is not possible to register a provider with the wrong type.[^1]
  • 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. Instead, providers are declared idiomatically as constructors and static methods.
  • 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 :)

Installation

This project is available as a package in NPM.

$ npm install --save-dev karambit-inject
$ npm install karambit-decorators

Karambit is a decorator-based framework. The decorators exist just to annotate code, they have no effect at runtime. Karambit will work regardless of what value is set for the Typescript experimentalDecorators compiler flag.

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.

Using the CLI

The CLI has a simple command to run code generation.

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

A simple build script would look like:

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

Getting started

Components

Fundamentally, Karambit works by generating an implementation of each 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({modules: [HelloWorldModule]})
export abstract class HelloWorldComponent {
    abstract readonly greeter: Greeter
}

This Component exposes a single type, the Greeter, which during compile will be implemented by Karambit. Classes marked with @Component must be exported.

Providers

@Inject

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. Classes marked with @Inject must be exported.

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!`
    }
}

@Provides

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 static 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. Classes marked with @Module must be exported.

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

@Module
export abstract class HelloWorldModule {
    @Provides
    static 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 modules parameter of the @Component decorator.

@Component({modules: [HelloWorldModule]})

Putting it all together

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 "./karambit-generated/component"
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 extends component_1.HelloWorldComponent {
    constructor() {
        super();
    }
    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 clearly 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 small, real-world migration example, check out this PR that bootstrapped Karambit to use itself for dependency injection.

License

Copyright 2022-2024 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.