Git Product home page Git Product logo

electrolyte's Introduction

Electrolyte

Build Coverage Quality Dependencies

Electrolyte is a simple, lightweight inversion of control (IoC) container for Node.js applications.

Electrolyte automatically wires together the various components and services needed by an application. It does this using a technique known as dependency injection (DI). Using Electrolyte eliminates boilerplate code and improves software quality by encouraging loose coupling between modules, resulting in greater reusability and increased test coverage.

For further details about the software architecture used for IoC and dependency injection, refer to Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler.

Install

$ npm install electrolyte

Usage

There are two important terms to understand when using Electrolyte: components and annotations.

Components

Components are simply modules which return objects used within an application. For instance, a typical web application might need a place to store settings, a database connection, and a logging facility.

Here's a component that initializes settings:

exports = module.exports = function() {
  var settings = {}
    , env = process.env.NODE_ENV || 'development';

  switch (env) {
    case 'production':
      settings.dbHost = 'sql.example.com';
      settings.dbPort = 3306;
      break;
    default:
      settings.dbHost = '127.0.0.1';
      settings.dbPort = 3306;
      break;
  }

  return settings;
}

exports['@singleton'] = true;

Pretty simple. A component exports a "factory" function, which is used to create and initialize an object. In this case, it just sets a couple options depending on the environment.

What about exports['@singleton']? That's an annotation, and we'll return to that in a moment.

Here's another component that initializes a database connection (saved as 'database.js'):

var mysql = require('mysql');

exports = module.exports = function(settings) {
  var connection = mysql.createConnection({
    host: settings.dbHost,
    port: settings.dbPort
  });

  connection.connect(function(err) {
    if (err) { throw err; }
  });

  return connection;
}

exports['@singleton'] = true;
exports['@require'] = [ 'settings' ];

Also very simple. A function is exported which creates a database connection. And those annotations appear again.

Async Components

Async components are defined in an identical manner to traditional components except that the factory function should return a promise.

Let's rewrite the database component above slightly to return a promise.

var mysql = require('mysql');

exports = module.exports = function(settings) {
  return mysql.connectAsPromise({
    host: settings.dbHost,
    port: settings.dbPort
  }).then(function (conn) {
    // do something clever
    return conn;
  });
}

exports['@singleton'] = true;
exports['@require'] = [ 'settings' ];

Let's also define a users model that relies on the database component (saved as users.js).

exports = module.exports = function(database) {
  return {
    create: function (name, email, password) {
      return database.execute('INSERT INTO users ...');
    }
  };
}

exports['@singleton'] = true;
exports['@require'] = [ 'database' ];

Annotations

Annotations provide an extra bit of metadata about the component, which Electrolyte uses to automatically wire together an application.

  • @require Declares an array of dependencies needed by the component. These dependencies are automatically created and injected as arguments (in the same order as listed in the array) to the exported function.

  • @singleton Indicates that the component returns a singleton object, which should be shared by all components in the application.

Creating Components

Components are created by asking the IoC container to create them:

var IoC = require('electrolyte');

var db = IoC.create('database');

Electrolyte is smart enough to automatically traverse a component's dependencies (and dependencies of dependencies, and so on), correctly wiring together the complete object structure.

In the case of the database above, Electrolyte would first initialize the settings component, and pass the result as an argument to the database component. The database connection would then be returned from IoC.create.

This automatic instantiation and injection of components eliminates the boilerplate plumbing many application need for initialization.

Creating Async Components

Again, components are created by asking the IoC container to create them:

var IoC = require('electrolyte');

var usersPromise = IoC.createAsync('users');

usersPromise.then(function (users) {
  ...
});

Here as well electrolyte is smart enough to automatically traverse a component's dependencies, correctly wiring together the complete object structure and waiting for each promise to resolve along the way.

In the case of the users model above, Electrolyte would first initialize the settings component, and pass the result as an argument to the database component. Electrolyte would then wait for the database connection promise to resolve before passing the resulting value to the users component. IoC.createAsync then returns a promise that resolves to the object defined by the users component after the all of its dependencies resolve.

Configure the Loader

When a component is @require'd by another component, Electrolyte will automatically load and instantiate it. The loader needs to be configured with location where an application's components are found:

IoC.use(IoC.dir('app/components'));

@require vs require()

