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

inversio

v2.0.1

Published

A Promise-based dependency injection system.

Downloads

18

Readme

inversio

A Promise-based dependency injection system.

npm travis license js-standard-style

Motivation

  • Clean and intuitive way of organizing large applications into truly separate modules
  • A cure for require-hell

The basics

Make sure you have a version of node with a native (or shim) Promise implementation. Otherwise, just install bluebird.

Create a container

var container = require('inversio')()

A container is repository of components. It keeps track of their names, dependencies and how and when they are instantiated.

Register a component

container.component({
  name: 'myService',
  factory: function myService_factory() { return ... }
})

name must be unique (unless timid or tags are specified) and can be used by dependant components to reference the component. Anonymous components are allowed if tags are specified (as in {tags: ['a'], factory: () => {...}}).

factory is a function that returns a service instance or a promised service instance.

Important: the factory is invoked only once during the container lifetime, effectively making components singletons.

Register dependent components

container
  .component({
    name: 'A',
    depends: ['myService']
    factory: function A_factory(myService) { return ... }
  })
  .component({
    name: 'B',
    depends: ['myService', 'A']
    factory: function B_factory(myService, a) { return ... }
  })

Note that dependecies to other components are explitly listed in depends and that the factories have a corresponding argument list.

Resolving components

resolve(name) is used for resolving a single component. inject(...names, optional factory) is used for resolving a multiple components at once.

resolve() and inject() both returns promises, but evaluation of dependencies and invokation of factories will be with settled values - using natural, synchronous composition.

container
  .resolve('myService')
  .then(myService => ...) <!-- resolve single component

container
  .inject('a', 'b', 'c')
  .then([a ,b ,c] => ...) <!-- resolve multiple components

container
  .inject(['a', 'b', 'c'])
  .then([a ,b ,c] => ...) <!-- resolve multiple components

container
  .inject('a', 'b', 'c', f)
  .then(value => ...) <!-- value will be f(a, b, c)

container
  .inject(['a', 'b', 'c'], f)
  .then(value => ...) <!-- value will be f(a, b, c)

Timid components

A timid component must be named and may be overwritten by another component with the same name, unless already resolved.

container
  .component({
    name: 'A',
    timid: true,
    factory: () => 'timid one'
  })
  .component({
    name: 'A',
    factory: () => 'the bold winner...'
  })

Special dependencies

? (optional)

Dependencies on the form ?fooare resolved normally with name (foo) if registered, otherwise undefined.

container
  .resolve('?missing') // => undefined

container
  .component({name: 'a', factory: () => 'A'})
  .resolve('?a') // => 'A'

require

Dependencies on the form 'require:foo' are resolved with require('foo') rather than looking up a component.

container
  .resolve('require:fs') // => require('fs')

tag

Dependencies on the form 'tag:bar' are resolved with all components tagged with 'bar'. The resolved result is an array of resolved dependencies.

Tagged components can be anonymous, i.e. name must not be specified.

Tagging is straightforward:

container
  .component({
    name: 'x',
    tags: ['bar', 'baz']
    factory: ...
  })

Tagging is powerful, since it allows dependencies on the form is a rather than is.

Order of tagged components can be specified with order:

container
  .component({name: 'first', order: -100, ... })
  .component({name: 'between (no order specified, defaults to 0)', ... })
  .component({name: 'last', order: 100, ... })

Decorators, mixins and subclassing

The decorator pattern and the particular case of subclassing/mixins are expressed using (replace <name> below with something in your liking)

  • a named root (such as a base class) named 'super:<name>'
  • decorators (or mixins) named 'extends:<name>'
  • a concrete binding named 'class:<name>' which combines super: and extends: in a natural way.

Decorators are expected to map from one value to another.

Consider the following composition:

console.log(foo(bar('X')))

This can be expressed and evaluated with

container
  .component({name: 'super:x', factory: () => 'X'})
  .component({tags: ['extends:x'], factory: bar})
  .component({tags: ['extends:x'], factory: foo})
  .resolve('class:x')
  .then(console.log) // --> console.log(foo(bar('X')))

The motivation for super:, extends: and class: comes from real world applications where class composition like class A extends (class B extends ... extends SomeBaseClass) is achieved using the mechanisms above.

Organizing express applications

For a working example, check out the sample-express-site

  • concerns are separated into separate modules (database, index page, blog pages, express and middleware etc)
  • each module is self contained, including views and controllers
  • module dependencies are injected, so there are no require() between modules

The challenge (at least for me) with larger express/koa applications is that dependencies are hard to maintain while still handling tests, structure, refactoring, technical debt and such. My personal preference is to separate the application into modules, each with a specific and isolated concern. These modules are then bootstrapped in the main script of the application.

An important principle when working with dependency inversion, is that code that deals with concrete dependency decisions (i.e. which module or class to instantiate) should be kept at a minimum. In the example below, main.js knows about the actual application structure, while the modules are blissfully ignorant.

The main script, where modules are discovered, registered and composed to an aggregate that is your final application.

glob('app/**/*.component.js')
  .then(function requireComponents (componentFiles) {
    return componentFiles.map(function (componentFile) {
      log('loading component %s', componentFile)
      return {
        file: componentFile,
        module: require('./' + componentFile)
      }
    })
  })
  .then(function registerComponents (components) {
    return components.reduce(function (container, component) {
      log('registering component %s', component.file)
      component.module.register(container)
      return container
    },
    inversio())
  })
  .then(function (container) {
    log('resolving express app and all routes')
    return container.inject('app', 'tag:route', function (app) {
      return app
    })
  })
  .then(function (app) {
    log('express application listing to port %s', port)
    app.listen(port)
  })

inject(['app', 'tag:route'], ...) does some magic. First, it will get the registered app service, needed for listen(). But it ill also trigger loading of all modules tagged with route, ensuring that the admin, users and blogs routes will be registered with express.

The tag syntax for resolving dependencies is vey powerful for ensuring instantiation of service by category without having to know them explicitly.

Globbing for modules is quite cool (in my opinion). It basically allows you just drop in a new module. If tagged as routes, they will be part of your express application.

The express module (./app/express/component.js) could be something like

module.exports.register = function (container) {
  return container.component({
    name: 'app',
    factory: createApp
  })
}

function createApp() {
  var app = express()
  // Setup require middleware
  // app.use(...)
  return app
}

The users/admin/blogs module could be something like

module.exports.register = function (container) {
  return container.component({
    name: 'users',
    depends: ['app', 'db']
    tags: ['route']
    factory: setupRoutes
  })
}

function setupRoutes(app, db) {
  // setup routes
  // app.get(...)
  // app.post(...)
}

The database logic could be isolated as

module.exports.register = function (container) {
  return container.component({
    name: 'db',
    factory: configureDatabase
  })
}

function configureDatabase() {
  return require('levelup')('./data')
}

Having scattered component.js files, each exporting a function register for its module registrations, is only a convention used in this example. Other ways of discovering and bootstrapping modules are certainly possible.