Socket
Socket
Sign inDemoInstall

electrolyte

Package Overview
Dependencies
7
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.0 to 0.5.0

lib/assembly.js

229

lib/container.js

@@ -6,8 +6,10 @@ // Load modules.

, Promise = require('promise')
, FactorySpec = require('./patterns/factory')
, ConstructorSpec = require('./patterns/constructor')
, LiteralSpec = require('./patterns/literal')
, Assembly = require('./assembly')
, FactoryComponent = require('./patterns/factory')
, ConstructorComponent = require('./patterns/constructor')
, LiteralComponent = require('./patterns/literal')
, InjectedContainer = require('./injectedcontainer')
, SpecNotFoundError = require('./errors/specnotfound')
, deprecate = require('depd')('electrolyte')
, InterfaceNotFoundError = require('./errors/interfacenotfound')
, ComponentNotFoundError = require('./errors/componentnotfound')
, ComponentCreateError = require('./errors/componentcreate')
, debug = require('debug')('electrolyte');

@@ -23,9 +25,17 @@

*
* Objects are created from a specification. A specification defines the
* requirements needed in order to create an object. Such requirements include
* the objects required by the object being created. When an object requires
* other objects, the required objects will be created prior to the requiring
* object, and so on, transitively assembling the complete graph of objects as
* necessary.
* Objects are created according to a component specification. A component
* specification defines the requirements necessary to create an object. Such
* requirements include the objects required by the object being created. When
* an object requires other objects, the required objects will be created prior
* to the requiring object, and so on, transitively assembling the complete
* graph of objects as necessary. Component specifications, and the objects
* created from them, are often colloquially referred to as simply "components."
*
* A component specification can request objects that conform to an interface.
* An interface declares the abstract behavior of an object, without concern
* for implementation details. The container will dynamically create an object
* that conforms to the interface according to the runtime environment and
* configuration. This component-based approach to software development
* increases modularity of the system.
*
* @constructor

@@ -36,5 +46,5 @@ * @api public

EventEmitter.call(this);
this._specs = {};
this._sources = {};
this._assemblies = {};
this._order = [];
this._components = {};
this._resolvers = [];

@@ -50,49 +60,49 @@