Loading components is similar in many regards to requireing a module, with one primary difference: components have the ability to return an object that is configured according to application-level or environment-specific settings. Traditional modules, in contrast, assume very little about the runtime configuration of an application and export common, reusable bundles of functionality.

Using the strengths of each approach yields a nicely layered architecture, which can be seen in the database component above. The mysql module provides reusable functionality for communicating with MySQL databases. The database component provides a configured instance created from that module that connects to a specific database.

This pattern is common: modules are require()'d, and object instances created from those modules are @require'd.

There are scenarios in which this line can blur, and it becomes desireable to inject modules themselves. This is typical with modules that provide network-related functionality that needs to be mocked out in test environments.

Electrolyte can be configured to do this automatically, by configuring the loader to inject modules:

IoC.use(IoC.node_modules());

With that in place, the database component above can be re-written as follows:

exports = module.exports = function(mysql, settings) {
  var connection = mysql.createConnection({
    host: settings.dbHost,
    port: settings.dbPort
  });

  connection.connect(function(err) {
    if (err) { throw err; }
  });

  return connection;
}

exports['@singleton'] = true;
exports['@require'] = [ 'mysql', 'settings' ];

Note that now the mysql module is injected by Electrolyte, rather than explicitly require()'d. This makes it easy to write tests for this component while mocking out network access entirely.

Examples

  • Express An example Express app using IoC to create routes, with necessary components.

  • Async Express An example Express app using IoC to create routes asynchronously, with necessary components.

Tests

$ npm install
$ npm test

Credits

License

The MIT License

Copyright (c) 2013 Jared Hanson <http://jaredhanson.net/>

electrolyte's People

Contributors

axelmm avatar bosgood avatar eligeske avatar h4cc avatar jaredhanson avatar niftylettuce avatar patrickhulce avatar quartzjer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

electrolyte's Issues

Constructor passed an object with dependencies instead of as parameters

I was looking to add the feature in myself, but I'm not very clear on your code base and not sure how it would fit in.

As an alternative to passing in dependencies to a constructor as each dependency being a parameter, and thus possibly creating a very unwieldy constructor signature, it would be nice if the dependencies could be passed in as an object.

As an example:

exports = module.exports = function(deps) {
  ...
};

exports['@requireObj'] = [ 'foo', 'bar' ];

And then deps would look like:

{
  foo: {},
  bar: {} // or whatever the instance is supposed to be
}

Add tests to example project

I am trying to find a typical pattern of mocking dependencies using electrolyte. Some example tests would be helpful. Thank you!

Scripts can not run in path other than that of project folder

Hi all,

I try to write a script that I can run anywhere. It worked fine when I was developing. However, it was unable to find my component after I installed it as a global package. Is it a good idea to provider an additional option for change current path?

Unable to create object "XXXX" required by: unknown

I don't know why I am getting this error. Below is my code

  1. custom_logger.js
exports = module.exports = function() {
  let mylog = new Promise((resolve, reject=>{
    console.log("Initializing promise function.");
    resolve("Worked fine");
  }));
  return mylog;
}

exports['@singleton'] = true;
  1. app.js
var IoC = require('electrolyte');
var mylog = IoC.create('custom_logger');
mylog.then(function(msg) {
  /* do something with the result */
}).catch(function(err) {
    console.log(err);
  });

This error which I got while running node app.js


Error: Unable to create object "custom_logger" required by: unknown
    at /home/darshitp/Documents/nodejs/di/node_modules/electrolyte/lib/container.js:146:18
    at Container._loadSpec (/home/darshitp/Documents/nodejs/di/node_modules/electrolyte/lib/container.js:216:10)
    at Container.<anonymous> (/home/darshitp/Documents/nodejs/di/node_modules/electrolyte/lib/container.js:144:12)
    at tryCallTwo (/home/darshitp/Documents/nodejs/di/node_modules/promise/lib/core.js:45:5)
    at doResolve (/home/darshitp/Documents/nodejs/di/node_modules/promise/lib/core.js:200:13)
    at new Promise (/home/darshitp/Documents/nodejs/di/node_modules/promise/lib/core.js:66:3)
    at Container._create (/home/darshitp/Documents/nodejs/di/node_modules/electrolyte/lib/container.js:132:10)
    at Container.create (/home/darshitp/Documents/nodejs/di/node_modules/electrolyte/lib/container.js:120:15)
    at Object.<anonymous> (/home/darshitp/Documents/nodejs/di/index.js:2:17)
    at Module._compile (module.js:571:32)

Performance?

Not sure if this has been tested before โ€“ but are there any ways we can optimize this?

Fix insecure dependencies

Due to this electrolyte uses out-dated version of debug package, so it would be better to fix this for users, who care about security.

Using custom resolvers to substitute components for testing and other purposes

Hello,

First I would like to say that I really appreciate your work on many Javascript projects, I learned a lot from how you use the full power of Javascript in simple and elegant ways in PassportJS and other projects. I especially like the almost complete extensibility you build into your frameworks. I use the Electrolyte framework since a few months ago and I have to say it's yet another brilliant piece of software. Ok, so enough of this :)

I have an issue related to replacing components with mockups (or other components) for testing purposes. Sometimes I need to replace a component not with a mockup, but with another component from the same assembly or from another assembly. Example: I need to replace a KMS client for an online service with an in-memory KMS in order to perform the tests without having to connect to an online service.

One way to do this is to use a custom assembly function that receives component ids and substitutes them with already created components previously annotated as literal. (aka container.use(assemblyLoadFunction)).

But the most elegant way I found is to use a custom resolver which substitutes component ids with other ids of mockup components. However, there is an issue here, the "id" resolver is already the first resolver in the list and I cannot make the system to use my resolver before that (unless I would modify my components to use special characters in required component ids which would not be a feasible solution for many reasons).

I would suggest adding the resolvers in reverse order (just like you do with assemblies on container.use()). So, in container.js, the method

Container.prototype.resolver = function(fn) {
this._resolvers.push(fn);
}

would be more useful if it would look like this:

Container.prototype.resolver = function(fn) {
this._resolvers.unshift(fn);
}

This way the custom resolvers would be called before the default id resolver and it would add to the extensibility of the framework.

Of course, this can be hacked right now with accessing the internal this._resolvers array which is what I am currently doing.

I'm not sure if this would break current projects but I dare to say it would not, given the fact that you have suggested custom resolvers to use special characters in component ids.

Thanks !

Should Electrolyte load scripts based on require annotations?

Looking through the Express example, it seems as if Electrolyte will load scripts based on the names given in require annotations. I'm not finding this to be the case. Take a look at this repository -- https://github.com/jsumners/electrolyte-test

Should that repository work as-is? Because I'm getting the following error when I run it:

TypeError: Cannot read property 'message' of undefined
    at module.exports (/private/tmp/04/electrolyte-test/server/config/routes.js:11:29)
    at Factory.instantiate (/private/tmp/04/electrolyte-test/node_modules/electrolyte/lib/patterns/factory.js:15:18)
    at Factory.Component.create (/private/tmp/04/electrolyte-test/node_modules/electrolyte/lib/component.js:29:28)
    at Container.create (/private/tmp/04/electrolyte-test/node_modules/electrolyte/lib/container.js:81:15)
    at Object.<anonymous> (/private/tmp/04/electrolyte-test/server.js:12:18)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)

Or am I mistaken in my understanding of how Electrolyte works? Should be creating instances of my components prior to trying to use them as dependencies?

Feature: Override existing Modules

Hello everyone,

at the moment i am looking for a nice way to mock my modules and at this point i noticed that it would be nice if i could override single modules in my Container with custom test functions.

That step would not be that far away since the Conatiner has a _specs property with the required path as key and the the cached Module as value.

Thoughts?

Documenting patterns and general API

First off, I think this project has great potential.
Moved by my need to instantiate a local module before calling IoC.create('some-module'), I discovered IoC.literal API.

Can the exposed API be a little documented? Examples in README are clear, a short list of exposed methods as recap at the end would be perfect.

I think a also documenting the semantic difference between literal, factory and constructor patterns would shorten the time span adopting this project (I think a couple lines each should be enough, eventually linking to addyosmani design patterns book).

A fan ๐Ÿ‘

Node_module factory functions not instantiated

I am experiencing an issue where my factory components that are also node modules are not instantiated but the functions are marked as literals.

The statement module['@literal'] = true; in loaders/node_modules (line 6) is causing this. Changing it to if (module['@literal'] === undefined) module['@literal'] = true; should solve the problem.

