From 4ed5ab25a18ec21fb007149a4dd5bdc5d9e287ac Mon Sep 17 00:00:00 2001 From: Dexter Miguel Date: Tue, 8 Dec 2015 00:52:12 -0500 Subject: [PATCH] Release v0.2 --- README.md | 118 ++++++++++++--------- examples/expressjs/config.js | 11 +- examples/expressjs/index.js | 4 +- examples/expressjs/server.js | 6 +- examples/koajs/config.js | 13 +-- examples/koajs/index.js | 4 +- nwire.js | 114 ++++++--------------- package.json | 3 +- spec/fixtures/circular/config.js | 7 -- spec/fixtures/circular/consumer.js | 6 -- spec/fixtures/circular/provider.js | 6 -- spec/fixtures/config.js | 8 -- spec/fixtures/consumer.js | 9 -- spec/fixtures/emptyNeeds.js | 4 - spec/fixtures/provider.js | 6 -- spec/nwire.js | 159 +++++++++++++++++++++++------ spec/packages.js | 33 ------ tests/nwire.js | 13 --- 18 files changed, 247 insertions(+), 277 deletions(-) delete mode 100644 spec/fixtures/circular/config.js delete mode 100644 spec/fixtures/circular/consumer.js delete mode 100644 spec/fixtures/circular/provider.js delete mode 100644 spec/fixtures/config.js delete mode 100644 spec/fixtures/consumer.js delete mode 100644 spec/fixtures/emptyNeeds.js delete mode 100644 spec/fixtures/provider.js delete mode 100644 spec/packages.js delete mode 100644 tests/nwire.js diff --git a/README.md b/README.md index 28d129e..f0f78aa 100644 --- a/README.md +++ b/README.md @@ -17,40 +17,63 @@ Bootstrapping a simple server using Express.js: var wire = require('nwire'); var config = require('./config'); -wire(config, function(err, app){ // Composite root - if (err) throw err; // Something happened while building dependencies - app.packages.server.listen(3000); +wire(config, function(err, app){ // Composite root + if (err) throw err; // Handle errors + app.server.listen(3000); // Start your server }); ``` ```js // server.js -module.exports.needs = ['express']; -module.exports.fn = function($){ +module.exports.needs = ['express']; // What your package needs +module.exports.fn = function($){ // Dependencies are injected through $ var app = $.express(); // Add your routes and configuration here - - return app; + + return app; } ``` ```js // config.js module.exports = { - url: __dirname, // Base URL - packages: { // Packages to be injected - 'server': './server', - 'express': 'express' - } + 'server': require('./server'), // Provide packages + 'express': require('express') } ``` ## Why? -Dependency injection shouldn't be complicated. `nwire.js` encourages loosely coupled functionality and simplifies the process of isolating your code for testing. +Dependency injection shouldn't be complicated. `nwire` encourages loosely coupled functionality and simplifies the process of isolating your code for testing. -## Creating packages +## Creating the container -### Package definition +You must feed `nwire` a configuration object containing the packages you wish to provide for injection. + +Consider this sample configuration object. +```js +// config.js +module.exports = { + 'app': require('./server'), + 'redis-db': require('./db'), + 'express': require('express'), + 'morgan': require('morgan'), + 'passport': require('passport') +}; +``` + +Here we can see that the packages `app`, `redis-db`, `express`, `morgan`, and `passport` are registered and are ready to be injected in packages that need them. `nwire` will then inject all other four packages through the `imports` parameter for packages that contain the properties `fn` and `needs`. + +```js +// server.js +module.exports.needs = ['redis-db', 'express', 'morgan', 'passport']; +module.exports.fn = function(import){ // You can use $ for short + // import now contains four properties each named after the injected packages + var app = import.express(); + import["redis-db"].open(); +} +``` + +## Creating packages Packages are comprised of two properties: `fn` and `needs`. @@ -62,63 +85,64 @@ module.exports.fn = function(imports) { var login = function login(username, password, callback){ // Perform authentication here... } - var logout = function logout(callback) { } - - return { + var logout = function logout(callback) { /*...*/ } + + return { login: login, - logout: logout + logout: logout } } ``` -This package resolves an object that exposes two functions: `login` and `logout`. The resolved object is then injected in other packages that require it through the `needs` property. +This package returns an object that exposes two functions: `login` and `logout`. The returned object is then injected in other packages that require it through the `needs` property. ```js // server.js module.exports.needs = ['auth']; -module.exports.fn = function(imports) { // You can use $ for short - var auth = imports.auth; // The auth module is injected - auth.login('testing', '123', function(err){ +module.exports.fn = function($) { + $.auth.login('testing', '123', function(err, user){ // Handle whether user is authorized }); } ``` -If the `fn` property is not provided, nwire.js will not perform any dependency injection. If the `needs` property is not provided, the `imports` parameter will be empty. +If the `fn` and `needs` properties are not provided, `nwire` will not perform any dependency injection. -### Package discovery +## Running the test suite -In order to perform dependency injection, you must feed nwire.js a configuration object containing the `url` and `packages` properties. +``` +$ npm install +$ npm test +``` -The `url` property allows nwire.js to resolve packages without needing their absolute paths. In most configurations, assigning `__dirname` to the `url` property will do. If this property is not provided, nwire.js will attempt to resolve modules from within its own directory. +## Breaking changes from v0.1 -The `packages` property assigns a name and location for every package. It must contain an object where property names define package names and property values are corresponding locations. +Release `v0.2` did away with string declarations for `config.js` files. This is to allow `nwire` applications to work with bundlers like Browserify and `system.js`. If your `config.js` file looked like this: -Consider this sample configuration object. -```js -// config.js -var path = require('path'); +```javascript module.exports = { - url: path.join(__dirname, 'src'), + url: __dirname, packages: { - 'app': './server', - 'database': './db', - 'express': 'express', - 'morgan': 'morgan', - 'passport': 'passport' + 'app': './app' } -}; +} ``` -Here we can see that the packages `app`, `database`, `express`, `morgan`, and `passport` are registered and are ready to be injected in packages that need them. Assuming that the `app` package looks like the following code, nwire.js will inject all other four packages through the `imports` parameter. +You will now need to use CommonJS (or equivalent) to load your application. -```js -// server.js -module.exports.needs = ['database', 'express', 'morgan', 'passport']; -module.exports.fn = function(import){ - // import now contains four properties each named after the injected packages - var app = import.express(); +```javascript +module.exports = { + 'app': require('./app') } ``` +Also, packages are now properties of the container returned by `nwire` rather than living under a `packages` object. + +```javascript +wire({ /*...config...*/}, function(err, app) { + // app.packages.server.bootstrap(3000); + app.server.bootstrap(3000); +}); +``` + ## Suggestions and questions -If you have any suggestions or questions regarding this project, please open an issue. If you feel that you have a feature that would be useful to add, fork it and open a pull request. \ No newline at end of file +If you have any suggestions or questions regarding this project, please open an issue. If you feel that you have a feature that would be useful to add, fork it and open a pull request. diff --git a/examples/expressjs/config.js b/examples/expressjs/config.js index 16fab67..21e4e89 100644 --- a/examples/expressjs/config.js +++ b/examples/expressjs/config.js @@ -1,8 +1,5 @@ module.exports = { - url: __dirname, - packages: { - 'server': './server', - 'express': 'express', - 'winston': 'winston' - } -} \ No newline at end of file + 'server': require('./server'), + 'express-app': require('express'), + 'winston': require('winston') +} diff --git a/examples/expressjs/index.js b/examples/expressjs/index.js index 680f686..981a2fd 100644 --- a/examples/expressjs/index.js +++ b/examples/expressjs/index.js @@ -2,5 +2,5 @@ var wire = require('../../'); wire(require('./config'), function(err, app) { // Composite root if (err) throw err; - app.packages.server.bootstrap(1337); -}); \ No newline at end of file + app.server.bootstrap(3000); +}); diff --git a/examples/expressjs/server.js b/examples/expressjs/server.js index e0a2ecd..c5f5ab8 100644 --- a/examples/expressjs/server.js +++ b/examples/expressjs/server.js @@ -1,6 +1,6 @@ -module.exports.needs = ['express', 'winston']; +module.exports.needs = ['express-app', 'winston']; module.exports.fn = function($) { - var app = $.express(); + var app = $["express-app"](); var bootstrap = function(port) { return app.listen(port, function() { @@ -11,4 +11,4 @@ module.exports.fn = function($) { return { bootstrap: bootstrap }; -} \ No newline at end of file +} diff --git a/examples/koajs/config.js b/examples/koajs/config.js index 44ea72a..4537610 100644 --- a/examples/koajs/config.js +++ b/examples/koajs/config.js @@ -1,9 +1,6 @@ module.exports = { - url: __dirname, - packages: { - 'server': './server', - 'koa': 'koa', - 'winston': 'winston', - 'http': 'http' - } -} \ No newline at end of file + 'server': require('./server'), + 'koa': require('koa'), + 'winston': require('winston'), + 'http': require('http') +} diff --git a/examples/koajs/index.js b/examples/koajs/index.js index 680f686..981a2fd 100644 --- a/examples/koajs/index.js +++ b/examples/koajs/index.js @@ -2,5 +2,5 @@ var wire = require('../../'); wire(require('./config'), function(err, app) { // Composite root if (err) throw err; - app.packages.server.bootstrap(1337); -}); \ No newline at end of file + app.server.bootstrap(3000); +}); diff --git a/nwire.js b/nwire.js index e1b63a0..b2178ff 100644 --- a/nwire.js +++ b/nwire.js @@ -1,98 +1,50 @@ -module.exports = function nwire(config, callback) { - 'use strict'; +var Container = function(config, callback) { + if (typeof callback !== "function") + throw new Error("Please provide a callback function.") - // Set up a wiring container that injects all necessary components into - // the packages provided - var Wiring = function() { - // Validate configuration object - if (!config || typeof config !== 'object') - throw "Please provide a valid configuration object."; + var $ = {}; // Resolved packages + var decls = config.packages || config; + if (!decls) return callback(null, $); - var path = require('path'); - var base = config.url || ''; - var definitions = config.packages || {}; + var resolve = function(decl) { + if ($[decl]) return $[decl]; - // Validate package definitions - if (!definitions || definitions instanceof Array || - !(definitions instanceof Object)) definitions = {}; + var mod = decls[decl]; - var self = this; - self.packages = {}; + if (mod.fn && typeof mod.fn === "function" && + mod.needs && mod.needs instanceof Array && !mod.ignore) { + var needs = {}; - var load = function(name, skipNeeds) { // Responsible for loading packages - if (typeof(name) !== 'string') throw "Invalid package definition."; + $[decl] = {}; - if (!definitions[name]) return undefined; - - // If a package already exists with the same name, do not attempt to - // overwrite it. Return the existing package. - var loaded = self.packages[name]; - if (loaded) return loaded; - - var pkg, imports = {}; - - var resolve = function(name) { - try { // Try to load a system module first - return require(name) - } catch (e) { - try { // Try to load an NPM module - return require(path.join(base, 'node_modules', name)); - } catch (er) { // Try to load the module through the base directory - try { - return require(path.join(base, name)); - } catch (err) { - return null; - } - } - } - } - - pkg = resolve(definitions[name]); - if (!pkg) return null; - - // If a package is dependent on other packages, it's time to load them. - if (pkg.hasOwnProperty('needs') && pkg.needs instanceof Array) - pkg.needs.forEach(function(dependencyName) { - var definition = resolve(definitions[dependencyName]); - var skip = false; - - if (definitions && definition.needs) - for(var i = 0; i < definition.needs.length; i++){ - var need = definition.needs[i]; - if (need == name) skip = true; - } - - if (!skipNeeds) { - self.packages[dependencyName] = load(dependencyName, skip); - Object.defineProperty(imports, dependencyName, { - get: function(){ - return self.packages[dependencyName]; - } - }); + mod.needs.forEach(function(need) { + Object.defineProperty(needs, need, { + get: function() { + if (!decls[need]) return null; + return $[need] || resolve(need); } }); + }); - // If package implements the fn function then inject the necessary - // packages and replace the package signature with the object it - // returns - if (pkg.hasOwnProperty('fn')) pkg = pkg.fn(imports); - - return pkg; + if (!mod.construct) $[decl] = mod.fn(needs); + else Object.defineProperty($, decl, { + get: function() { + return new mod.fn(needs); + } + }); } - for (var definition in definitions) { - if (!definition) continue; - var fn = load(definition); - if (fn) self.packages[definition] = fn; - } + $[decl] = $[decl] || mod; + return $[decl]; } try { - var app = new Wiring(); - if (!callback) return app; - callback(null, app); + for (var decl in decls) $[decl] = resolve(decl); + callback(null, $); } catch (err) { - if (!callback) throw err; - callback(err); + callback(err, null); } } + + +module.exports = Container; diff --git a/package.json b/package.json index 656d3eb..e67c6ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nwire", - "version": "0.1.4", + "version": "0.2.0", "description": "Simplified dependency injection in Node.js", "main": "nwire.js", "scripts": { @@ -25,7 +25,6 @@ "homepage": "https://github.com/divmgl/nwire#readme", "devDependencies": { "chai": "^3.2.0", - "lodash": "^3.10.1", "mocha": "^2.2.5" } } diff --git a/spec/fixtures/circular/config.js b/spec/fixtures/circular/config.js deleted file mode 100644 index 3ce4bfb..0000000 --- a/spec/fixtures/circular/config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - url: __dirname, - packages: { - 'consumer': './consumer', - 'provider': './provider' - } -}; diff --git a/spec/fixtures/circular/consumer.js b/spec/fixtures/circular/consumer.js deleted file mode 100644 index 0364808..0000000 --- a/spec/fixtures/circular/consumer.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports.needs = ['provider']; -module.exports.fn = function(imports) { - return { - imports: imports - } -} diff --git a/spec/fixtures/circular/provider.js b/spec/fixtures/circular/provider.js deleted file mode 100644 index d2356ac..0000000 --- a/spec/fixtures/circular/provider.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports.needs = ['consumer']; -module.exports.fn = function(imports) { - return { - dummyFn: function(){return null;} - } -} diff --git a/spec/fixtures/config.js b/spec/fixtures/config.js deleted file mode 100644 index f2034aa..0000000 --- a/spec/fixtures/config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - url: __dirname, - packages: { - 'consumer': './consumer', - 'provider': './provider', - 'emptyNeeds': './emptyNeeds' - } -} diff --git a/spec/fixtures/consumer.js b/spec/fixtures/consumer.js deleted file mode 100644 index 3a2b181..0000000 --- a/spec/fixtures/consumer.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports.needs = ['provider']; -module.exports.fn = function(imports) { - return { - imports: imports, - changeProvider: function (){ - imports.provider.member = false; - } - } -} diff --git a/spec/fixtures/emptyNeeds.js b/spec/fixtures/emptyNeeds.js deleted file mode 100644 index 6764ec9..0000000 --- a/spec/fixtures/emptyNeeds.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports.needs = []; -module.exports.fn = function(){ - return {}; -} diff --git a/spec/fixtures/provider.js b/spec/fixtures/provider.js deleted file mode 100644 index bbe4038..0000000 --- a/spec/fixtures/provider.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports.fn = function(imports) { - return { - dummyFn: function(){return null;}, - member: true - } -} diff --git a/spec/nwire.js b/spec/nwire.js index 60e7cdf..bb11686 100644 --- a/spec/nwire.js +++ b/spec/nwire.js @@ -1,51 +1,144 @@ -require('chai').should(); -var wire = require('..'); +var expect = require('chai').expect; +var wire = require('../nwire') -describe('nwire', function(){ - it('should return an object', function(){ - var app = wire({}); - app.should.be.a('object'); - }) +describe('nwire', function() { + it('should callback with object', function(done){ + wire({}, function(err, app) { + expect(app).to.not.equal(null); + done(); + }); + }); + + it('should return a simple package', function(done) { + wire({ 'prov': { value: 123 } }, function(err, app) { + expect(app.prov.value).to.equal(123); + done(); + }); + }); + + it('should inject a package', function(done) { + wire({ + 'prov': { value: 123 }, + 'cons': { + needs: ['prov'], + fn: function($) { return { consumedValue: $.prov.value }; } + } + }, function(err, app) { + expect(app.cons.consumedValue).to.equal(123); + done(); + }); + }); - it('should return an object on callback', function(done){ - wire({}, function(err, app){ - app.should.be.a('object'); + it('should not crash on circular dependency', function(done) { + wire({ + 'provc': { + needs: ["consc"], + fn: function ($) { return { value: $.consc.consumedValue || 123 }; } + }, + 'consc': { + needs: ["provc"], + fn: function ($) { return { consumedValue: $.provc.value }; } + } + }, function(err, app){ + expect(app.provc.value).to.equal(123); done(); }); }); - it('throws an error when configuration undefined', function(){ - (function(){ - wire(); - }).should.throw(); + it('should not be able to replace providers', function(done) { + wire({ + 'prov': { + value: 123 + }, + 'cons': { + needs: ["prov"], + fn: function($) { $.prov = { value: 456 }; } + } + }, function(err, app) { + expect(app.prov.value).to.equal(123); + done(); + }); }); - it('throws an error when configuration not an object', function(){ - (function(){ - wire(String()); - }).should.throw(); + it('should ignore wiring when ignore flag is on', function(done) { + wire({ + 'prov': { + needs: [], + fn: function($) { return { value: 123 } }, + ignore: true + } + }, function(err, app) { + expect(app.prov.fn().value).to.equal(123); + done(); + }) }); - it('does not crash on invalid package', function() { - (function(){ - wire({ packages: { 'asdf' : './asdf' }}); - }).should.not.throw(); + it('returns a new instance every time', function(done) { + wire({ + 'prov': { + needs: [], + fn: function($) { this.value = 123; }, + construct: true + } + }, function (err, app) { + app.prov.value = 456; + expect(app.prov.value).to.equal(123); + done(); + }) }); - describe('application', function(){ - var configurationFixture = require('./fixtures/config'); - var circularConfigurationFixture = require('./fixtures/circular/config'); + it('handles errors gracefully', function(done) { + wire({ + 'prov': { + throws: function() { + throw new Error("Stuff happened"); + } + }, + 'cons': { + needs: ['prov'], + fn: function($){ + $.prov.throws(); + } + } + }, function (err, app){ + expect(err).to.not.equal(null); + done(); + }) + }); - it('should be able to access packages', function(){ - var app = wire(configurationFixture); - app.packages.consumer.should.be.a('object'); - app.packages.provider.should.be.a('object'); + it('handles unknown packages gracefully', function(done) { + wire({ + 'cons': { + needs: ['prov'], + fn: function($) { + return { + prov: $.prov + }; + } + } + }, function (err, app){ + expect(err).to.equal(null); + expect(app).to.not.equal(null); + expect(app.cons.prov).to.equal(null); + expect(app.cons).to.not.equal(null); + done(); }); + }); - it('does not crash on circular dependency', function(){ - var app = wire(configurationFixture); - app.packages.consumer.should.be.a('object'); - app.packages.provider.should.be.a('object'); + it('handles odd characters', function(done) { + wire({ + 'asdf!@#$': "hi", + 'cons': { + needs: ['asdf!@#$'], + fn: function($){ + return { + value: $["asdf!@#$"] + } + } + } + }, function(err, app){ + expect(app.cons.value).to.equal("hi"); + done(); }); }); }); diff --git a/spec/packages.js b/spec/packages.js deleted file mode 100644 index 0b6d77e..0000000 --- a/spec/packages.js +++ /dev/null @@ -1,33 +0,0 @@ -require('chai').should(); - -var wire = require('..'); -var configurationFixture = require('./fixtures/config'); -var circularConfigurationFixture = require('./fixtures/circular/config'); - -describe('packages', function(){ - var app; - - beforeEach(function(){ - app = wire(configurationFixture); - }); - - it('should not crash on empty needs', function(){ - app.packages.emptyNeeds.should.be.a('object'); - }); - - describe('consumers', function(){ - it('should be able to access imports', function(){ - app.packages.consumer.imports.provider.member.should.equal(true); - }); - - it('should be able to modify imports', function(){ - app.packages.consumer.changeProvider(); - app.packages.provider.member.should.equal(false); - }); - }) - describe('providers', function(){ - it('that are providers should expose properties', function() { - app.packages.provider.dummyFn.should.be.a('function'); - }); - }); -}); diff --git a/tests/nwire.js b/tests/nwire.js deleted file mode 100644 index 62abaa6..0000000 --- a/tests/nwire.js +++ /dev/null @@ -1,13 +0,0 @@ -var expect = require('chai').expect; -var _ = require('lodash'); - -var wire = require('..'); - -var configurationFixture = require('./fixtures/config'); -var circularConfigurationFixture = require('./fixtures/circular/config'); - -describe('nwire', function(){ - it('should return an object on callback', function(){ - - }) -});