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

ginject

v0.1.0

Published

Dependency injection done right.

Downloads

7

Readme

npm version build Gitpod ready-to-code

Ginject [ʤɪnject] is a non-intrusive and typesafe dependency injection library for Node.js and JavaScript, powered by TypeScript.

Ginject empowers developers designing decoupled applications and frameworks. Ginject's main goal is increasing the developer experience by offering a tiny, yet powerful API, keeping dependencies in central module definitions and by using TypeScript's type system to restrain runtime challenges.

The concept of ginject's central module definition is inspired by Google Guice. However, ginject is going further by lifting the API to the functional level.

Despite its simplicity, ginject is powerful enough to cover all features provided by Inversify. Direct support for classes and constructors, property injection, rebinding dependencies and dependency cycle detection are just a few of the features worth mentioning.

| | ginject | inversify | |------------------|:----------:|:-----------:| | minified | minified size | minified size | | minzipped | minzipped size | minzipped size | | typesafe | ✅ | ❌ | | requirements | none | decorators | | style | functional | imperative | | API surface area | tiny | non-trivial |

Quickstart

The first step is to add ginject to your application.

npm i ginject

Bascially, the only thing needed is to define modules of factories and finally call inject. The resulting container provides concrete instances.

import { inject } from 'ginject';

// create an inversion of control container
const container = inject({
    hi: () => 'Hi',
    sayHi: () => (name: string) => `${container.hi} ${name}!`
});

// prints 'Hi Ginject!'
console.log(container.sayHi('Ginject'));

API

Terminology

The inject function is turning modules into a container. A module is a plain vanilla JS object, composed of nested groups and dependency factories. Factories may return any JS value, e.g. constants, singletons and providers. Unlike Inversify, there is no need to decorate classes.

import { inject, Module } from 'ginject';

// Defining a _context_ of dependencies
type Context = {
    group: {
        value: Value // any JS type, here a class
    }
}

// A _module_ contains nested _groups_ (optional) and _factories_
const module: Module<Context> = {
    group: {
        // a factory of type Factory<Context, Value>
        value: (ctx: Context) => new Value(ctx)
    }
};

// A _container_ of type Container<Module<Context>> = Context
const container = inject(module);

// Values can be obtained from the container
const value = container.group.value;

Context

A container provides each factory with a parameter called context.

type C = {
    value: string
}

const container = inject({
    factory: (ctx: C) => () => ctx.value
});

The context of type C provides a value that can't be resolved. The inject call is type-checked by TS the way that the completeness of the arguments is checked.

Such missing dependencies need to be provided by adding additional modules to the inject call.

const container = inject({
    factory: (ctx: C) => () => ctx.value
}, {
    value: () => '🍸'
});

Now the compiler is satisfied and we can start using the container.

// prints 🍸
console.log(container.factory());

You might have noticed that the container automatically calls the factory and injects itself as the context. The use-site receives the value.

Eager vs lazy initialization

A dependency container.group.value is lazily initialized when first accessed on the container. Turn a factory eager to initialize the dependency at the time of the inject call.

A use case for eager initialization would be to ensure that side effects take place during the initialization of the container.

import { eager, inject, Module } from 'ginject';

type C = {
    gin: string
}

const module: Module<C> = {
    gin: eager(() => {
        const gin = '🍸';
        console.log('Gin mixed');
        return gin;
    })
}

const ctr = inject(module);

console.log('App started');

ctr.gin

In the eager case, the output is

Gin mixed
App started

In the lazy case, the output is

App started
Gin mixed

Please note that eager factories overwrite lazy factories vice versa when rebinding them.

Rebinding dependencies

The main advantage of dependency injection arises from the fact that an application is able to rebind dependencies. That way the structure of a system can be fixated while the behavior can be changed.

The main vehicle for rebinding dependencies is the inject function which receives a variable amount of modules.

The behavior of an application can be enhanced by overwriting existing functionality using additional modules.

type C = {
    readonly print: () => void
    eval: (a: number, b: number) => number
}

const module_0: Module<C> = {
    print: (ctx) => () => {
        console.log(ctx.eval(1, 1));
    },
    eval: () => (a, b) => a + b
};

const ctr = inject(module_0, {
    eval: () => (a: number, b: number) => a * b
});

// = 1
ctr.print();

Cyclic Dependencies

Asynchronous Factories