Nocturnal Practitioners of Magic
    Have ideas to improve npm?Join in the discussion! »

    iodine

    0.1.1 • Public • Published

    Iodine

    NPM version Build Status

    Iodine is a JavaScript framework built to help Node.js developers write modular applications by providing a Dependency Injector engine.

    Installation

    Install iodine with npm by running:

    npm install iodine

    Basic Usage

    var iodine = require('iodine');
     
    var app = iodine();
     
    if (process.env.PRODUCTION) {
        app.value('db.hostname', 'mydomain');
        app.value('db.port', '5678');
        app.value('db.name', 'users');
    } else {
        app.value('db.hostname', 'localhost');
        app.value('db.port', '1234');
        app.value('db.name', 'test');
    }
     
    app.bean('db', ['#db.hostname', '#db.port', '#db.name', function (dbHostname, dbPort, dbName) {
        return new Connection(dbHostname, dbPort, dbName);
    });
     
    app.run(function (db) {
        db.save({
            name: 'John Doe',
            age: 20
        });
    });

    Concepts

    App

    An App is the object responsible for holding values, beans and their respective instances. You can have more than only one App in your code. Example:

    var app1 = iodine();
    var app2 = iodine({
        // Some custom configuration
    });

    Bean

    A Bean is the responsible to represent an object that is accessible through your App instance. It can be either run or injected into another bean's implementation. It's holds information such as name, implementation, dependencies, initialization type (factory/constructor) and caching mode (singleton/transient).

    Implementation

    Implementation is the term used to describe the function (factory/constructor) responsible for the bean initialization. It can also take as arguments a set of other beans' instances.

    Implicit Dependency Annotations

    In order the define which beans should be injected into your new bean's implementation, you can use what is called Implicit Dependency Annotations (inspired by the Angular.js framework). This way, you don't need to explicitly list your dependencies, just define a bean and give it some arguments. Example:

    app.bean('myBean', function (dep1, dep2) {
        // ...
    });

    Iodine will parse your function and will find out that it depends on dep1 and dep2 and, if they exist, they will be injected.

    Note: You should only implicitly define your dependencies if your code is not going to be minified, since minifying process is based on renaming all variables into shorter names and dependency names are parsed from the actual arguments names. This is what would happen if you minify your code:

    // Will try to inject 'a' and 'b', but will fail
    app.bean('myBean',function(a,b){/* ... */});

    Inline Notation

    The inline notation allows beans implementations to be defined using an array, where the all items are strings that represent the name of beans that should be injected into current bean's implementation, which corresponds to the last item of this array. Example:

    app.bean('myBean1', ['dep1', 'dep2', function (dep1, dep2) {
        // ...
    }]);
     
    // Will infer the bean name from the implementation function's name
    app.bean(['dep1', 'dep2', function myBean2(dep1, dep2) {
        // ...
    }]);

    Object Notation

    By using the object notation, you are able to pass a BeanConfig object to a .bean() method call. Example:

    app.bean('myBean1', function () {
        this.method = function (dep1, dep2) {};
    }, {
        inject: ['dep1', 'dep2'],
        singleton: true,
    });
     
    app.bean(function myBean2() {
        this.method = function (dep1, dep2) {};
    }, {
        inject: ['dep1', 'dep2'],
        singleton: true,
    });

    Function Notation

    You can also define bean's configuration directly into it's implementation function by defining .__inject, .__singleton and __.factory properties. Example:

    var numberFactory = function (random) {
        return random(0, 100);
    };
    numberFactory.__inject = ['random'];
    numberFactory.__singleton = false;
    numberFactory.__factory = true;
     
    app.bean('number', numberFactory);

    Factory vs Constructor

    Factory

    A factory implementation is a bean implementation that is called as a normal function and is expected to return any value. Example

    app.bean('myFactory', function () {
        return {
            property: 'value',
            method: function () {}
        };
    });

    Constructor

    A constructor implementation is a bean implementation that is called with new keyword, and thus it shouldn't return any value. Instead, it should initialize the new object's methods/attributes. Example:

    app.bean('myConstructor', function () {
        this.property = 'value';
        this.method = function () {};
    });

    Singleton vs Transient

    Singleton

    A singleton is a bean that is initialized only once, and then its reference is cached for further uses. Example:

    var count = 0;
    app.bean('myBean', function () {
        // This function will be called only once the return value will be cached
        return ++count;
    }, { singleton: true });
     
    app.run(function (myBean) {
        console.log(myBean); // 1
    });
     
    app.run(function (myBean) {
        console.log(myBean); // 1
    });

    Transient

    A transient is a bean that is initialized everytime it's injected into another bean. Example:

    var count = 0;
    app.bean('myBean', function () {
        // This function will be called anytime this bean is injected
        return ++count;
    }, { singleton: false });
     
    app.run(function (myBean) {
        console.log(myBean); // 1
    });
     
    app.run(function (myBean) {
        console.log(myBean); // 2
    });

    API

    iodine(config: AppConfig): App

    Return an instance of App with the given configuration.

    iodine.version: SemVer

    Iodine's version holder. Corresponds to version defined in the package.json file, according to the semantic versioning specification.

    AppConfig

    A plain object containing the App's configuration. Example:

    {
        valueDelimiter: '#',
        logger: function (type, message) {
            switch (type) {
                case 'warning':
                    console.warn(message);
                    break;
                default:
                    console.log(message);
            }
        }
    }

    AppConfig#valueDelimiter: String (default = '#')

    The delimiter to be used to identify values being injected.

    AppConfig#logger: Function(type: String, message: String) (default = null)

    The logger function that will be eventually called by Iodine to log information.

    App

    Object that represents an Iodine application. It's responsible for holding all beans, instances and values.

    App#bean(...): App

    Register a new bean to the application.

    App#bean(impl: Function): App

    Register a new bean with name impl.name and implementation impl. Also, will try to infer all dependencies based on impl.toString() output.

    App#bean(name: String, impl: Function): App

    Register a new bean with name name and implementation impl. Also, will try to infer all dependencies based on impl.toString() output.

    App#bean(def: Inline): App

    Register a new bean with the last item of def as the bean's implementation, .name property of implementation function as the bean's name and the rest of items as dependencies names.

    App#bean(name: String, def: Inline): App

    Register a new bean with name name, the last item of def as the bean's implementation and the rest of items as dependencies names.

    App#bean(impl: Function, config: BeanConfig): App

    Register a new bean with name impl.name and implementation impl. Also, will use configuration defined in config.

    App#bean(def: Inline, config: BeanConfig): App

    Register a new bean with the last item of def as the bean's implementation, .name property of implementation function as the bean's name and the rest of items as dependencies names. Also, will use configuration defined in config.

    App#bean(name: string, def: Inline, config: BeanConfig): App

    Register a new bean with the last item of def as the bean's implementation, name as the bean's name and the rest of items as dependencies names. Also, will use configuration defined in config.

    App#bean(name: String, impl: Function, config: BeanConfig): App

    Register a new bean with name name and implementation impl. Also, will use configuration defined in config.

    App#value(...): void/PlainObject/any

    Register/restore a value from the application's values map.

    App#value(map: PlainObject): void

    Replace the whole values map with map. Example:

    app.value({ foo: 'bar' });
    App#value(namespace: String, value: *): void

    Register a new value value into namespace. Uses '.' as namespace delimiter. Example:

    app.value('foo.bar', true);
    App#value(): PlainObject

    Restore the whole values map. Example:

    app.value('foo.bar', true);
    app.value(); // { foo: { bar: true } }
    App#value(namespace: String): *

    Restore the value stored in namespace. Example:

    app.value('foo.bar', true);
    app.value('foo.bar'); // true

    App#inject(...): Function

    Return a wrapper function for the bean with all dependencies injected.

    App#inject(name: String): Function

    Find the bean with name nameand inject its dependencies.

    App#inject(impl: Function): Function

    Create a new bean based on impland inject its dependencies. Also, will try to infer all dependencies based on impl.toString() output.

    App#inject(def: Inline): Function

    Create a new bean based on def and inject its dependencies.

    App#inject(def: Inline, config: BeanConfig): Function

    Create a new bean based on def and inject its dependencies. Will use configuration defined in config.

    App#inject(impl: Function, config: BeanConfig): Function

    Create a new bean based on impl and inject its dependencies. Will use configuration defined in config.

    App#run(...): any

    Inject all dependencies and initialize the bean by calling it's implementation. Return the result returned from the implementation function.

    App#run(name: String): any

    Find the bean with name nameand initialize it.

    App#run(impl: Function): any

    Create a new bean based on impland initialize it. Also, will try to infer all dependencies based on impl.toString() output.

    App#run(def: Inline): any

    Create a new bean based on def and initialize it.

    App#run(def: Inline, config: BeanConfig): any

    Create a new bean based on def and initialize it. Will use configuration defined in config.

    App#run(impl: Function, config: BeanConfig): any

    Create a new bean based on impl and initialize it. Will use configuration defined in config.

    App#import(...): App

    Try to register the first argument as a new bean. Useful to register beans defined in other files.

    App#import(filename: String): App

    Require filename and pass returned value to #import. Example:

    // lib.js
    module.exports = {
        alpha: function alpha(dep1, dep2) {},
        beta: ['dep1', 'dep2', function beta(dep1, dep2) {}]
    };
     
    // index.js
    app.import('lib.js');
    App#import(map: PlainObject): App

    Iterate over each key/value tuple of map and try to register a new bean with based on tuple's value. Note that this value can be either a Function or an Inline notation. Example:

    // lib.js
    module.exports = {
        alpha: function alpha(dep1, dep2) {},
        beta: ['dep1', 'dep2', function beta(dep1, dep2) {}]
    };
     
    // index.js
    app.import(require('./lib.js'));
    App#import(map: PlainObject, keyAsName: Boolean): App

    Similar to the overload described above, expect for the fact that, in case keyAsName is truthy, each bean will be registered and their name will correspond to the key of each key/value tuple.

    App#import(impl: Function): App

    Will try to register a new bean based on impl. Example:

    // lib.js
    module.exports = function myBean(dep1, dep2) {};
     
    // index.js
    app.import(require('./lib.js'));
    App#import(impl: Function): App

    Will try to register a new bean based on impl. Example:

    // lib.js
    module.exports = ['dep1', 'dep2', function myBean(dep1, dep2) {}];
     
    // index.js
    app.import(require('./lib.js'));

    App#require(filename: String): Any

    Require filename and pass returned value to #run.

    BeanConfig

    A plain object containing the Bean's configuration. Example:

    {
        inject: ['dep1', 'dep2'],
        singleton: true,
        factory: true
    }

    BeanConfig#inject: Array (default= [])

    Array containing the names of the beans which the current bean depends on. Example:

    // lib.js
    module.exports = function (dep1, dep2) {};
     
    // index.js
    app.require('lib.js');

    BeanConfig#singleton: Boolean (default = true)

    Define whether the bean should be treated as a singleton or not.

    BeanConfig#factory: Boolean (default = true)

    Define whether the bean should be initialized from either a factory or a constructor.

    Inline

    A mixed-type array where the items are all strings that contain the names of the beans which the current bean depends on, except for the last item, which is actually a function that represents the bean's implementation.

    Example:

    ['dep1', 'dep2', function (dep1, dep2) {
        // ...
    }]

    License

    MIT License

    Install

    npm i iodine

    DownloadsWeekly Downloads

    6

    Version

    0.1.1

    License

    MIT

    Last publish

    Collaborators

    • avatar