hinoki

magical inversion of control for nodejs and the browser

hinoki

magical inversion of control for nodejs and the browser

hinoki is a powerful and flexible asynchronous dependency injection system designed to manage complexity in large applications

hinoki is inspired by prismatic's graph and angular's dependency injection.

hinoki takes its name from the hinoki cypress, a tree that only grows in japan and is the preferred wood for building palaces, temples and shrines...

in a nutshell

Use multiple Containers with different lifetimes that reference each Other

containers with lower lifetimes can depend on containers with higher lifetimes

A is active during a request but depends on some Things in b which lives through the entire process

containers can depend on other containers granular control over that allows functionality/features to emerge around it that enables a lot of emerging functionality to be build with it.

it is used in production and growing with the challenges encountered there

hinoki will always stay true to its core principles.

  • a functional data-driven rather than object oriented approach
  • small elegant codebase
  • simple, well-thought-out carefully-designed

if you would like to

if you use hinoki i am very happy to hear from you.

system of such pieces where some depend on others

software systems are composed of many pieces that depend on each other in various ways.

libraries, functions for accessing the database

hinoki solves the problem of composing all those parts

dependency injection is a

building blocks

simplifies getting data to where its needed

programming against an interface.

wire up closures on the fly

a lot more testable

hinoki allows you to declare the ways in which those pieces depend on each other and can then resolve the dependencies automatically.

mock

self contained units

separation of concerns

very testable

dont use hinoki dependencies for libraries

use them for application code!!!

By making it very easy to Get a hold of a Part of the system

Don't repeat yourself is encouraged

see the example app (entry point is main.js)

npm install hinoki
var hinoki = require('hinoki');

your markup should look something like the following

<html>
  <body>
    <!-- content... -->
 
    <!-- hinoki requires bluebird -->
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/bluebird/1.2.2/bluebird.js"></script> 
    <!-- take src/hinoki.js from this repository and include it -->
    <script type="text/javascript" src="hinoki.js"></script> 
    <!-- your script can now access hinoki through the global var `hinoki` -->
    <script type="text/javascript" src="example.js"></script> 
  </body>
</html>

hinoki.js makes the global variable hinoki available

its best to fetch bluebird with bower, hinoki with npm and then use a build system like gulp to bring everything together

in the world of hinoki a NAME uniquely identifies a part of a system. a NAME can have a VALUE: it could be a function, a piece of data, an object, a module, a library...

var values = {
  xs: [1, 2, 3, 6]
};

a NAME can depend on other NAMES, its dependencies.

a FACTORY for a NAME is a function that takes the VALUES of the NAME's dependencies and returns the VALUE of the NAME:

var factories = {
  countfunction(xs) {
    return xs.length;
  },
  meanfunction(xscount) {
    var reducer = function(accx) {
      return acc + x;
    };
    return xs.reduce(reducer, 0) / count;
  },
  meanOfSquaresfunction(xscount) {
    var reducer = function(accx) {
      return acc + x * x;
    };
    return xs.reduce(reducer, 0) / count;
  },
  variancefunction(meanmeanOfSquares) {
    return meanOfSquares - mean * mean;
  }
};

a CONTAINER manages the FACTORIES and VALUES for a set of NAMES:

var container = {
  factories: factories,
  values: values
};

CONTAINERS are just a plain old javascript objects

a CONTAINER can be asked for the VALUE of a NAME:

hinoki.get(container, 'mean').then(function(mean) {
  console.log(mean);  // -> 3 
});

hinoki always returns a promise: to normalize synchronous and asynchronous dependencies and to simplify error handling.

asking for an uncached NAME will ask for its dependencies (and their dependencies...), call its FACTORY to get the VALUE and cache the new VALUES in the CONTAINER:

console.log(container.values);
// -> 
// { xs: [ 1, 2, 3, 6 ], 
//   count: 4, 
//   mean: 3 } 

asking for a cached NAME again will return the cached VALUE.

hinoki.get(container, 'count').then(function(count) {
  console.log(mean);  // -> 3 
});
hinoki.get(container, 'variance').then(function(variance) {
  console.log(variance);  // -> 3.5 
 
  console.log(container.values);
  // -> 
  // { xs: [ 1, 2, 3, 6 ], 
  //   count: 4, 
  //   mean: 3, 
  //   meanOfSquares: 12.5, 
  //   variance: 3.5 } 
});
 

see the whole example again

hinoki itself uses no global or module-level mutable state.

its side effects are localized in containers: hinoki adds values to containers. containers are just data (plain old javascript objects). inspect and manipulate them easily using standard javascript.

scope

lifetime

think about containers as tuples of VALUES and FACTORIES that belong together in a specific combination.

feel free to mix and match.

feel free to tear them apart.

it's often useful for multiple CONTAINERS to use the same FACTORIES but different VALUES

var otherValues = {
  xs: [2, 3, 4, 5]
};
 
var otherContainer = {
  factories: factories,
  values: otherValues
};
 
hinoki.get(otherContainer, 'mean').then(function(mean) {
  console.log(mean);  // -> 3.5 
  console.log(otherContainer.values);
  // -> 
  // { xs: [ 2, 3, 4, 5 ], 
  //   count: 4, 
  //   mean: 3.5 } 
  });

a CONTAINER owns VALUES and controls their scope and lifetime.

just use a new CONTAINER whenever you need a fresh scope.

if a factory returns a thenable (for example a bluebird , q or when promise) hinoki will resolve it automatically.

if the promise returned by a factory is rejected then the promise returned by hinoki is rejected with a hinoki.PromiseRejectedError.