/**
* Utilize a source of objects.
* Utilize an assembly.
*
* The container creates a objects from object sources. Sources are typically
* a directory on the file system or a package of objects that are specifically
* designed to function together.
* The container creates objects from assemblies. An assembly is a collection
* of components that are built to work together and form a logical unit of
* functionality. An assembly is typically distributed as a package,
* facilitating resuse across applications. For application-specific components
* that are not reusable, an assembly can simply consist of a directory on the
* file system.
*
* @param {string} ns - The namespace under which to mount the source.
* @param {function} fn - Loader of object specifications from the source.
* @param {string?} ns - optional namespace under which to mount the assembly.
* @param {function|object} asm - assembly of components.
* @public
*/
Container.prototype.use = function(ns, s, options) {
Container.prototype.use = function(ns, asm) {
if (typeof ns !== 'string') {
options = s;
s = ns;
// force ns to be an empty string if not specified
asm = ns;
ns = '';
}
options = options || {};
var load = s
, manifest = {}
, ids, aid, spec, i, len;
if (typeof s == 'object') {
manifest = s;
load = s.load || function noop(id) { return; };
asm = asm || {};
// accept either object or loader function
if (typeof asm == 'function') {
asm = { load: asm, export: true };
}
if (typeof load != 'function') {
throw new Error("Container#use requires a load function, was passed a '" + (typeof load) + "'");
if (typeof asm.load != 'function') {
throw new TypeError("Container#use requires `asm` to be either a function or an object with a `load` function, '" + (typeof asm.load) + "' has been passed");
}
var h = this._order.length;
var source = { namespace: ns, load: load };
this._sources[h] = source;
asm = new Assembly(h, ns, asm);
this._assemblies[h] = asm;
this._order.unshift(h);
ids = Object.keys(manifest);
var ids = asm.components
, comp, i, len;
for (i = 0, len = ids.length; i < len; ++i) {
aid = path.join(source.namespace, ids[i]);
spec = manifest[ids[i]];
if (spec === load) { continue; }
this._registerSpec(aid, spec, source);
comp = asm.load(ids[i]);
if (!comp) {
throw new ComponentNotFoundError("Cannot find component '" + ids[i] + "'");
}
this._registerComponent(ids[i], comp, asm);
}
return this;
}
Container.prototype.loader = deprecate.function(Container.prototype.use, 'Container#loader: Use Container#use instead');

@@ -102,11 +112,11 @@ /**

*
* Creates an object from the specifications registered with the container.
* When the object being created requires other objects, the required objects
* will automatically be created and injected into the requiring object. In
* this way, complex graphs of objects can be created in a single single line of
* Creates an object from the assemblies registered with the container. When
* the object being created requires other objects, the required objects will
* automatically be created and injected into the requiring object. In this
* way, complex graphs of objects can be created in a single single line of
* code, eliminating extraneous boilerplate.
*
* A specification can declare an object to be a singleton. In such cases, only
* one instance of the object will be created. Subsequent calls to create the
* object will return the singleton instance.
* A component specification can declare an object to be a singleton. In such
* cases, only one instance of the object will be created. Subsequent calls to
* create the object will return the singleton instance.
*

@@ -119,21 +129,49 @@ * Examples:

*
* @param {string} id - The id of the object to create.
* @param {Spec} [parent] - (private) The parent specification requiring the object.
* @param {string} id - The id or interface of the object to create.
* @param {Component} [parent] - (private) The parent component requiring the object.
* @param {Component} [ecomp] - (private) The component to create.
* @returns {Promise}
* @public
*/
Container.prototype.create = function(id, parent) {
return this._create(id, parent);
};
Container.prototype._create = function(id, parent) {
var psource = parent ? parent._source : undefined;
Container.prototype.create = function(id, parent, ecomp) {
// built-ins
switch (id) {
case '!container':
return Promise.resolve(new InjectedContainer(this, parent, psource && psource.namespace));
return Promise.resolve(new InjectedContainer(this, parent));
}
return new Promise(function(resolve, reject) {
var self = this;
function create(comp) {
if (!parent || comp._assembly.h == parent._assembly.h) {
// The object is being created by another object within the same
// assembly, or by the main script.
return resolve(comp.create(self));
} else {
// The object is being created by an object from another assembly.
// Most typically, a component interface is requested, allowing dynamic
// object creation according to the runtime environment and
// configuration.
//
// Assemblies declare the components they export for use by other
// assemblies. Any non-exported components are considered private, and
// usable only within the assembly itself. Restrictions are put in
// place to prevent private components from being used outside of their
// assembly.
if (!comp._assembly.isExported(comp.id)) {
return reject(new ComponentCreateError("Private component '" + comp.id + "' required by '" + parent.id + "'"));
}
return resolve(comp.create(self));
}
}
if (ecomp) {
// A component has been exposed via introspection, and an object is being
// created from the component specification. This is a fast path,
// implemented as an optimization to avoid redundant resolution
// operations.
return create(ecomp);
}
if (parent && id[0] == '.') {

@@ -144,15 +182,18 @@ // resolve relative component ID

id = this.resolve(id, parent);
var spec = this._specs[id];
if (spec) {
return resolve(spec.create(this));
var comp = this._components[id];
if (comp) {
return create(comp);
} else {
var self = this;
this._loadSpec(id, function(err, spec) {
if (err && err.code == 'SPEC_NOT_FOUND') {
reject(new Error('Unable to create object "' + id + '" required by: ' + (parent && parent.id || 'unknown')));
this._loadComponent(id, function(err, comp) {
if (err instanceof ComponentNotFoundError) {
// Reject with a more informative error message that indicates the
// requiring component. This assists the developer in finding and
// fixing the cause of error.
reject(new ComponentNotFoundError("Unable to create component '" + id + "' required by '" + (parent && parent.id || 'unknown') + "'"));
} else if (err) {
return reject(err);
}
resolve(spec.create(self))
return create(comp);
});

@@ -163,12 +204,10 @@ }

Container.prototype.specs = function() {
// TODO: Wrap specs so they can be created in an optimzied way
var ids = Object.keys(this._specs)
, specs = []
Container.prototype.components = function() {
var ids = Object.keys(this._components)
, comps = []
, i, len;
for (i = 0, len = ids.length; i < len; ++i) {
specs.push(this._specs[ids[i]]);
comps.push(this._components[ids[i]]);
}
return specs;
return comps;
}

@@ -184,3 +223,3 @@

}
throw new Error('Unable to resolve interface "' + id + '" required by: ' + (parent && parent.id || 'unknown'));
throw new InterfaceNotFoundError("Cannot find component implementing interface '" + id + "' required by '" + (parent && parent.id || 'unknown') + "'", id);
}

@@ -194,5 +233,5 @@

/**
* Load object specification.
* Load component specification.
*
* As a prerequisite for creating an object, an object specification must be
* As a prerequisite for creating an object, a component specification must be
* available. The specification declares instructions about how to create an

@@ -202,27 +241,27 @@ * object, such as whether the object should be a singleton instance and any

*
* Object instances will be created by invoking the specification's factory
* function. A factory function is typically a function that returns the
* object or a constructor that is invoked using the `new` operator.
* Object instances will be created by invoking the component's factory
* function. A factory function is typically a function that returns the object
* or a constructor that is invoked using the `new` operator.
*
* @param {string} id - The id of the specification to load.
* @param {string} id - The id of the component specification to load.
* @private
*/
Container.prototype._loadSpec = function(id, cb) {
Container.prototype._loadComponent = function(id, cb) {
debug('autoload %s', id);
var order = this._order
, source, mod, rid
, asm, comp, rid
, i, len;
for (i = 0, len = order.length; i < len; ++i) {
source = this._sources[order[i]];
rid = path.relative(source.namespace, id);
asm = this._assemblies[order[i]];
rid = path.relative(asm.namespace, id);
if (rid.indexOf('../') == 0) { continue; }
mod = source.load(rid);
comp = asm.load(id);
if (mod) {
spec = this._registerSpec(id, mod, source);
if (comp) {
spec = this._registerComponent(id, comp, asm);
return cb(null, spec);
}
}
return cb(new SpecNotFoundError('Cannot find spec "' + id + '"'));
return cb(new ComponentNotFoundError("Cannot find component '" + id + "'"));
}

@@ -251,3 +290,3 @@

*/
Container.prototype._registerSpec = function(id, mod, source) {
Container.prototype._registerComponent = function(id, mod, source) {
var spec, pattern;

@@ -272,7 +311,7 @@

debug('register factory %s', id);
spec = new FactorySpec(id, mod, source);
spec = new FactoryComponent(id, mod, source);
break;
case 'constructor':
debug('register constructor %s', id);
spec = new ConstructorSpec(id, mod, source);
spec = new ConstructorComponent(id, mod, source);
break;

@@ -282,7 +321,7 @@ case 'literal':

debug('register literal %s', id);
spec = new LiteralSpec(id, mod, source);
spec = new LiteralComponent(id, mod, source);
break;
}
this._specs[spec.id] = spec;
this._components[spec.id] = spec;
return spec;

@@ -289,0 +328,0 @@ }

// Load modules.
var path = require('canonical-path')
, ExposedSpec = require('./exposedspec');
, ExposedComponent = require('./exposedcomponent');

@@ -9,3 +9,3 @@

*
* When a "factory" requires that the container itself be injected, the
* When a factory requires that the container itself be injected, the
* container is first wrapped. This wrapper provides an interface that can be

@@ -24,6 +24,8 @@ * used by the factory to introspect its environment, which is useful when

*/
function InjectedContainer(c, spec, ns) {
function InjectedContainer(c, parent) {
var pasm = parent ? parent._assembly : undefined;
this._c = c;
this._spec = spec;
this._ns = ns || '';
this._parent = parent;
this._ns = (pasm && pasm.namespace) || '';
}

@@ -34,25 +36,19 @@

*
* This function is equivalent to Container#create, with the exception that the
* object being created must be within the namespace object creating it.
* This function is equivalent to Container#create.
*
* @param {string} id - The id of the object to create.
* @param {Component} [comp] - (private) The component to create.
* @returns {Promise}
* @public
*/
InjectedContainer.prototype.create = function(id) {
var aid = path.join(this._ns, id);
var rid = path.relative(this._ns, aid);
if (rid.indexOf('../') == 0) {
return Promise.reject(new Error(id + ' not within namespace'));
}
return this._c.create(aid, this._spec);
InjectedContainer.prototype.create = function(id, comp) {
return this._c.create(id, this._parent, comp);
}
/**
* Introspect object specifications.
* Introspect component specifications.
*
* This function is equivalent to Container#specs, with the exception that the
* object object specifications returned are restricted to those within the
* namespace object introspecting them.
* This function is equivalent to Container#components, with the exception that
* the object component specifications returned can be filtered in various ways
* relative to the component performing introspecting.
*

@@ -62,21 +58,35 @@ * @returns {array}

*/
InjectedContainer.prototype.specs = function(q) {
if (q && !Array.isArray(q)) {
InjectedContainer.prototype.components = function(q, filt) {
if (typeof q == 'string' && !Array.isArray(q)) {
q = [ q ];
} else if (typeof q == 'boolean') {
filt = q;
q = undefined;
}
// Filter the exposed specs to only those that exist within the namespace
// of the object being created.
var specs = this._c.specs()
if (filt === true) {
// When `filt` is set to true, the exposed components will be filtered to
// consist of only those that exist within the namespace of the
// introspecting component.
filt = this._ns;
}
var comps = this._c.components()
, exposed = []
, spec, rid, i, len;
for (i = 0, len = specs.length; i < len; ++i) {
spec = specs[i];
rid = path.relative(this._ns, spec.id);
if (rid.indexOf('../') == 0) { continue; }
, comp, rid, i, len;
for (i = 0, len = comps.length; i < len; ++i) {
comp = comps[i];
if (typeof filt == 'string') {
// Filter the exposed specs to only those that exist within the namespace
// of the object being created.
rid = path.relative(filt, comp.id);
if (rid.indexOf('../') == 0) { continue; }
}
if (!q) {
exposed.push(new ExposedSpec(rid, spec));
} else if (spec.implements.some(function(i) { return i == q })) {
// The spec implements one of the requested interfaces.
exposed.push(new ExposedSpec(rid, spec));
exposed.push(new ExposedComponent(comp, this));
} else if (comp.implements.some(function(i) { return i == q })) {
// The component implements one of the requested interfaces.
exposed.push(new ExposedComponent(comp, this));
}

@@ -83,0 +93,0 @@ }

// Load modules.
var Spec = require('../spec')
var Component = require('../component')
, util = require('util')
, debug = require('debug')('electrolyte');
, ComponentCreateError = require('../errors/componentcreate');
/**
* A specification using a constructor.
* A component created using a constructor.
*

@@ -14,22 +14,21 @@ * Objects will be created by applying the `new` operator to the constructor

* @constructor
* @param {string} id - The id of the specification.
* @param {object} mod - The module containing the object factory.
* @param {number} hs - The handle of the source from which the spec was loaded.
* @param {string} id - The id of the component.
* @param {object} mod - The module containing the object constructor.
* @param {number} asm - The assembly from which the component was loaded.
* @protected
*/
function ConstructorSpec(id, ctor, hs) {
Spec.call(this, id, ctor, hs);
function ConstructorComponent(id, ctor, hs) {
Component.call(this, id, ctor, hs);
this._ctor = ctor;
}
// Inherit from `Spec`.
util.inherits(ConstructorSpec, Spec);
// Inherit from `Component`.
util.inherits(ConstructorComponent, Component);
/**
* Instantiate an object from the specification.
* Instantiate an object from the component specification.
*
* @private
*/
ConstructorSpec.prototype.instantiate = function() {
debug('instantiate %s', this.id);
ConstructorComponent.prototype.instantiate = function() {
var args = [].slice.call(arguments)

@@ -50,3 +49,3 @@ , ctor = this._ctor;

}
throw new Error("Constructor for object '" + this.id + "' requires too many arguments");
throw new ComponentCreateError("Constructor for object '" + this.id + "' requires too many arguments");
}

@@ -56,2 +55,2 @@

// Expose constructor.
module.exports = ConstructorSpec;
module.exports = ConstructorComponent;
// Load modules.
var Spec = require('../spec')
var Component = require('../component')
, util = require('util')

@@ -8,3 +8,3 @@ , debug = require('debug')('electrolyte');

/**
* A specification using a factory function.
* A component created using a factory function.
*

@@ -15,21 +15,21 @@ * Objects will be created by calling the factory function with any required

* @constructor
* @param {string} id - The id of the specification.
* @param {object} mod - The module containing the object factory.
* @param {number} hs - The handle of the source from which the spec was loaded.
* @param {string} id - The id of the component.
* @param {object} fn - The module containing the object factory.
* @param {number} asm - The assembly from which the component was loaded.
* @protected
*/
function FactorySpec(id, fn, hs) {
Spec.call(this, id, fn, hs);
function FactoryComponent(id, fn, asm) {
Component.call(this, id, fn, asm);
this._fn = fn;
}
// Inherit from `Spec`.
util.inherits(FactorySpec, Spec);
// Inherit from `Component`.
util.inherits(FactoryComponent, Component);
/**
* Instantiate an object from the specification.
* Instantiate an object from the component specification.
*
* @private
*/
FactorySpec.prototype.instantiate = function() {
FactoryComponent.prototype.instantiate = function() {
debug('instantiate %s', this.id);

@@ -39,3 +39,3 @@

id: this.id,
baseNS: this._source.namespace
baseNS: this._assembly.namespace
}

@@ -47,2 +47,2 @@ return this._fn.apply(ctx, arguments);

// Expose constructor.
module.exports = FactorySpec;
module.exports = FactoryComponent;
// Load modules.
var Spec = require('../spec')
var Component = require('../component')
, util = require('util');

@@ -7,3 +7,3 @@

/**
* A specification of an object literal.
* A component that is an object literal.
*

@@ -22,17 +22,17 @@ * A literal is returned directly when created by the IoC container.

* @constructor
* @param {string} id - The id of the specification.
* @param {object} mod - The module containing the object factory.
* @param {number} hs - The handle of the source from which the spec was loaded.
* @param {string} id - The id of the component.
* @param {object} obj - The module containing the literal object.
* @param {number} asm - The assembly from which the component was loaded.
* @protected
*/
function LiteralSpec(id, obj, hs) {
Spec.call(this, id, obj, hs);
function LiteralComponent(id, obj, asm) {
Component.call(this, id, obj, asm);
this._instance = obj;
}
// Inherit from `Spec`.
util.inherits(LiteralSpec, Spec);
// Inherit from `Component`.
util.inherits(LiteralComponent, Component);
// Expose constructor.
module.exports = LiteralSpec;
module.exports = LiteralComponent;

@@ -27,5 +27,6 @@ /**

} catch (ex) {
return;
if (ex.code == 'MODULE_NOT_FOUND') { return; }
throw ex;
}
};
};
{
"name": "electrolyte",
"version": "0.4.0",
"version": "0.5.0",
"description": "Elegant dependency injection for Node.js.",

@@ -5,0 +5,0 @@ "keywords": [

@@ -98,4 +98,3 @@ # Electrolyte

Async components are defined in an identical manner to traditional components
except that the factory function should return a promise. An additional annotation
is used to signify that the dependency should be treated as asynchronous.
except that the factory function should return a promise.

@@ -117,3 +116,2 @@ Let's rewrite the database component above slightly to return a promise.

exports['@async'] = true;
exports['@singleton'] = true;

@@ -134,3 +132,2 @@ exports['@require'] = [ 'settings' ];

exports['@async'] = true;
exports['@singleton'] = true;

@@ -152,7 +149,2 @@ exports['@require'] = [ 'database' ];

- `@async` Indicates that the component returns a promise OR depends on a
component that is computed asynchronously. An async component can only be created
using the `.createAsync` method. Attempting to create an async component using
the standard `.create` will throw an exception.
#### Creating Components

@@ -159,0 +151,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc