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

@mft/di

v1.0.4

Published

Dependency injection

Downloads

12

Readme

DI

npm dependency Status devDependency Status license node

Simple, predictable dependency injection

Example

function myDependencyFactory() {
  return {
    doTheThing() {
      return "done"
    }
  }
}
function myServiceFactory({myDependency}) {
  return {
    doAThing() {
      return myDependency.doTheThing()
    },
  }
}

const container = require("@mft/di").createContainer("application")
container.registerFactory("myService", myServiceFactory)
container.registerFactory("myDependency", myDependencyFactory)

const myService = container.resolve("myService")
myService.doAThing() // "done"

Terminology

  • component - a reusable piece of your application
  • factory - a function which creates a component
  • registration - the process of mapping an ID to a component factory
  • resolution - the process of mapping an ID to component instance
  • resolver - object which performs resolution
  • container - object which contains component registrations and instances, performs resolution
  • lifetime - how long a component instance is expected to live
  • scope - which components, and instances, are available to inject into a component instance

Concepts

The purpose of this library is to enable easy creation of your application's components, without having to worry about those components' dependencies.

Components

Components are the individual pieces of your application. They can be functions, objects, classes, strings... whatever you want.

An example of a component with two dependencies. Here, we have a factory function, which takes an object with named dependencies, and returns an instance of the component (in this case, an object).

Notice that there is no direct dependency on the DI library here. The only requirement is that factory functions take their dependencies as named arguments (an object). This makes the code portable, easy to test, and possible to wire together manually.

// my-component.js
const factory = ({dep1, dep2}) => {
  const instance = {
    get() {
      return dep1.getSomething() + dep2.getSomethingElse()
    },
  }
  return instance
}

Registration and resolution

When we have enough components in our application, and the dependency tree starts to get deeper (e.g. A depends on B which depends on C etc.), wiring these components together can become tedious.

What we can do instead is register all our component factories with a 'container', and let it be responsible for creating our components (and their dependencies) for us.

Here is how we would register the component factories for the above example, and manually resolve (create) an instance of the component, with its dependencies injected.

const di = require("@mft/di")

const container = di.createContainer("application")

// register all component factories
container.register("myComponent", require("./my-component"))
container.register("dep1", require("./dep1"))
container.register("dep1", require("./dep2"))

// resolve an instance of the component
const myInstance = container.resolve("myComponent")
myInstance.get()

Here we've called container.resolve() directly to manually resolve an instance of myComponent. But how do dep1 and dep2 get resolved? Let's have a look at myComponent again, but slightly re-write it to illustrate what's happening.

// my-component.js
const factory = (resolver) => {
  const dep1 = resolver.dep1 // resolved when this property is accessed
  const instance = {
    get() {
      const dep2 = resolver.dep2 // lazily resolve dep2
      return dep1.getSomething() + dep2.getSomethingElse()
    },
  }
  return instance
}

Here we can see that what actually gets injected is a 'resolver' object. Accessing properties on the resolver is what triggers resolution of dependencies.

Lifetimes

A lifetime is a statement about how long a container caches a reference to a component instance.

There are two lifetimes currently supported:

  • TRANSIENT
    • no reference cached
    • one instance per resolution
  • REGISTRATION
    • reference cached in container in which the factory was registered
    • one instance per registration container

TRANSIENT is the default lifetime.

To explicitly set a lifetime, use the registerFactory function like so:

container.registerFactory("id", factory, di.lifetimes.REGISTRATION)

Child containers

Sometimes you don't want a component to live for the entire life of your application, but also don't want to create a new instance every time it's resolved. For example, in an HTTP server, you might want to create some components which hold some data associated with a request.

For this, we can use child containers. Create one like so:

const di = require("@mft/di")
const express = require('express')
const app = express()

const container = di.createContainer("application")

app.get("/", (req, res) => {
  req.container = container.child("request")
  // register some components
  // resolve and use a component
})

Now, each request can register its own components and have its own instances.

It is expected that a child container will have a shorter lifetime that its parent. Here, for example, request container will live only as long as the request, but the application container will live for the lifetime of the application.

With that in mind, there are two simple rules which dictate how instances get resolved when using child containers:

  • instances get resolved in the container they were registered in
  • a container can only resolve dependencies from itself or a parent container

This means nothing can depend on something with a shorter lifetime. For example, no components in the application container could depend on a component in the request container. However, components in the request container can depend on instances from the application container.

API

const di = require("@mft/di")

DI

  • di.createContainer(scopeName: string) -> container
  • di.lifetimes.TRANSIENT
  • di.lifetimes.REGISTRATION

Container

Services and values can be registered with a container. It is responsible for resolving an ID to an instance.

  • container.registerFactory(id: string, factory: function [, lifetime]) -> container
  • container.registerValue(id: string, value: any) -> container
  • container.registerValues(values: {string: any}) -> container
  • container.resolve(id: string) -> componentInstance
  • container.child(scopeName: string) -> container

Factory functions

Factory functions are used to create the component instances.

  • factory(resolver) -> componentInstance

Resolver

The resolver gets injected into each factory function. It is a Proxy which resolves component instances on property lookup.

  • resolver[id] -> componentInstance

Further reading

Inversion of Control Containers and the Dependency Injection pattern - Martin Fowler

Dependency Injection in Node.js - 2016 edition