kuul-ioc v1.0.1
Kuul-ioc Node.js and browser module for asynchronous promise based inversion of control
Motivation
As Node.js developer I really missed some good asynchronous inversion of control module for Node.js, there are some but I was not fully satisfied with them. Also using native Node.js require is not always best idea, definitely not for bigger project. Good IoC is foundation stone for every Javascript application, so here you have one :)
Features
- Resolve your dependencies asynchronously with promises
- Module logic can be wrapped into promise executor, generator function or async function
- Resolve singleton instance of your module
- You can extend ( add or replace ) dependencies when you resolving module
- Simple mocking modules for tests and replacement
- It's perfect to use if you like promises, ES6 generators or ES7 async / await features
- You don't have to worry about
module.export
orexport
keyword anymore,kuul-ioc
will handle that for you - Small and very powerful library
Installation
$ npm install kuul-ioc
Example folder
You can see example usage for sample koa server
Simple usage example
Module definition
file
core/simple.js
var ioc = require('kuul-ioc')
ioc.createModule(module)
.dependencyValue('sentence', 'Anything can be there')
.dependencyValue('object', {'greeting' : 'Hello world !'})
.module(function (dep, resolve, reject) {
console.log(dep.sentence)
/* 'Anything can be there' */
console.log(dep.object.greeting)
/* 'Hello world !' */
/* some async work */
setTimeout(function () {
resolve(dep.object.greeting)
}, 1000)
})
Module resolve
file
index.js
var ioc = require('kuul-ioc')
var appContainer = ioc.get('app').setBasePath(__dirname)
appContainer.module('core/simple').get()
.then(function(simple) {
console.log(simple)
/* 'Hello world !' */
})
Advanced usage example
Module definition
file
core/advanced.js
var ioc = require('kuul-ioc')
var someContainer = ioc.createContainer().setBasePath(__dirname)
ioc.createModule(module)
.dependency('simple', 'core/simple')
.dependency('simpleRelative', './simple')
.dependency('moduleResolver', someContainer.module('config') )
.dependency('moduleOnTheFly',
ioc.createModule()
.dependencyValue('sentence', 'Anything can be there')
.module(function(dep, resolve, reject) {
resolve(dep.sentence)
})
)
.dependencyValue('sentence', 'Anything can be there')
.dependencyFunction('fcePromiseExecutor', function(resolve, reject) {
resolve('This is like factory')
})
.dependencyFunctionOnce('fceGeneratorOnce', function* () {
yield 'This is like singleton'
})
.module(function (dep, resolve, reject) {
console.log(dep.sentence)
/* 'Anything can be there' */
console.log(dep.object.greeting)
/* 'Hello world !' */
/* some async work */
setTimeout(function () {
resolve(dep.object.greeting)
}, 1000)
})
Module resolve
file
index.js
var ioc = require('kuul-ioc')
var appContainer = ioc.get('app').setBasePath(__dirname)
appContainer.module('core/advanced').get()
.then(function(simple) {
console.log(simple)
/* 'Hello world !' */
})
Table of contents
Module definition - file
Module definition - file with Promise executor
file
hello-world.js
var ioc = require('ioc')
ioc
.createModule(module)
.module(function (dep, resolve, reject) {
setTimeout(function() {
resolve('Hello world')
}, 1000)
})
Module definition - file with ES6 generator function
file
hello-world.js
var ioc = require('ioc')
ioc
.createModule(module)
.module(function* (dep) {
yield new Promise(function (resolve, reject) {
setTimeout(resolve, 1000)
})
return 'Hello world'
})
Module definition - file with ES7 async function
file
hello-world.js
var ioc = require('ioc')
ioc
.createModule(module)
.module(async function (dep) {
await new Promise(function (resolve, reject) {
setTimeout(resolve, 1000)
})
return 'Hello world'
})
Module definition - on the fly
var ioc = require('ioc')
var container = ioc.createContainer().setBasePath('/')
/*
Now you dont' pass module variable into createModule function
ioc.createModule(module)
but you just left it empty
ioc.createModule()
*/
var onTheFlyModule = ioc
.createModule()
.module(function* (dep) {
yield new Promise(function (resolve, reject) {
setTimeout(resolve, 1000)
})
return 'Hello world'
})
container.module('not/real/path').set(onTheFlyModule)
Module resolve
ioc.get('containerName').module('someModule').get()
get()
if module is singleton it will be resolved only first time, next time you call get() on singleton module it will return cached result, it will be exact same result as first time, see Module resolve - singleton
ioc.get('containerName').module('someModule').resolve()
resolve()
Does not care if module is singleton, it will always return a new instance. Basically it always run your module function and fetch the result, see Module resolve - factory
This will be our file structure
file
core/config.js
ioc
.createModule(module)
.module(async function (dep) {
return 'config'
})
file
core/factory.js
ioc
.createModule(module)
.setSingleton(false)
.module(async function (dep) {
return 'factory'
})
Module resolve - Singeton
Module is singeton by default, you don't have to use setSingleton(true),
file
core/config.js
var ioc = require('kuul-ioc')
ioc.get('nameForContainer').setBasePath(__dirname)
var moduleResolver = ioc.get('nameForContainer').module('core/config')
moduleResolver.get().then(function(configA) {
moduleResolver.get().then(function(configB) {
moduleResolver.resolve().then(function(configC) {
console.log( configA === configB )
/* returns true */
console.log( configA === configC )
/* returns false
resolve() will ignore module singeton boolean, it will always run module executor function and fetch result
*/
})
})
})
Module resolve - Factory
In this example
co
module is used to transform generator function to async control flow, you can read more about it here https://www.npmjs.com/package/co
kuul-ioc
useco
module internally to resolve your generator function, so you can use all it's features
co
module internally use alsokoa
var ioc = require('kuul-ioc')
var co = require('co')
ioc.get('nameForContainer').setBasePath(__dirname)
var moduleResolver = ioc.get('nameForContainer').module('core/factory')
co(function* () {
var factoryA = yield moduleResolver.get()
var factoryB = yield moduleResolver.get()
var factoryC = yield moduleResolver.get()
/* this would be same as above, because core/factory.js have
setSingleton(false)
var factoryA = yield moduleResolver.resolve()
var factoryB = yield moduleResolver.resolve()
var factoryC = yield moduleResolver.resolve()
*/
console.log( configA === configB )
/* returns false */
console.log( configA === configC )
/* returns false */
})
Module resolve - ES7 async/await
To run this example, you have to use some compiler, ex. babel
file
.babelrc
{
"presets": [ "es2015-node5", "stage-3" ]
}
var ioc = require('kuul-ioc')
var co = require('co')
ioc.get('nameForContainer').setBasePath(__dirname)
var moduleResolver = ioc.get('nameForContainer').module('core/factory')
(async function() {
var factory = await moduleResolver.get()
})()
Api
Class: Ioc
ioc.get(name
)
name
String
Name of container
return
Container
instance
Create new or return existing instance of
Container
ioc.createContainer()
return
Container
instance
Create new instance of
Container
ioc.createModule(module
)
module
Node.jsmodule
keyword, used tomodule.exports
orexports
Objects from native Node.js module system
return
Module
instance
Create new instance of
Module
Parameter module
is optional because you can create Module
instance on fly so kuulioc
will not internally call require
function to get Module
instance. So basically you don't provide any parameter when creating Module
instance on fly.
Class: Container
container.setBasePath(basePath
)
basePath
String
Base path of container
return
Container
instance
Set base path of container
container.module(modulePath
)
modulePath
String
Path to requested module
return
ModuleResolver
instance
Create
ModuleResolver
instance for module specified bymodulePath
container.getReplacements()
return
replacements Object
Get
Container
replacements Object
container.setReplacements(mixed
)
mixed
Container
|Object
return
ModuleResolver
instance
Set replacements object. Replacements object is used with all
ModuleResolved
.set*
functions, also used when resolving replaced / mocked modules
Class: Module
module.setSingleton(boolean
)
boolean
Boolean
return
Module
instance
It specify if module should be resolved as singleton or new instance every time you resolve it
module.dependency(name
, mixed
)
name
String
Name of dependency when it will be resolved and put in module functionmixed
String
|ModuleResolver
|Module
return
Module
instance
Add dependency to your module
module.dependencyValue(name
, mixed
)
name
String
Name of dependency when it will be resolved and put in module functionmixed
Mixed
Anything
return
Module
instance
Add dependency to your module
module.dependencyFunction(name
, mixed
)
name
String
Name of dependency when it will be resolved and put in module functionmixed
Function
|GeneratorFunction
|AsyncFunction
return
Module
instance
Add dependency to your module
module.dependencyFunctionOnce(name
, mixed
)
name
String
Name of dependency when it will be resolved and put in module functionmixed
Function
|GeneratorFunction
|AsyncFunction
return
Module
instance
Add dependency to your module
module.module(mixed
)
mixed
Function
|GeneratorFunction
|AsyncFunction
return
Module
instance
Function that handle logic of module or in other words function that return content of module
Class: ModuleResolver
moduleResolver.get()
return
Promise
instance
Asynchronously resolve module, if module is singleton it will be resolved only first time, next time you call get() on singleton module it will return the same result as first time
moduleResolver.resolve()
return
Promise
instance
Asynchronously resolve module, it does not care if module is singleton, it will always return a new instance. Basically it always run your module function and fetch the result
moduleResolver.extend(name
, mixed
)
name
String
Name of dependency that will be added or replacedmixed
String
|ModuleResolver
|Module
return
ModuleResolver
instance
Add or replace dependency to your module
moduleResolver.extendValue(name
, mixed
)
name
String
Name of dependency that will be added or replacedmixed
Mixed
Anything
return
ModuleResolver
instance
Add or replace dependency to your module
moduleResolver.extendFunction(name
, mixed
)
name
String
Name of dependency that will be added or replacedmixed
Function
|GeneratorFunction
|AsyncFunction
return
ModuleResolver
instance
Add or replace dependency to your module
moduleResolver.extendFunctionOnce(name
, mixed
)
name
String
Name of dependency that will be added or replacedmixed
Function
|GeneratorFunction
|AsyncFunction
return
ModuleResolver
instance
Add or replace dependency to your module
moduleResolver.set(mixed
)
mixed
String
|ModuleResolver
|Module
return
ModuleResolver
instance
It replace your module
moduleResolver.setValue(mixed
)
mixed
Mixed
Anything
return
ModuleResolver
instance
It replace your module
moduleResolver.setFunction(mixed
)
mixed
Function
|GeneratorFunction
|AsyncFunction
return
ModuleResolver
instance
It replace your module
moduleResolver.setFunctionOnce(mixed
)
mixed
Function
|GeneratorFunction
|AsyncFunction
return
ModuleResolver
instance
It replace your module