Skip to content

A dependency injection container for JavaScript.

License

Notifications You must be signed in to change notification settings

matuszeman/bb-dic

Repository files navigation

bb-dic

Build Status

A dependency injection container with async support.

Installation

npm install @kapitchi/bb-dic

Usage

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.

Sync usage

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();

Async usage

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();
});

API

Classes

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.

Typedefs

defOpts : Object

DicConfigLoader

Config loader - sets up Dic from the config (plain object)

Kind: global class

new DicConfigLoader(opts)

Param Type Description
opts Object
opts.optionsSuffix string What suffix to use for "options" config. See: loadConfig

dicConfigLoader.loadConfig(dic, config)

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`, ...
    }
  }
}

DicFactory

A factory

Kind: global class

DicFactory.createDic(params) ⇒ Object

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

DicLoader

Dic loader

Kind: global class

new DicLoader(opts)

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;

dicLoader.loadPath(dic, path, [opts])

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

Dic

Dependency injection container

For more usage examples see: instance, class, factory, asyncFactory, bind.

Kind: global class

new Dic([options])

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');

dic.asyncFactory(name, factory, [opts])

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);
});

dic.factory(name, factory, [opts])

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);
});

dic.instance(name, instance)

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) });

dic.class(name, classDef, [opts])

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'
})

dic.asyncInit()

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);
 });

dic.has(name) ⇒ boolean

Returns true if a service is registered with a container

Kind: instance method of Dic

Param
name

dic.get(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');

dic.getAsync(name) ⇒ *

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 => {
  // ...
});

dic.alias(name, alias)

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')

dic.bind(dic, opts)

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');

dic.createInstance(def, opts) ⇒ *

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' }
  }
})

dic.createInstanceAsync(def, opts) ⇒ *

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

defOpts : 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.

Framework usage examples

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);
})();

Development

Run the command below to builds es5 folder and README.md.

npm run build

Tests

npm test

Contribute

Please feel free to submit an issue/PR or contact me at matus.zeman@gmail.com.

License

MIT