Another option is to remove the above mentioned line entirely and use the container's _registerModule function. The default pattern for anything that is not a function is already literal there.

Using it with class

Hello,

I've wrote my class as this:

class Application {
  /**
   * Creates a new Application
   */
  constructor(express, router, config) {
    this.app = express();
    this.router = router;
    this.setting = config;
  }
}

module.exports = Application;

module.exports['@require'] = [
  'express',
  'router',
  'config'
]

In my entry point, i have this:

let IoC = require('electrolyte');
IoC.use(IoC.node_modules());
IoC.use(IoC.dir('src/'));
IoC.use(IoC.dir('app/'));

But all the dependencies are undefined. Can you help please?

Webpack?

Any chance Electrolyte could work with Webpack?

support for es6 module syntax

at the moment it doesn't seem possible to use the proposed es6 module syntax with the annotations required by this module. the easiest solution would seem to be allowing custom annotations, or namespacing the annotations under a top level attribute that is a valid variable name (similar to scatter)

renaming:

// in index.js
import Container from 'electrolyte';

Container.configure({
  prefix: '_'
});
// in foo.js
export default function(bar) {
  // ..
};

export const _singleton = true;
export const _require = ['bar'];

namespacing:

// in index.js
import Container from 'electrolyte';

Container.configure({
  namespace: 'ioc'
});
// in foo.js
export default function(bar) {
  // ..
};

export const ioc = {
  singleton: true,
  require: ['bar']
};

thoughts?

Error on require

I have just been requireing electrolyte
simple as:
var IoC = require('electrolyte');

throws following error:

node test.js

