hinoki

effective yet simple dependency injection and more for Node.js and browsers

hinoki

this is a release candidate for version 1.0.0. implementation and tests are complete. the documentation in this readme is not yet finished. it's already useful though. the newest version introduces massive breaking changes. if you used an earlier version of hinoki please read this new readme carefully. future changes will be documented in the changelog. future versions will use semver.

effective yet simple dependency injection and more for Node.js and browsers

Hinoki seems to be the least surprising IoC container available for Node.
I definitely do like its ascetic worldview.
Thanks a lot for this!
andrey

let's see an example:

var lifetime = {
  one: 1,
  two: 2,
  three: 3,
};
 
var source = function(key) {
  if (key === 'five') {
    return function(onefour) {
      return one + four;
    };
  }
  if (key === 'four') {
    return function(onethree) {
      return Promise.resolve(one + three);
    };
  }
};
 
hinoki(source, lifetime, 'five').then(function(value) {
  assert(value === 5);
  assert(lifetime.five === 5);
  assert(lifetime.four === 4);
});

there's a lot going on here. let's break it down. you'll understand most of hinoki afterwards:

we want the value for the key 'five'.
hinoki immediately returns a promise. it will resolve that promise with the value later.
there is no key 'five' in the lifetime so hinoki calls our source function with the argument 'five'.
source returns the factory function for 'five'.
the factory for 'five' has parameter names 'one' and 'four'.
hinoki calls itself to get the values for 'one' and 'four' such that it can call the factory with those values.
'one' is easy. it's already in the lifetime.
but there is no key 'four' in the lifetime therefore hinoki calls our source function again with the argument 'four'.
source returns the factory function for 'four'.
the factory for 'four' has parameters 'one' and 'three'.
hinoki calls itself to get the values for 'one' and 'three'.
fortunately values for both 'one' and 'three' are already in the lifetime.
hinoki can now call the factory for 'four' with arguments 1 and 3.
the factory for 'four' returns a promise.

when a factory returns a promise hinoki must naturally wait for the promise to be resolved before calling any other factories that depend on that value !

at some point the promise for 'four' resolves to 4.
hinoki can now continue making everything that depends on the value for 'four':

first hinoki sets lifetime.four = 4.

values returned from factories are stored/cached in the lifetime !
what if we have multiple lifetimes? the answer is very useful and worth its own section.

now hinoki can call the factory for 'five' with arguments 1 and 4.
ince he factory for 'five' doesn't return a promise hinoki doesn't have to wait.
hinoki sets lifetime.five = 5.
remember that promise hinoki has returned immediately ? now that we have the value for key 'five' hinoki resolves it with 5.

that's it for the breakdown.
you should now have a pretty good idea what hinoki does.
keep in mind that this scales to any number of keys, values, factories, lifetimes and dependencies !

having to add an if-clause in the source for every factory is not very convenient.
fortunately there's much more to sources. read on !

the first argument passed to hinoki is always interpreted as the source and passed to hinoki.source internally.

hinoki.source takes either an object, a string, an array or a function.
hinoki.source always returns a source function.

when hinoki.source is called with a function argument its simply returned.

if an object mapping keys to the factories for those keys is passed to hinoki.source it is wrapped in a source function that simply looks up the key in the object:

var lifetime = {
  one: 1,
  two: 2,
  three: 3,
};
 
var factories = {
  fivefunction(onthree) {
    return one + four;
  },
  fourfunction(onethree) {
    return Promise.resolve(one + three);
  },
};
 
hinoki(factories, lifetime, 'five').then(function(value) {
  assert(value === 5);
  assert(lifetime.five === 5);
  assert(lifetime.four === 4);
});

much more readable and maintainable than all those if-clauses we had before.

if a string is passed to hinoki.source it is interpreted as a filepath.

if the filepath points to a .js or .coffee file hinoki.source will require it and return a source function that looks up keys in the module.exports returned by the require.

if the filepath points to a folder hinoki.source will require all .js and .coffee files in that folder recursively. other files are ignored. all module.exports returned by the requires are merged into one object. a source function is returned that looks up keys in that object.

it is very easy to load factories from files that simply export factories this way !

sources can also be strings. in that case hinoki interprets them as filenames to require

this means that you can just drop your factories as exports pull them all in and wire them up.

you could have all your factories as exports in a number of

your factories could then depend on other factories exported by any other file in that directory.

sources compose:
if an array is passed to hinoki.source it is interpreted as an array of potential sources.

this section needs work

this section needs work

lifetimes store values.

why multiple lifetimes ?

because values

there's only one application

but there are many requests

there are many events

many lifetimes

but you still want

let's see an example:

request, response

fragments is with this idea at its core.