A dependency injection container with async support.
npm install @kapitchi/bb-dic
For ES5/ES6 compatible implementation use require('@kapitchi/bb-dic/es5')
.
DIC uses acorn to parse the code to discover function parameters.
If you encounter parsing issues try to fiddle with ecmaVersion
parameter (default 8).
See examples
folder for full usage examples.
Framework usage examples can be found at the bottom.
class MyService {
constructor(myServiceOpts) {
this.options = myServiceOpts;
}
showOff() {
console.log('My options are:', this.options);
}
}
const {Dic} = require('@kapitchi/bb-dic');
const dic = new Dic();
// register all instances
dic.instance('myServiceOpts', { some: 'thing' });
dic.class('myService', MyService);
dic.factory('myApp', function(myService) {
return function() {
// some application code
myService.showOff();
}
});
// use it
const app = dic.get('myApp');
app();
Use when one of your class instances or instance factories needs async initialization.
const {Dic} = require('@kapitchi/bb-dic');
const dic = new Dic();
class AsyncService {
async asyncInit() {
// some async await calls
}
showOff() {
console.log('Perfect, all works!');
}
}
dic.class('asyncService', AsyncService);
dic.asyncFactory('asyncMsg', async function() {
// some async calls needed to create an instance of this service
return 'Async helps the server.';
})
dic.factory('myApp', function(asyncService, asyncMsg) {
return function() {
// some application code with all services ready
myService.showOff();
console.log(asyncMsg);
}
});
// Creates myApp service and instantiate all its direct dependencies
dic.getAsync('myApp').then(app => {
app();
});
- DicConfigLoader
Config loader - sets up Dic from the config (plain object)
- DicFactory
A factory
- DicLoader
Dic loader
- Dic
Dependency injection container
For more usage examples see: instance, class, factory, asyncFactory, bind.
- defOpts :
Object
Config loader - sets up Dic from the config (plain object)
Kind: global class
Param | Type | Description |
---|---|---|
opts | Object |
|
opts.optionsSuffix | string |
What suffix to use for "options" config. See: loadConfig |
Set up Dic according the config
Kind: instance method of DicConfigLoader
Param | Type | Description |
---|---|---|
dic | Dic |
|
config | Object |
|
[config.options] | Object |
Create plain object "option" instances |
[config.aliases] | Object |
Create aliases |
[config.bindings] | Object |
Set up bind Dic |
Example
{
options: {
service1: { } // {} is registered as "service1Opts" instance
},
aliases: {
service2: 'service1' // "service1" is aliased to "service2"
},
bindings: {
package1: { // bind container name
imports: {
serviceA: 'service1' // "service1" from main container is imported into "package1" container as "serviceA"
},
//options for bind container, same as for main container i.e. `options`, `aliases`, ...
}
}
}
A factory
Creates DIC instance, uses loader and config loader
Kind: static method of DicFactory
Param | Type | Default | Description |
---|---|---|---|
params | Object |
||
[params.debug] | bool |
false |
|
[params.loaderRootDir] | string |
DicLoader#constructor If specified, params.loaderPath must be specified too. |
|
[params.loaderPath] | string | Array.<string> |
loadPath | |
[params.config] | Object |
loadConfig |
Dic loader
Kind: global class
Param | Type | Description |
---|---|---|
opts | Object |
|
opts.rootDir | string |
Absolute path to root folder of source files. Default: process.cwd() |
Example
// Registers all classes/factories/instances under `__dirname/src` folder.
const {Dic, DicLoader} = require('@kapitchi/bb-dic');
const dic = new Dic();
const loader = new DicLoader({
rootDir: __dirname + '/src' //if not specified process.cwd() is used by default
});
loader.loadPath(dic, '*.js');
module.exports = dic;
Load all instances/factories/classes to Dic.
File types and what they should export
- name.js -> class
- name.factory.js -> factory
- name.async-factory.js -> async factory
- name.instance.js -> instance
File name dictates what name the service will be registered as.
E.g. my-service.js
service would become registered as myService
=> file name is camelCased.
opts.removeDuplicate
option
If false, user/user-service.js
would normally be aliased as userUserService
.
If true, this would be work like examples below:
user/user-service.js
->userService
user-service/user-service.js
->userService
user-service/user-repository.js
->userServiceUserRepository
users/user-service.js
->usersUserService
Kind: instance method of DicLoader
Param | Type | Default | Description |
---|---|---|---|
dic | Dic |
||
path | string | Array.<string> |
glob expression https://www.npmjs.com/package/globby | |
[opts] | Object |
||
[opts.prefix] | string |
"''" |
Instance name prefix |
[opts.postfix] | string |
"''" |
Instance name postfix |
[opts.removeDuplicate] | string |
false |
If true, remove duplicated folder/file names as described above. |
[opts.rootDir] | string |
Overwrites loader's rootDir option |
Dependency injection container
For more usage examples see: instance, class, factory, asyncFactory, bind.
Kind: global class
- Dic
- new Dic([options])
- .asyncFactory(name, factory, [opts])
- .factory(name, factory, [opts])
- .instance(name, instance)
- .class(name, classDef, [opts])
- .asyncInit()
- .has(name) ⇒
boolean
- .get(name) ⇒
*
- .getAsync(name) ⇒
*
- .alias(name, alias)
- .bind(dic, opts)
- .createInstance(def, opts) ⇒
*
- .createInstanceAsync(def, opts) ⇒
*
Param | Type | Default | Description |
---|---|---|---|
[options] | Object |
||
[options.containerSeparator] | String |
_ |
Container / service name separator. See bind |
[options.debug] | boolean |
false |
Debug on/off. |
[options.ecmaVersion] | number |
8 |
ECMAScript version. |
Example
// Dependency injection example
class MyService {
constructor(myServiceOpts) {
this.options = myServiceOpts;
}
}
const {Dic} = require('@kapitchi/bb-dic');
const dic = new Dic();
dic.instance('myServiceOpts', { some: 'thing' });
const myService = dic.get('myService');
Registers async factory.
Factory function is called asynchronously and should return an instance of the service.
Kind: instance method of Dic
Param | Type |
---|---|
name | String |
factory | function |
[opts] | defOpts |
Example
dic.instance('mongoConnectionOpts', { url: 'mongodb://localhost:27017/mydb' });
dic.asyncFactory('mongoConnection', async function(mongoConnectionOpts) {
return await MongoClient.connect(mongoConnectionOpts.url);
});
Register a factory.
The factory function should return an instance of the service.
Kind: instance method of Dic
Param | Type |
---|---|
name | |
factory | |
[opts] | defOpts |
Example
dic.instance('myServiceOpts', { some: 'thing' })
dic.factory('myService', function(myServiceOpts) {
return new MyService(myServiceOpts);
});
Register an instance
Kind: instance method of Dic
Param |
---|
name |
instance |
Example
dic.instance('myScalarValue', 'string');
dic.instance('myObject', { some: 'thing' });
dic.instance('myFunction', function(msg) { console.log(msg) });
Register a class
Kind: instance method of Dic
Param | Type |
---|---|
name | |
classDef | |
[opts] | defOpts |
Example
// Class instance registration with dependency injection
class MyService {
constructor(myServiceOpts) {
this.options = myServiceOpts;
}
}
dic.instance('myServiceOpts', {
some: 'options'
})
dic.class('myService', MyService)
Example
// Class instance registration with default async init function
class MyService {
// optional async initialization of an instance
async asyncInit() {
//some async initialization e.g. open DB connection.
}
}
dic.class('myService', MyService)
Example
// Custom async init function
class MyService {
async otherAsyncInitFn() {
//...
}
}
dic.class('myService', MyService, {
asyncInit: 'otherAsyncInitFn'
})
Runs async initialization of container services.
This includes instances registered using:
- asyncFactory
- class a class having
async asyncInit()
method or with async init option set
Kind: instance method of Dic
Example
dic.asyncInit().then(() => {
// your services should be fully instantiated.
}, err => {
// async initialization of some service thrown an error.
console.error(err);
});
Returns true if a service is registered with a container
Kind: instance method of Dic
Param |
---|
name |
Get an instance.
Throws an error if instance needs to be async initialized and is not yet.
Kind: instance method of Dic
Param | Type |
---|---|
name | String |
Example
const myService = dic.get('myService');
Get an instance.
Async initialize the instance if it's not yet.
Kind: instance method of Dic
Param | Type |
---|---|
name | String |
Example
// Async/await
const myService = await dic.get('myService');
Example
// Promise
dic.getAsync('myService').then(myService => {
// ...
});
Creates an alias for existing container instance.
Kind: instance method of Dic
Param | Type | Description |
---|---|---|
name | String |
An instance to be aliased |
alias | String |
Alias |
Example
dic.instance('one', {some: 'instance'});
dic.alias('one', 'oneAgain');
dic.get('one') === dic.get('oneAgain')
Bind other Dic instance with this one.
Kind: instance method of Dic
Param | Type | Description |
---|---|---|
dic | Dic |
|
opts | Object |
|
opts.name | String |
Container services prefix name |
Example
// -----------------------------------------
// my-package.js - reusable package
// -----------------------------------------
const {Dic} = require('@kapitchi/bb-dic');
class Logger {
log(msg) {
console.log('MyLogger: ' + msg);
}
}
const dic = new Dic();
dic.instance('logger', Logger);
module.exports = dic;
// -----------------------------------------
// my-application.js - an application itself
// -----------------------------------------
const {Dic} = require('@kapitchi/bb-dic');
const packageDic = require('./my-package');
class MyService() {
constructor(myPackage_logger) {
// injected logger instance
this.logger = myPackage_logger;
}
sayHello(msg) {
this.logger.log(msg);
}
}
const dic = new Dic();
dic.class('myService', MyService);
dic.bind(packageDic, {
name: 'myPackage'
})
// get a child service instance directly
const logger = dic.get('myPackage_logger');
Create an instance injecting it's dependencies from the container
Kind: instance method of Dic
Param | Type | Description |
---|---|---|
def | Object |
|
def.factory | function |
Factory function |
def.class | function |
Class constructor |
def.inject | Object |
|
opts | Object |
Example
class MyClass {
constructor(myClassOpts, someService) {
}
}
dic.instance('myClassOpts', { my: 'options' });
dic.instance('someService', { real: 'service' });
const ins = dic.createInstance({
class: MyClass,
inject: {
// myClassOpts - injected from dic
// someService - the below is injected instead of dic registered 'someService'.
someService: { mock: 'service' }
}
})
Create an instance (async) injecting it's dependencies from the container.
See createInstance
Kind: instance method of Dic
Param | Type | Description |
---|---|---|
def | Object |
|
def.asyncFactory | function |
Async function |
def.factory | function |
Factory function |
def.class | function |
Class constructor |
def.inject | Object |
|
opts | Object |
Kind: global typedef
Properties
Name | Type | Description |
---|---|---|
asyncInit | string | boolean |
If true default asyncInit() function is used. If string, provided function is called on asyncInit. |
paramsAlias | Object |
Use to alias class constructor or factory parameters. E.g. { serviceA: 'serviceB' } injects serviceB instance instead of serviceA to the class constructor/factory. |
Run on NodeJS 7.* with --harmony
flag
const Koa = require('koa');
const {Dic} = require('@kapitchi/bb-dic');
const dic = new Dic();
dic.instance('functionMiddlewareOpts', { returnString: 'Hello World' });
dic.factory('functionMiddleware', function(functionMiddlewareOpts) {
return async (ctx) => {
console.log('functionMiddleware > before');//XXX
ctx.body = functionMiddlewareOpts.returnString;
console.log('functionMiddleware > after');//XXX
}
});
dic.class('classMiddleware', class ClassMiddleware {
async asyncInit() {
// some async initialization
}
async middlewareOne(ctx, next) {
console.log('classMiddleware.middlewareOne > before');//XXX
await next();
console.log('classMiddleware.middlewareOne > after');//XXX
}
});
dic.factory('app', function(
classMiddleware,
functionMiddleware
) {
const app = new Koa();
app.use(classMiddleware.middlewareOne);
app.use(functionMiddleware);
return app;
});
(async () => {
const app = await dic.getAsync('app');
app.listen(3000);
console.log('Running at: http://localhost:3000');
})();
const Hapi = require('hapi');
const {Dic} = require('@kapitchi/bb-dic');
const dic = new Dic();
dic.instance('functionHandlerOpts', {
response: {
msg: 'Hello from function handler'
}
});
dic.instance('classHandlerOpts', {
response: {
msg: 'Hello from class handler'
}
});
dic.factory('functionHandler', function (functionHandlerOpts) {
return async (request, reply) => {
reply(functionHandlerOpts.response);
}
});
dic.class('classHandler', class ClassHandler {
constructor(classHandlerOpts) {
this.options = classHandlerOpts;
}
async asyncInit() {
// some async initialization
}
async handler(request, reply) {
reply(this.options.response);
}
});
dic.factory('server', function(
functionHandler,
classHandler
) {
const server = new Hapi.Server();
server.register([
require('hapi-async-handler')
], function(err) {
if (err) {
throw err;
}
});
server.connection({
host: 'localhost',
port: 8000
});
server.route({
method: 'GET',
path: '/func',
handler: functionHandler
});
server.route({
method: 'GET',
path: '/class',
handler: classHandler.handler.bind(classHandler)
});
return server;
});
(async () => {
server = await dic.getAsync('server');
await server.start();
console.log('Server running at:', server.info.uri);
})();
Run the command below to builds es5 folder and README.md.
npm run build
npm test
Please feel free to submit an issue/PR or contact me at matus.zeman@gmail.com.