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

djinject

v0.5.0

Published

Dependency injection done right.

Downloads

7

Readme

npm version build Gitpod ready-to-code

Djinject empowers developers designing decoupled applications and frameworks. Djinject'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.

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

Features

  • type-safe
  • tiny footprint
  • property injection
  • rebinding dependencies
  • dependency cycle detection
  • lazy and eager initialization
  • no magic, no global state
  • no decorators
  • no dependencies
  • no configuration
  • no boilerplate

Quickstart

The first step is to add djinject to your application.

npm i djinject

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

import { inject } from 'djinject';

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

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

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 'djinject';

// 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 = {
    group: {
        // a factory of type Factory<Context, Value>
        value: (ctx: Context) => new Value(ctx)
    }
} satisfies Module<Context>;

// 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
}

// ❌ compiler error: value is missing
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.

// ✅ fixed, value is defined
const container = inject({
    createValue: (ctx: C) => () => ctx.value
}, {
    value: () => '🧞‍♀️'
});

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

// prints 🧞‍♀️
console.log(container.createValue());

You might have noticed that the container automatically injects itself as the context when calling the createValue function.

Eager vs lazy initialization

A dependency container.group.value is lazily initialized when first accessed on the container. Initialize a factory eagerly at the time of the inject call by wrapping it in an init call. Hint: groups can be eagerly initialized as well.

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

import { init, inject, Module } from 'djinject';

type C = {
    logger: string
}

const module = {
    service: init(() => {
        console.log('Service initialized');
    })
} satisfies Module<C>;

const ctr = inject(module);

console.log('App started');

ctr.service

In the eager case, the output is

Service initialized
App started

In the lazy case, the output is

App started
Service initialized

Please note that eager factories overwrite lazy factories vice versa when rebinding them using additional modules in the inject call.

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 = {
    test: () => void
    eval: (a: number, b: number) => number
}

const m1 = {
    test: (ctx) => () => {
        console.log(ctx.eval(1, 1));
    },
    eval: () => (a, b) => a + b
} satisfies Module<C, C>; // requires C

const m2 = {
    eval: () => (a, b) => a * b
} satisfies Module<C>; // partial C

const ctr = inject(m1, m2);

// = 1
ctr.test();