somersault

1.3.0 • Public • Published

somersault

Acrobatic yet-simple IoC Dependency Injection for Node.js Applications

Build Status Prod Dependencies Dev Dependencies Code Coverage npm version

Stats Downloads

Summary

somersault is a simple and flexible IoC solution for NodeJS (4.x+) projects that allows you to simplify your application and create beautiful code. As Node.js is untyped we have to rely on the declared names of your variables in order to determine what to inject.

The somersault library supports injection/building of:

  • Functions
  • ES6+ Arrow functions
  • ES6+ Classes

Depdencies are registered by a declared name (known as a 'Tag' in somersault), or by multiple such-tags. You can then either resolve a tag (i.e. build a tag and it's dependencies from scratch) or build a generator function - essentially fill in the blanks for parameters and constructors.

Installation

To install somersault into your node application:

npm install somersault --save

Usage

Container Creation

To create an IoC Container:

const somersault = require('somersault');
const myContainer = somersault.createContainer();

Registering Dependencies (.register(tags, value))

You can register a class, arrow function or function with:

myContainer.register('someAlias', myFuncOrClass);

You can also register the same object with multiple tags via:

myContainer.register(['someAlias', 'anotherAlias'], myFuncOrClass);

Resolving by Tag

To generate a complete object or graph of object from a tag, use .resolve(), such as:

const myInstance = myContainer.resolve('someAlias')

This will look up the most recently registered object with the tag 'someAlias'.

Building from a Template

To generate a complete value from a function, arrow function or class, pass it as the parameter to the .build() operation, such as:

const myResult = myContainer.build(MyClassName);

This will create a new instance of MyClassName, populating any constructor parameters that are known from other dependencies.

Worked Example

In this example we create a new somersault container, register a 'database' class and a constant-value of 'someConnectionString'. This allows us to inject the connection string into our database object at runtime.

const container = require('somersault').createContainer();

container.register('database', class Database {
  constructor(connectionString) {
    console.log('Built database class with %s', connectionString);
    this.connectionStringValue = connectionString;
  }
});
container.register('connectionString', 'someConnectionString');

const result = container.resolve('database');

The .resolve() operation looks up a defined 'tag' within the container, and builds an instance. In the example we are also registering the connectionString tag, which allows the parameter connectionString on our database class to be satisifeid.

somersault allows the inputs for registration to be:

  • Constant values
  • Generator values, such as:
    • Functions (parameters are resolved by tag/name)
    • Arrow Functions (parameters as per functions)
    • ES6+ classes (Constructor parameters are resolved by tag/name).

Container Methods and Properties

.createContainer()

Create a child container/context that uses the specified container as a fallback/parent.

const childContainer = myContainer.createChild();

.build(funcArrowOrClass)

Creates an instance of a class, or if a function/arrow-function, will execute the function and return it's value. The required parameters of the class constructor or function declaration are filled in using the container registrations.

const myObject = myContainer.build(SomeClass);

Overload .build(funcArrowOrClass, constructorTags)

Creates an instance of a class or executes a function/arrow, but remaps the detected names of the parameters to use alternate tags when resolving. This allows for consumption of types that do not have suitable IoC focused names that match pre-existing registrations:

  function myGenerator(someInstance, someFunc) {
    return someInstance.value + someFunc.value;
  }    
  const instance = container.build(myGenerator,
    ['alternateClass', 'alternateFunc']);

In this example the arguments someInstance and someFunc are resolved as if they'd been written with the overridden names.

.filterAll(tag|tags)

Creates a child container that will only include any registrations that match the nominated tag (or ALL of a list of nominated tags). This allows filtering down to a subset where registrations have multiple tags that combine to provide grouping:

// Register connection settings for environments
container.register(['connectionString', 'QA', 'serverA'], 'foo=bar1');
container.register(['connectionString', 'QA', 'serverB'], 'foo=bar2');
container.register(['connectionString', 'UAT', 'serverA'], 'foo=waa1');
container.register(['connectionString', 'UAT', 'serverB'], 'foo=waa2');

// Filter
const filteredChild = container.filterAll(['QA', 'serverB']);
// filteredChild will now only resolve connectionString = foo=bar2

This filter applies to the current container and all upstream registrations in the hierarchy, but does not modify results returned from references to the original container. Filter operations can be chained to create composed behaviours.

.filterAny(tag|tags)

Creates a child container that will only include any registrations that match the nominated tag (or any of a list of nominated tags). This allows filtering down to a subset where registrations have multiple tags that group them:

// Register connection settings for environments
container.register(['connectionString', 'QA'], 'foo=bar');
container.register(['connectionString', 'UAT'], 'foo=waa');

// Filter
const filteredChild = container.filterAny('QA');
// filteredChild will now only resolve registrations that include a QA tag.

This filter applies to the current container and all upstream registrations in the hierarchy, but does not modify results returned from references to the original container. Filter operations can be chained to create composed behaviours.

.filterOut(tag|tags)

Creates a child container that will exclude any dependencies that have the nominated tag or tags array assigned to them. This allows hiding of certain groups of dependencies in child containers.

// Register a tag, then overload it
container.register(['someTag'], 1);
container.register(['someTag', 'excludeMe'], -1);

// Initial result is -1
const firstResult = container.resolve('someTag');

// Resolve from filtered child
const filteredChild = container.filterOut('excludeMe');
// Will now be 1 (because excludeMe is hidden)

This filter hides all dependencies with the nominated tag/tags, no matter where they are in the parent container hierarchy. Filter operations can be chained to create composed behaviours.

.parent

Returns the parent container of a container context.

const myParent = myContainer.parent;

.register(tag|tags, value)

Register a value with the container. Value can be any of:

  • Arrow Function
  • Class
  • Function
  • Any other value (assumed to be a singleton/constant-value).

Classes are generated as new instances of the class on-demand, however arrows and functions are simply evaluated during resolution/build and returned. If you need to use prototypes or functions, register a 'generator' function to avoid problems around usage of this.

myContainer.register('someTag', MyClassHere);
myContainer.register(['can','have','many','tags'], function (foo) { ... });
myContainer.register('generator', (x, y, z) => new PrototypeNameHere(x, y, z));
myContainer.register('config', { someKey: 'value' });

Overload: .register(tag|tags, value, constructorTags)

It is possible to override the detection of parameter names on a function, arrow and class by passing a third parameter to register() - an array of alternate parameter/tag names.

myContainer.register(['can','have','many','tags'], function (foo, bar) { ... }, ['hello', 'world']);

In this example, any attempt to .resolve('tag') will now act as if function was function (hello, world) and resolve those parameter names instead.

.resolve(tag)

Produces the most recently registered tag value (and completes dependencies, if required). The tag value must match a tag used during registration.

const myInstance = myContainer.resolve('someTag');
myInstance.doSomething();

.resolveAll(tag)

Produces an instance of all instances of the specified tag registration in the container hierarchy all the way to the root.

const myWidgets = myContainer.resolveAll('widget');
for (const widget of myWidgets) {
  widget.frob();
}

Registrations are returned in most-recent registration order ascending through the hierarchy, so more recent registrations from parents appear after oldest child registrations.

Advanced Usage

Nested Dependencies

The somersault package will resolve dependencies, and then dependencies of those dependencies as required until all elements are populated and built. This allows complex object graphs to be satisfied easily.

Repeated Dependencies

Each specific dependency is only built once per resolve/build operation. It assumed that the same instance/value can be passed to each object in a graph requiring a specific dependency to be resolved.

Licencing

This repository is MIT licensed. That means you can use it freely without restriction or attribution. If this helped you get started, give the repo a star on GitHub and help spread the word.

Contribution & Development Guide

The library is built using the following tools and techniques:

These are all validated and working on NodeJS 4.x and above, we test for multiple variations of node which can be seen in our .travis.yml

Getting Started on Development

To get started for your contributions, please do the following:

  • Create a fork of our repository into your own workspace or organisation (this way you can track improvements and updates as we make them).
  • If you aren't using VSCode, then you can also drop the .vscode directory, which defines some IDE options to make sure the standards for tabs and spacing are used.

Then it's as simple as:

    npm install
    gulp

Please note all contributions require test coverage to be accepted.

Other Commands

The following gulp tasks are defined:

  • gulp docs - Regenerate esdoc documentation.
  • gulp lint - Run ESLint validation of code standards.
    • You can also lint-lib or lint-tests to look at specific areas.
  • gulp test - Run unit tests.

Package Sidebar

Install

npm i somersault

Weekly Downloads

11

Version

1.3.0

License

MIT

Last publish

Collaborators

  • steve-gray