util.js:555
ctor.prototype = Object.create(superCtor.prototype, {
^
TypeError: Object prototype may only be an Object or null
at Function.create (native)
at Object.exports.inherits (util.js:555:27)
at Object. (/Users/xxx/testproject/node_modules/electrolyte/lib/container.js:42:6)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.require (module.js:364:17)
at require (module.js:380:17)
at Object. (/Users/xxx/testproject/node_modules/electrolyte/lib/index.js:1:79)

am i using it wrong or is there an unstable state online?

thanks

Any release?

I suppose with the new commits a new release might be fine (works as before, but a little better from my empyrical tests), but a breaking one since ioc.loader is now deprecated, so i suppose 0.1.0 ?

@require'd component not passing into exported function?

OK, this is maybe only a newbies mistake. The thing is I can't see my component 'models/procedures' in my another component 'controllers/uploadHandler' here's an example.

What did I do wrong?

Electrolyte is awesome by the looks of it. Very simple concept and powerful, but lacks proper documentation ( I guess ). Anyhow I started learning it by reading the readme and following jsummers electrolyte example. ( I thank him very much, learn a lot from that. )

Basically I wanted to seperate database procedures and controllers. 'config/database' returns a connection of a postgres database and 'models/procedures' uses that connection. Other component like that 'controllers/uploadHandler' use functions that are in 'models/procedures', and that's my idea on how to communicate with a database using clean code thanks to electrolyte. ๐Ÿ‘

Thank you :)

version 0.3.0 returns objects while 0.4.0 returns promises

Can someone explain why the new version of electrolyte breaks the way of working. On doing ioc.create(<component>) it is returning promise in version 0.4.0 while in 0.3.0 it returns the object exported via the component. Nested promises as well....

Any pointers on how to use the new version of electrolyte and reconcile old code with new version of electrolyte?

Extending the container.

How would I go about getting the following to work. At the minute @create doesn't exist even though I'm extending the container instance. Any ideas?

Container = require 'electrolyte'
path = require 'path'

exports = module.exports = class App extends Container

    # @var object
    paths: {}

    # @var Configuration
    config: null

    constructor: ->
        @bootPaths()
        @bootConfig()
        @bootExpress()
        @bootServices()

    bootPaths: =>
        @paths =
            views: path.join __dirname, 'views'
            src: path.join __dirname, 'src'
            public: path.join __dirname, 'public'

    bootConfig: =>
        @config = @create 'libs/config'

node module with hyphens

When a node module has hyphens I can't inject it. E.g. documentdb-q-promises, when I add this as a parameter to my module I get a js issue. Is there any way to inject node modules with hyphens?

Babel transpile with babel-preset-es2015 + async function breaks Electrolyte

Something about the transpiling this class with babel-preset-2015 will cause Electrolyte to be unable to create the object:

'use strict';

class Test {
  async home() {
  }
}
module.exports = function() {
  return new Test();
};

module.exports['@singleton'] = true;

The two specific things that cause the error are the async function, and using babel-preset-es2015. So if you're using babel-preset-latest, this problem will occur since that includes es2015. Using babel-preset-2016 and babel-present-2017 will not cause this problem.

Since the ES2015 (ES6) features are all part of the most recent versions of (Node 6+) this isn't really a huge problem since you don't need to transpile for ES6 anymore. I just wanted to post this here in case anybody else bangs there head against the wall trying to figure out why the most basic usage of this package isn't working.

Removing the "async" part of the function or not using babel-preset-es2015 fixes the problem.

Manually adding an object to the container

I'm using hapi.js and I'd like to add the hapi server object to my electrolyte container while at the same time maintaining some of hapi's best practices.

The hapi server object maintains its own container of plugin objects, and a typical way to set up a hapi project is to use a 'bootstrap' plugin to set up the server. Mine looks like this:

var ioc = require('electrolyte');
var config = require('./config');

exports.register = (server, options, next) => {
    server.auth.strategy('jwt', 'jwt', {
        key           : config('/auth/secret'),
        validateFunc  : require('./src/application/auth/validateJWT'),
        verifyOptions : { algorithms: [ 'HS256' ] }
    });

    ioc.use(ioc.node('src/application'));
    ioc.use(ioc.node('src/lib'));

    server.route(ioc.create('example/example-routes'));
    server.route(ioc.create('auth/auth-routes'));
    server.route(ioc.create('user/user-routes'));

    next();
};

exports.register.attributes = {
    name    : 'ims-bootstrap',
    version : '0.0.1'
};

I want to make the server object that is passed into this plugin available in my electrolyte container. Is there some way to call ioc.use such that it will just make the already instantiated server object available in the container?

RangeError: Maximum call stack size exceeded

I'm getting this error , with the following information :

node_modules/electrolyte/lib/component.js:9
Component.prototype.create = function(container) {

this may be happening because I have two components that need each other: so

Component A requires Component B, and
Component B requires Component A

how would I get around this problem ?

Implicit factory resolution

Hi!

Is there a way to ask electrolyte NOT to resolve factories?
I've stuck with @require for node-uuid module, which exposes itself as a function with additional methods and electrolyte provides function return value only, not the module.

module.exports = function (uuid) {
  // uuid === 'uuid string' here
  return uuid.v4(); // error: v4 is undefined
}

module.exports['@require'] = ['node-uuid']

Provide a container factory rather than an immediate global container

I've been running into an issue with electrolyte where the fact that it operates as a global container (cached by node once required) makes it problematic to use when you have one electrolyte project using another electrolyte library as a dependency (since node doesn't guarantee that your dependency won't just use a higher level electrolyte in node_modules). So I get collisions on component names, which silently override the earlier ones. It also makes testing harder because I cannot create isolated containers for a given test suite.

Update documentation to better explain advantages

I'm having some difficulty understanding the advantage of using this library over what can be accomplished with vanilla modules via CommonJS/RequireJS. You do say:

Loading components is similar in many regards to requireing a module, with one primary difference: components have the ability to return an object that is configured according to application-level or environment-specific settings. Traditional modules, in contrast, assume very little about the runtime configuration of an application and export common, reusable bundles of functionality.

However, that's not necessarily true. Anyone can write a module that's "application-level or environment-specific" by just requireing another module that contains application-level or environment-specific elements. To take your database example:

var mysql = require('mysql');
var settings = require('./settings');

exports = module.exports = function() {
  var connection = mysql.createConnection({
    host: settings.dbHost,
    port: settings.dbPort
  });

  connection.connect(function(err) {
    if (err) { throw err; }
  });

  return connection;
}

I'm sure I'm missing something since I'm fairly new to DI in JavaScript, but is there something more than syntactic sugar in electrolyte? If you could expand on the advantages more, I think it would improve the documentation. (One thing is that it's easier to make a singleton, but that doesn't seem like the main reason you created this library).

Cannot find module in version 0.2.2

I am getting the below error with version 0.2.2 when using following:

ioc.use('util', ioc.dir('app/util'));

This was working in version 0.2.0

The exception is:

Error: Cannot find module 'C:\code\test\app\util'
    at Function.Module._resolveFilename (module.js:339:15)
    at Function.Module._load (module.js:290:25)
    at Module.require (module.js:367:17)
    at require (internal/module.js:16:19)
    at Object.app [as load] (C:\code\test\node_modules\electrolyte\lib\sources\dir.js:46:12)
    at Container._loadSpec (C:\code\test\node_modules\electrolyte\lib\container.js:194:18)
    at Container.create (C:\code\test\node_modules\electrolyte\lib\container.js:121:10)
    at FactorySpec.Spec.create (C:\code\test\node_modules\electrolyte\lib\spec.js:83:24)
    at Container.create (C:\code\test\node_modules\electrolyte\lib\container.js:131:18)
    at FactorySpec.Spec.create (C:\code\test\node_modules\electrolyte\lib\spec.js:83:24)
    at Container.create (C:\code\test\node_modules\electrolyte\lib\container.js:131:18)
    at FactorySpec.Spec.create (C:\code\test\node_modules\electrolyte\lib\spec.js:83:24)
    at Container.create (C:\code\test\node_modules\electrolyte\lib\container.js:131:18)
    at Object.<anonymous> (C:\code\test\app\main.js:9:18)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:141:18)
    at node.js:933:3

Does IoC.use have to return a function?

Hi!

I'll be honest here, I did not dig deep into the module to find out the answer to my question so here we go:

const IoC = require('electrolyte')

IoC.use(
    IoC.dir('app')
)

(async () => {
    const dep = await IoC.create('dependency')
    dep.run()
})()

If you try this, it will throw an error because IoC.use returns a function so the interpreter will try to run it with the async anonymous function as an argument.
Obviously one could evade this issue by just putting a semicolon at the end of IoC.use but in a lot of workspaces the coding style requires us not to write those at the end of the lines.

I hope that makes sense!

Full API documentation?

Can we get a full documentation about method and their descriptions that are exposed by electrolyte?

The quick start documentation doesn't explain the library in details (Example use, and node methods aren't very clear)

