Comparing version 0.0.6 to 0.1.0
@@ -0,3 +1,14 @@ | ||
## 0.1.X | ||
### 0.1.0 | ||
* #7 - Add better error reporting - include all unresolvable keys | ||
* #6 - Export canResolve to allow consumers to check in advance | ||
* Backfill dependencies from NPM modules when available | ||
* Add support for bulk registration | ||
## 0.0.X | ||
### 0.0.6 | ||
* Fix bug where things like sinon.stub cause dependency check to throw an exception | ||
* When a function's dependencies cannot be resolved, return the function rather than throw an exception | ||
* When a function's dependencies cannot be resolved, return the function rather than throw an exception |
@@ -1,15 +0,26 @@ | ||
var gulp = require( 'gulp' ), | ||
mocha = require( 'gulp-mocha' ); | ||
var gulp = require( 'gulp' ); | ||
var bg = require( 'biggulp' )( gulp ); | ||
gulp.task( 'test', function() { | ||
gulp.src( './spec/*.spec.js' ) | ||
.pipe( mocha( { reporter: 'spec' } ) ) | ||
.on( 'error', function( err ) { console.log( err.stack ); } ); | ||
gulp.task( 'coverage', bg.withCoverage() ); | ||
gulp.task( 'coverage-watch', function() { | ||
bg.watch( [ 'coverage' ] ); | ||
} ); | ||
gulp.task( 'watch', function() { | ||
gulp.watch( [ './src/**', './spec/**' ], [ 'test' ] ); | ||
gulp.task( 'show-coverage', bg.showCoverage() ); | ||
gulp.task( 'continuous-specs', function() { | ||
return bg.test(); | ||
} ); | ||
gulp.task( 'default', [ 'test', 'watch' ], function() { | ||
} ); | ||
gulp.task( 'specs-watch', function() { | ||
bg.watch( [ 'continuous-specs' ] ); | ||
} ); | ||
gulp.task( 'test-and-exit', function() { | ||
return bg.testOnce(); | ||
} ); | ||
gulp.task( 'default', [ 'coverage', 'coverage-watch' ], function() {} ); | ||
gulp.task( 'specs', [ 'continuous-specs', 'specs-watch' ], function() {} ); | ||
gulp.task( 'test', [ 'test-and-exit' ] ); |
{ | ||
"name": "fount", | ||
"version": "0.0.6", | ||
"version": "0.1.0", | ||
"description": "A source from which dependencies flow", | ||
@@ -26,12 +26,14 @@ "main": "./src/index.js", | ||
"dependencies": { | ||
"debug": "^1.0.4", | ||
"lodash": "^2.4.1", | ||
"when": "^3.3.1" | ||
"debug": "~2.1.3", | ||
"lodash": "~3.7.0", | ||
"when": "~3.7.2" | ||
}, | ||
"devDependencies": { | ||
"gulp": "^3.8.6", | ||
"gulp-mocha": "^0.5.1", | ||
"should": "^4.0.4", | ||
"sinon": "^1.12.1" | ||
"biggulp": "~0.0.2", | ||
"chai": "~2.1.1", | ||
"chai-as-promised": "~4.3.0", | ||
"gulp": "~3.8.11", | ||
"postal": "^1.0.2", | ||
"sinon": "~1.14.1" | ||
} | ||
} |
@@ -13,3 +13,3 @@ # Fount | ||
## A note about style and safety | ||
Fount supports two styles of identifying dependency: string array (like AMD) and by argument names (like Angular). | ||
Fount supports two styles of identifying dependency: string array (like AMD) and by argument names (like Angular). | ||
@@ -42,3 +42,3 @@ Example: | ||
```javascript | ||
fount( 'myContainer' ).resolve( 'myKey' ).then( function( value ) { | ||
fount( 'myContainer' ).resolve( 'myKey' ).then( function( value ) { | ||
// do something cool | ||
@@ -48,3 +48,3 @@ } ); | ||
// same as above, but terser | ||
fount.resolve( 'myContainer.myKey' ).then( function( value ) { | ||
fount.resolve( 'myContainer.myKey' ).then( function( value ) { | ||
// do something cool | ||
@@ -73,3 +73,3 @@ } ); | ||
## Registering | ||
## Registering | ||
Registering is simple - provide a string name and then supply either a value, function or promise. See each section for more detail. | ||
@@ -148,2 +148,10 @@ | ||
You can also check in advance whether or not fount is able to resolve a dependency using `canResolve`: | ||
```javascript | ||
fount.canResolve( 'key1' ); | ||
fount.canResolve( [ 'key1', 'key2' ] ); | ||
``` | ||
## Injecting | ||
@@ -161,9 +169,51 @@ Injecting is how you get fount to invoke a function on your behalf with resolved dependencies. If you're familiar with AMD, it's somewhat similar to how define works. | ||
fount.inject( [ 'one.a', 'two.b' ], function( a, b ) { ... } ); | ||
// alternate support for multiple containers | ||
fount.inject( function( one_a, two_b ) { ... } ); | ||
``` | ||
## Configuration | ||
Configuration of multiple containers, keys and values can be accomplished via a configuration hash passed to fount. The format of the hash is as follows: | ||
```javascript | ||
{ | ||
[containerName]: { | ||
[keyName]: [value], | ||
[keyName]: { | ||
[scope]: [value] | ||
} | ||
} | ||
} | ||
``` | ||
__example__ | ||
```javascript | ||
fount( { | ||
default: { | ||
a: 1, | ||
b: function() { | ||
return 2; | ||
} | ||
}, | ||
other: { | ||
c: { scoped: 3 }, | ||
d: { scoped: function() { | ||
return 4; | ||
} | ||
}, | ||
e: { static: 5 }, | ||
f: { factory: function() { | ||
return 6; | ||
} } | ||
} | ||
} ); | ||
``` | ||
## Diagnostic | ||
Right now this is pretty weak, but if you call `log`, Fount will dump the containers and scopes out so you can see what keys are present. Got ideas for more useful ways to troubleshoot? I'd love a PR :smile:! | ||
## Tests & CI Mode | ||
## Things to do soon | ||
* Good error handling - returning clear error messages when a resolution/injection fails | ||
* Running `gulp` starts both the `test` and `watch` tasks, so you'll see the tests re-run any time you save a file under `src/` or `spec/`. | ||
* Running `gulp coverage` will run istanbul and create a `coverage/` folder. | ||
* Running `gulp show-coverage` will run istanbul and open the browser-based coverage report. |
304
src/index.js
@@ -6,8 +6,27 @@ var _ = require( 'lodash' ); | ||
var debug = require( 'debug' )( 'fount' ); | ||
var util = require( 'util' ); | ||
var path = require( 'path' ); | ||
var fs = require( 'fs' ); | ||
var containers = {}; | ||
var parent; | ||
var getDisplay = process.env.DEBUG ? displayDependency : _.noop; | ||
function backfillMissingDependency( name, containerName ) { | ||
var mod = getLoadedModule( name ) || getModuleFromInstalls( name ); | ||
if ( mod ) { | ||
var lifecycle = _.isFunction( mod ) ? 'factory' : 'static'; | ||
register( containerName, name, mod, lifecycle ); | ||
} else { | ||
debug( 'Could not backfill dependency %s in in container %s', name, containerName ); | ||
} | ||
return mod; | ||
} | ||
function canResolve( containerName, dependencies, scopeName ) { | ||
return getMissingDependencies( containerName, dependencies, scopeName ).length === 0; | ||
} | ||
function checkDependencies( fn, dependencies ) { | ||
var fnString = fn.toString(); | ||
if( /[(][^)]*[)]/.test( fnString ) ) { | ||
if ( /[(][^)]*[)]/.test( fnString ) ) { | ||
return ( _.isFunction( fn ) && !dependencies.length ) ? | ||
@@ -21,2 +40,30 @@ trim( /[(]([^)]*)[)]/.exec( fnString )[ 1 ].split( ',' ) ) : | ||
function configure( config ) { | ||
_.each( config, function( val, containerName ) { | ||
_.each( val, function( opt, key ) { | ||
var dependency = opt; | ||
var lifecycle; | ||
if ( _.isObject( opt ) ) { | ||
if ( opt.scoped ) { | ||
lifecycle = 'scoped'; | ||
dependency = opt.scoped; | ||
} else if ( opt.static ) { | ||
lifecycle = 'static'; | ||
dependency = opt.static; | ||
} else if ( opt.factory ) { | ||
lifecycle = 'factory'; | ||
dependency = opt.factory; | ||
} else { | ||
dependency = undefined; | ||
} | ||
} | ||
if ( !dependency ) { | ||
dependency = opt; | ||
lifecycle = _.isFunction( opt ) ? 'factory' : 'static'; | ||
} | ||
register( containerName, key, dependency, lifecycle ); | ||
} ); | ||
} ); | ||
} | ||
function container( name ) { | ||
@@ -26,2 +73,54 @@ return ( containers[ name ] = containers[ name ] || { scopes: {} } ); | ||
function displayDependency( obj ) { | ||
if ( _.isFunction( obj ) ) { | ||
return obj.name || 'anonymous function'; | ||
} else if ( _.isString( obj ) || _.isNumber( obj ) || _.isArray( obj ) || _.isDate( obj ) ) { | ||
return obj; | ||
} else if ( _.isPlainObject( obj ) ) { | ||
return '[Object Literal]'; | ||
} else { | ||
return obj.constructor.name || '[Object]'; | ||
} | ||
} | ||
function findParent( mod ) { | ||
if ( parent ) { | ||
return parent; | ||
} | ||
if ( mod.parent ) { | ||
return findParent( mod.parent ); | ||
} else { | ||
parent = mod; | ||
return mod; | ||
} | ||
} | ||
function getLoadedModule( name ) { | ||
var parent = findParent( module ); | ||
var regex = new RegExp( name ); | ||
var candidate = _.find( parent.children, function( child ) { | ||
return regex.test( child.id ) && _.contains( child.id.split( '/' ), name ); | ||
} ); | ||
if ( candidate ) { | ||
candidate.exports.__npm = candidate.exports.__npm || true; | ||
return candidate.exports; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
function getModuleFromInstalls( name ) { | ||
var parent = findParent( module ); | ||
var installPath = _.find( parent.paths, function( p ) { | ||
var modPath = path.join( p, name ); | ||
return fs.existsSync( modPath ); | ||
} ); | ||
var mod; | ||
if ( installPath ) { | ||
mod = require( path.join( installPath, name ) ); | ||
mod.__npm = mod.__npm || true; | ||
} | ||
return mod; | ||
} | ||
function getArgs( obj ) { | ||
@@ -31,5 +130,40 @@ return Array.prototype.slice.call( obj ); | ||
function getMissingDependencies( containerName, dependencies, scopeName ) { | ||
scopeName = scopeName || 'default'; | ||
containerName = containerName || 'default'; | ||
dependencies = _.isArray( dependencies ) ? dependencies : [ dependencies ]; | ||
return _.reduce( dependencies, function( acc, key ) { | ||
if ( _.isArray( key ) ) { | ||
var ctr = container( containerName ); | ||
key.forEach( function( k ) { | ||
var originalKey = k; | ||
var ctrName = containerName; | ||
var parts = k.split( /[._]/ ); | ||
if ( parts.length > 1 ) { | ||
ctr = container( parts[ 0 ] ); | ||
ctrName = parts[ 0 ]; | ||
k = parts[ 1 ]; | ||
} | ||
if ( !ctr[ k ] && !backfillMissingDependency( key, ctrName ) ) { | ||
acc.push( originalKey ); | ||
} | ||
} ); | ||
} else { | ||
var originalKey = key; | ||
var parts = key.split( /[._]/ ); | ||
if ( parts.length > 1 ) { | ||
containerName = parts[ 0 ]; | ||
key = parts[ 1 ]; | ||
} | ||
if ( !container( containerName )[ key ] && !backfillMissingDependency( key, containerName ) ) { | ||
acc.push( originalKey ); | ||
} | ||
} | ||
return acc; | ||
}, [] ); | ||
} | ||
function inject( containerName, dependencies, fn, scopeName ) { | ||
scopeName = scopeName || 'default'; | ||
if( _.isFunction( dependencies ) ) { | ||
if ( _.isFunction( dependencies ) ) { | ||
scopeName = fn; | ||
@@ -40,5 +174,11 @@ fn = dependencies; | ||
dependencies = checkDependencies( fn, dependencies ); | ||
var missingKeys = getMissingDependencies( containerName, dependencies, scopeName ); | ||
if ( missingKeys.length > 0 ) { | ||
throw new Error( util.format( 'Fount could not resolve the following dependencies: %s', missingKeys.join( ', ' ) ) ); | ||
} | ||
var args = dependencies.map( function( key ) { | ||
var parts = key.split( '.' ); | ||
if( parts.length > 1 ) { | ||
var parts = key.split( /[._]/ ); | ||
if ( parts.length > 1 ) { | ||
containerName = parts[ 0 ]; | ||
@@ -53,2 +193,3 @@ key = parts[ 1 ]; | ||
function purge( containerName ) { | ||
debug( 'purging container %s', containerName ); | ||
containers[ containerName ] = { scopes: {} }; | ||
@@ -58,2 +199,3 @@ } | ||
function purgeAll() { | ||
debug( 'purging all containers' ); | ||
containers = { scopes: {} }; | ||
@@ -63,2 +205,3 @@ } | ||
function purgeScope( containerName, scopeName ) { | ||
debug( 'purging container %s, scope %s', containerName, scopeName ); | ||
delete container( containerName ).scopes[ scopeName ]; | ||
@@ -71,3 +214,3 @@ } | ||
var key = args[ 1 ]; | ||
var parts = key.split( '.' ); | ||
var parts = key.split( /[._]/ ); | ||
var dependencies = _.isArray( args[ 2 ] ) ? args[ 2 ] : []; | ||
@@ -77,8 +220,9 @@ var fn = dependencies.length ? args[ 3 ] : args[ 2 ]; | ||
if( parts.length > 1 ) { | ||
if ( parts.length > 1 ) { | ||
containerName = parts[ 0 ]; | ||
key = parts[ 1 ]; | ||
} | ||
if( _.isFunction( fn ) ) { | ||
if ( _.isFunction( fn ) ) { | ||
dependencies = checkDependencies( fn, dependencies ); | ||
@@ -88,3 +232,4 @@ } else { | ||
} | ||
debug( 'Registering key "%s" for container "%s" with %s lifecycle: %s', key, containerName, lifecycle, dependencies, fn ); | ||
debug( 'Registering key "%s" for container "%s" with %s lifecycle: %s', | ||
key, containerName, lifecycle, getDisplay( fn ) ); | ||
var promise = wrappers[ lifecycle ]( containerName, key, fn, dependencies ); | ||
@@ -94,38 +239,15 @@ container( containerName )[ key ] = promise; | ||
function canResolve( containerName, dependencies, scopeName ) { | ||
scopeName = scopeName || 'default'; | ||
return _.all( dependencies, function( key ) { | ||
if( _.isArray( key ) ) { | ||
var ctr = container( containerName ); | ||
var vals = []; | ||
key.forEach( function( k ) { | ||
var originalKey = k; | ||
var parts = k.split( '.' ); | ||
if( parts.length > 1 ) { | ||
ctr = container( parts[ 0 ] ); | ||
k = parts[ 1 ]; | ||
} | ||
vals.push( ctr[ k ] ); | ||
} ); | ||
return _.all( vals ); | ||
} else { | ||
var parts = key.split( '.' ); | ||
if( parts.length > 1 ) { | ||
containerName = parts[ 0 ]; | ||
key = parts[ 1 ]; | ||
} | ||
return container( containerName )[ key ]; | ||
} | ||
} ); | ||
} | ||
function resolve( containerName, key, scopeName ) { | ||
scopeName = scopeName || 'default'; | ||
if( _.isArray( key ) ) { | ||
var missingKeys = getMissingDependencies( containerName, key, scopeName ); | ||
if ( missingKeys.length > 0 ) { | ||
throw new Error( util.format( 'Fount could not resolve the following dependencies: %s', missingKeys.join( ', ' ) ) ); | ||
} | ||
if ( _.isArray( key ) ) { | ||
var hash = {}; | ||
var ctr = container( containerName ); | ||
var hash = {}; | ||
key.forEach( function( k ) { | ||
var originalKey = k; | ||
var parts = k.split( '.' ); | ||
if( parts.length > 1 ) { | ||
var parts = k.split( /[._]/ ); | ||
if ( parts.length > 1 ) { | ||
ctr = container( parts[ 0 ] ); | ||
@@ -138,16 +260,27 @@ k = parts[ 1 ]; | ||
} else { | ||
var parts = key.split( '.' ); | ||
if( parts.length > 1 ) { | ||
var parts = key.split( /[._]/ ); | ||
if ( parts.length > 1 ) { | ||
containerName = parts[ 0 ]; | ||
key = parts[ 1 ]; | ||
} | ||
return container( containerName )[ key ]( scopeName ); | ||
return container( containerName )[ key ]( scopeName ); | ||
} | ||
} | ||
function trimString( str ) { return str.trim(); } | ||
function trim( list ) { | ||
return ( list && list.length ) ? _.filter( list.map( trimString ) ) : []; | ||
function scope( containerName, name ) { | ||
var ctr = container( containerName ); | ||
return ( ctr.scopes[ name ] = ctr.scopes[ name ] || {} ); | ||
} | ||
function setModule( mod ) { | ||
parent = mod; | ||
} | ||
function trimString( str ) { | ||
return str.trim(); | ||
} | ||
function trim( list ) { | ||
return ( list && list.length ) ? _.filter( list.map( trimString ) ) : []; | ||
} | ||
function type( obj ) { | ||
@@ -157,23 +290,23 @@ return Object.prototype.toString.call( obj ); | ||
function scope( containerName, name ) { | ||
var ctr = container( containerName ); | ||
return ( ctr.scopes[ name ] = ctr.scopes[ name ] || {} ); | ||
} | ||
var wrappers = { | ||
factory: function ( containerName, key, value, dependencies ) { | ||
factory: function( containerName, key, value, dependencies ) { | ||
return function( scopeName ) { | ||
if( _.isFunction( value ) && dependencies && canResolve( containerName, dependencies, scopeName ) ) { | ||
var args = dependencies.map( function( key ) { | ||
return resolve( containerName, key, scopeName ); | ||
} ); | ||
return whenFn.apply( value, args ); | ||
} else { | ||
return when.promise( function ( resolve ) { | ||
resolve( value ); | ||
} ); | ||
if ( _.isFunction( value ) ) { | ||
var dependencyContainer = containerName; | ||
if ( value.__npm ) { | ||
dependencyContainer = key; | ||
} | ||
if ( dependencies && canResolve( dependencyContainer, dependencies, scopeName ) ) { | ||
var args = dependencies.map( function( key ) { | ||
return resolve( dependencyContainer, key, scopeName ); | ||
} ); | ||
return whenFn.apply( value, args ); | ||
} | ||
} | ||
return when.promise( function( resolve ) { | ||
resolve( value ); | ||
} ); | ||
}; | ||
}, | ||
scoped: function ( containerName, key, value, dependencies ) { | ||
scoped: function( containerName, key, value, dependencies ) { | ||
return function( scopeName ) { | ||
@@ -185,6 +318,5 @@ var cache = scope( containerName, scopeName ); | ||
}; | ||
if( cache[ key ] ) { | ||
if ( cache[ key ] ) { | ||
return cache[ key ]; | ||
} | ||
else if( _.isFunction( value ) && dependencies && canResolve( containerName, dependencies, scopeName ) ) { | ||
} else if ( _.isFunction( value ) && dependencies && canResolve( containerName, dependencies, scopeName ) ) { | ||
var args = dependencies.map( function( key ) { | ||
@@ -195,4 +327,4 @@ return resolve( containerName, key, scopeName ); | ||
} else { | ||
return when.promise( function ( resolve ) { | ||
if( when.isPromiseLike( value ) ) { | ||
return when.promise( function( resolve ) { | ||
if ( when.isPromiseLike( value ) ) { | ||
value.then( store ); | ||
@@ -209,3 +341,3 @@ } else { | ||
var promise; | ||
if( _.isFunction( value ) && dependencies && canResolve( containerName, dependencies ) ) { | ||
if ( _.isFunction( value ) && dependencies && canResolve( containerName, dependencies ) ) { | ||
var args = dependencies.map( function( key ) { | ||
@@ -218,3 +350,5 @@ return resolve( containerName, key ); | ||
} | ||
return function() { return promise; }; | ||
return function() { | ||
return promise; | ||
}; | ||
} | ||
@@ -224,19 +358,29 @@ }; | ||
var fount = function( containerName ) { | ||
return { | ||
inject: inject.bind( undefined, containerName ), | ||
register: register.bind( undefined, containerName ), | ||
resolve: resolve.bind( undefined, containerName ), | ||
purge: purgeScope.bind( undefined, containerName ), | ||
purgeScope: purgeScope.bind( undefined, containerName ) | ||
}; | ||
if ( _.isObject( containerName ) ) { | ||
configure( containerName ); | ||
} else { | ||
return { | ||
canResolve: canResolve.bind( undefined, containerName ), | ||
inject: inject.bind( undefined, containerName ), | ||
register: register.bind( undefined, containerName ), | ||
resolve: resolve.bind( undefined, containerName ), | ||
purge: purgeScope.bind( undefined, containerName ), | ||
purgeScope: purgeScope.bind( undefined, containerName ) | ||
}; | ||
} | ||
}; | ||
fount.canResolve = canResolve.bind( undefined, 'default' ); | ||
fount.inject = inject.bind( undefined, 'default' ); | ||
fount.register = register.bind( undefined, 'default' ); | ||
fount.resolve = resolve.bind( undefined, 'default' ); | ||
fount.purge = purgeScope.bind( undefined, 'default' ); | ||
fount.purge = purge.bind( undefined ); | ||
fount.purgeAll = purgeAll; | ||
fount.purgeScope = purgeScope.bind( undefined, 'default' ); | ||
fount.log = function() { console.log( containers ); }; | ||
fount.setModule = setModule; | ||
module.exports = fount; | ||
fount.log = function() { | ||
console.log( containers ); | ||
}; | ||
module.exports = fount; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
214
21100
6
7
355
3
1
+ Addeddebug@2.1.3(transitive)
+ Addedlodash@3.7.0(transitive)
+ Addedms@0.7.0(transitive)
- Removeddebug@1.0.5(transitive)
- Removedlodash@2.4.2(transitive)
- Removedms@2.0.0(transitive)
Updateddebug@~2.1.3
Updatedlodash@~3.7.0
Updatedwhen@~3.7.2