with asynchronous dependencies hinoki makes it easy to structure asynchronous computation.

see example

var factory = function(variancemean) {
  /* ... */
};
 
var dependencyNames = hinoki.getFunctionArguments(factory);
// -> ['variance', 'mean'] 
 
hinoki.get(container, dependencyNames).spread(factory);

asks container for variance and mean and calls factory with them.

if a factory function has the $inject property containing an array of dependency names then hinoki will ask for values of those names and inject them into the factory.

otherwise hinoki will parse the dependency names from the factory function arguments and cache them in the $inject property of the factory function:

var factories = {
  afunction() { return 'a'; },
  bfunction() { return 'b'; },
  cfunction() { return 'c'; },
  dfunction() { return 'd'; },
  // this should depend on ['a', 'c'], we override this below 
  acfunction(ab) { return a + b; },
  acdfunction(acd) { return ac + d; }
};
 
factories.ac.$inject = ['a', 'c'];
 
var container = {
  factories: factories
};
 
hinoki.get(container, 'acd', console.log).then(function(acd) {
  console.log(acd);  // -> 'acd' 
  // dependency names have been cached 
  console.log(factories.a.$inject); // -> [] 
  console.log(factories.acd.$inject); // -> ['ac', 'd'] 
});

source

a VALUE can be null

if a factory returns the promise is rejected with a

if a factory returns null then the value null is cached and returned.

hinoki supports multiple containers.

containers are asked in order from first to last.

values are added to the container that resolved the factory.

factories can depend on dependencies in succeeding containers.

see example ...you get the idea ;-)

this opens new possibilities for web development - demo application coming soon!

pass in a callback as the third argument to hinoki.get and it will be called on various steps during the dependency injection process:

hinoki.get(container, 'variance', console.log)
  .then(function(variance) {
    /* ... */
  });

the callback will be called with an event object which has the following properties:

  • event = one of valueFound, factoryFound, valueUnderConstruction, valueCreated, promiseCreated, promiseResolved
  • name = NAME of the dependency that caused the event
  • path = full dependency path (call path.toString() -> 'a <- b <- c' or path.segments() -> ['a', 'b', 'c'])
  • container = the CONTAINER on which the event occured
  • value = the value (just for valueFound, valueCreated and promiseResolved)
  • factory = the FACTORY (just for factoryFound)
  • promise = the promise returned by the FACTORY (just for promiseCreated)

filter on event type

hinoki.get(container, 'variance')
  .catch(hinoki.CircularDependencyError, function(error) {
    /* just on circular dependencies... */
  }
  .catch(function(error) {
    /* on any error... */
  })
  .then(function(variance) {
    /* on success... */
  })

click here for all error types and how to catch them

~~ RESOLVERS ARE LIKELY TO CHANGE IN THE FUTURE ~~

resolvers add a level of indirection that allows you to intercept the lookup of factories and values in containers.

... in a structured fashion

container = {
  factoryResolvers: [
    function(containernameinner) {
      var factory = inner();
      if (factory) {
        return factory;
      }
      if name is '
    }
  ]
};

resolvers must be pure (deterministic) functions: given the same inputs they must return the same outputs. they must not depend on uncontrollably changing factors like randomness or time or external services

there are but the same is true for value resolvers - just replace FACTORY with VALUE in your mind.

a factory resolver is just a function that takes a container and a name and returns a factory or null.

by default hinoki uses the hinoki.defaultFactoryResolver that simply looks up the name in the containers factories property.

by adding `

those take an additional third argument

has a single factoryResolver that resolves factories in the factories object. you can manipulate the resolvers:

container.factoryResolvers.push(myFactoryResolver);

resolvers can be used to

resolvers can be used to generate factories and values on the fly. they can return factories without them being in container.factories. a resolver could respond to getUserWhereId with a function

interesting alternative to rubys method missing

takes one or many CONTAINERS and one or many NAMES.

returns a bluebird promise that is resolved with an value (for one name) or an array of values (for many names). the promise is rejected in case of errors. side effect the container

hinoki.get(container, 'variance')
  .then(function(variance) {
    /* ... */
  });
hinoki.get(container, ['variance', 'mean'])
  .spread(function(variancemean) {
    /* ... */
  });
hinoki.get([container1, container2], ['variance', 'mean'])
  .spread(function(variancemean) {
    /* ... */
  });

you can pass a function as a third argument which is called on various events (to see exactly what is going on under the hood which is useful for debugging).

hinoki.get(container, 'variance', console.log)
  .then(function(variance) { /* ... */ })
  .catch(function(error) { /* ... */ });

when there is a cycle in the dependency graph described by the factory dependencies

hinoki.get(container, 'variance')
  .catch(hinoki.CircularDependencyError, function(error) { /* ... */ });

when no resolver returns a factory for a name

hinoki.get(container, 'variance')
  .catch(hinoki.UnresolvableFactoryError, function(error) { /* ... */ });

when a factory throws an error

hinoki.get(container, 'variance')
  .catch(hinoki.ExceptionInFactoryError, function(error) { /* ... */ });

when a factory returns a promise and that promise is rejected

hinoki.get(container, 'variance')
  .catch(hinoki.PromiseRejectedError, function(error) { /* ... */ });

when a resolver returns a value that is not a function

hinoki.get(container, 'variance')
  .catch(hinoki.FactoryNotFunctionError, function(error) { /* ... */ });

when a factory returns undefined

hinoki.get(container, 'variance')
  .catch(hinoki.FactoryReturnedUndefinedError, function(error) { /* ... */ });

license: MIT