Child container

Hi,

I'd like to have a Child DI containers in a way like InversifyJS ones in order

  • to provide per-request context provisioning;
  • to use electrolyte-based libraries.
    Is there a way to achieve that or it should be developed from scratch?

change default as singleton = true

Is that possible via configurable options?

Usually node files are somewhat similar to singletons, due to the require-cache, so when refactoring for electrolyte it doesn't require to add @singleton = true everywhere

Automatically Detect Dependencies

At first, great job. This DI framework is really great.

But while working with angular, i really like to simply require a dependency by just naming the parameter correctly.

Example:

module.exports = function TimeAlarm(moment) { // Lets roll }

This removes the "@require" annotation.

Maybe my attempt is already helpful:

To get the parameter of a function, i copied the auto-annotation function from angular's di code:

function isArray(arr){
  return typeof arr.length != "undefined"
}

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        fnText = fn.toString().replace(STRIP_COMMENTS, '');
        argDecl = fnText.match(FN_ARGS);
        argDecl[1].split(FN_ARG_SPLIT).forEach(function (arg) {
          arg.replace(FN_ARG, function (all, underscore, name) {
            $inject.push(name);
          });
        });
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    // assertArgFn(fn[last], 'fn');
    $inject = fn.slice(0, last);
  } else {
    // assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

This will simply give an array of the parameter names of passed function.

Example from the code:

Container.prototype._registerModule = function(id, mod, sid) {
  var dependencies = mod['@require'] || []
    , pattern = 'literal';

  if(typeof mod != "undefined" && (!mod['@require'])) {
    //Will print ["moment"] for the TimeAlarm component
    console.log(annotate(mod));  
    dependencies = annotate(mod);
  }
  if (typeof mod == 'function') {  [...]

(container.js:235)

I did some changes, but i get stuck when my application uses paths to reach the components.

I changed the registering function:

Container.prototype.register = function(comp, sid) {
  // TODO: Pass sid to constructor (??)
  comp._sid = sid;

  //This will register components defined with paths as well
  //Example: "utils/Logger" will be available under "utils/Logger" and just "Logger"
  var arrSplitPath = comp.id.split("/");
  if(typeof this._o[arrSplitPath[arrSplitPath.length-1]] != "undefined"){
    this._o[arrSplitPath[arrSplitPath.length-1]] = comp;
  }

  this._o[comp.id] = comp;
}

(container.js:132)

With this change, my annotation will inject 'Logger' correctly.

But when i use ioc.node, the application cant find the components.

Example:
In my code i've got something like this:

ioc.loader("components", ioc.node("src/core/components"));

The required components is under "components/alarm/AlarmManager"

I will require "AlarmManager" by auto-annotation.

It will call the function Container.prototype._loadModule
And there it fails, because i cant simply search within source.fn of each node, since it will try to load the script with scripts.resolve directly.

Extending DI into loaded modules

More of a topic for discussion than an issue. I've been thinking quite a bit lately about how DI in general, and electrolyte specifically, might be able to support injection in loaded dependencies (i.e. other projects I've specified as dependencies in package.json). I think I've got the basic framework of a solution in place, so with straw man in hand it's time for outside opinions!

First, let me state conceptually what I think DI in dependencies means. Basically I was looking for a few things:

  • Dependencies should be available as modules to the parent app container (what IoC.node_modules() currently accomplishes - this should not break!).
  • Modules declared in the parent container should be injectable into modules inside of dependencies. So a logger configured in the main app should be usable even inside of modules in a loaded dependency).
  • Modules declared in the dependency should NOT be injectable into modules in the main application. The API of a dependency should be inviolate and implementation details should not leak.
  • Within a dependency, modules references should be addressable directly without any sort of special syntax to identify the dependent project (e.g. exports['@require'] = ['userRepository'];, not exports['@require'] = ['myProject/userRepository'];).

Okay, so with that goal in mind and after quite a bit of trying to understand the guts of electrolyte, what I'd like to propose is adding to Container the concept of an optional "parent". Each library which wants to support DI would then have the container of the parent app injected into their main file, and use that container as the parent of a container local to that library. When a container is asked to create a module, it then attempts to create that module locally and, if unsuccessful and has a linked parent container, defers to the parent to attempt to create the module. And that's pretty much it.

So what does this look like in practice? Essentially, the main file of a library looks very much like the main file of the parent app, AND it looks a bit like a module spec - because now it's actually both. For example:

var electrolyte = require('electrolyte'),
    path = require('path');

var exports = module.exports = function(parentContainer) {
  var ioc = require('electrolyte')(parentContainer);
  ioc.use(electrolyte.node(path.join(__dirname, 'lib')));
  return ioc.create('myService');
};
exports['@singleton'] = true;
exports['@require'] = ['!container'];

And then any modules inside of the lib directory of this library are in turn configured by the container as needed using either other modules inside this library or any needed modules from the parent container. And as you might suspect, this hierarchical pattern is completely repeatable - there's no reason this library could not in turn load dependencies of its own that are similarly configured (and on and on as many levels deep as required).

What's it take to make that work? Actually very little:

  • Add _parent to Container
  • Add logic to Container.prototype.create to fail up to the parent if spec is not resolved locally
  • Modify the main index.js to allow for optionally passing in a parent container instead of always constructing a raw new Container()

And that's it? Well, no, not it. Remember the first bullet in the objectives - making this work with the existing .node_modules(). That I have not done yet and it's going to take a little creativity...

Also, I'm not very happy with the fact that the libraries have to explicitly prefix their local paths with __dirname for all of their use() statements. Haven't figured out how to work around that yet :'(

But other than that, the above solution seems to actually work very well.

Okay - straw man erected and sticks passed around to the group. Begin with the beating :)

Async creation of components

Is there any intention to support async creation of components? For example, a connection to db should not be injected into another component until it is actually established (something that can be confirmed only asynchronously).

Ideally, a component could return a promise and creation of dependent components should be conditioned on the fulfillment of that promise.

Question: How to use Electrolyte IoC in tests?

Is there a way to somehow reset.reload container? That would be useful since, what I planned to do (in absence of a better idea) is to mock/modify/stub some of the injected dependencies for the purpose of a test. However, I want a clean state for the next test (usually I would reset container in beforeEach()).

If this is not the right way, can someone point me to the right direction? Electrolyte is great, but I could not find good example how to use it in tests and how to mock deps.

Another good thing would be the ability to manually add/override some component in the container - again for test mocking purposes (something similar to Angular's $provide...)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.