gearbox
v0.0.7
Published
Another Dependency Injection container for Node.js
Downloads
12
Readme
Gearbox
Gearbox is a Node.js framework for assembling pretty-much anything from an extensible set of components. Within Gearbox, these components are known as Gears. There's a growing library of pre-canned Gears available on NPM and a Yeoman Generator to help roll your own.
Contents
Gearbox Module
Installation
$ npm install gearbox
Gear Prelude
Gearbox delivers functionality via configurable components referred to as 'Gears'.
As explained later, Gears are little-more than Node.js modules that provide some bounded functionality.
Gears can offer something as small as a textBox for use in a web form, through to something as big as a web server.
Though a few internal Gears come bundled with the main Gearbox module (e.g. a logger
), a one-NPM-package-per-Gear approach is employed for everything else.
- Gears can inherit properties from other Gears
- Gears can have other Gear instances injected into them
- Gears can be arranged into tree structures
Basic usage
var Gearbox = require('gearbox');
var box = new Gearbox(); // Create a new Gearbox instance
// Get a 'car' gear
box.get('car', {maxSpeed: 128}, function(err, car) {
car.parkingBrake(false);
car.accelerateToSpeed(30);
});
Configuration
When creating a new Gearbox instance, it's possible to provide an optional configuration object:
var box = new Gearbox(config);
###gearConfig
- Required: No
- Type: Object
#####Description This is one of several ways to configure Gears - it's especially useful for configuring Gearbox-wide singleton-Gears such as a logger or a database connection.
- The keys of the configuration-object should be a Gear name, and the value should be an object that will be passed as config when the Gear is instantiated.
- Providing configuration for a Gear does not imply it will ever get instantiated... only when a new Gear instance is required will this config be consulted.
Example
var Gearbox = require('gearbox');
var box = new Gearbox(
{
gearConfig: {
logger: {
level: 'debug'
},
database: {
dataSource: {
connector: 'postgresql',
host: 'localhost',
port: 5432,
database: 'gearbox',
username: 'gearbox',
password: 'gearbox'
}
}
}
}
);
- Here, when a
logger
Gear is first required, it will be configured with alevel
ofdebug
, and if adatabase
Gear is required, it will be instantiated with adataSource
object. - Gears which are designed to behave like singletons are usually configured in this way.
###modulePrefixes
- Required: No
- Default:
['gear']
- Type: Array of Strings
#####Description
If all fails, Gearbox will use Node's internal machinery to require
a Gear module (as described here).
By default the requested Gear name will be turned into a module name with a prepended gear-
, but if you're developing your own Gears you may prefer to use your own prefixes.
This can be achieved by providing alternative modulePrefixes
, Gearbox will iterate over the provided prefixes in order, looking for a resolvable module.
#####Example
{
modulePrefixes: ['funky-startup', 'gear']
}
###maxDepth
- Required: No
- Default:
100
- Type: Integer
#####Description
This places a limit on the recursive depth things will be allowed to descend when constructing dependencies. Pretty internal - probably doesn't need changing.
###maxConstructors
- Required: No
- Default:
1500
- Type: Integer
#####Description
The maximum number of cached Gear-constructors that will be held in memory at any one time.
###maxSingletons
- Required: No
- Default:
1500
- Type: Integer
#####Description
The maximum number of cached singleton instances that will be held in memory at any one time.
###maxModules
- Required: No
- Default:
1500
- Type: Integer
#####Description
The maximum number of 'loaded' Gear modules that will be held in memory at any one time.
###path
- Required: No
- Default:
[]
- Type: Array of Strings
#####Description
###registeredGearDirs
- Required: No
- Default:
{}
- Type: Object
#####Description
Gearbox API
###registerGearPath(gearName, absDir, relDir)
Explicitly signposts the directory where the named Gear can be found. Gears that are registered in this way will resolve to the specified directory ahead of employing less specific discovery techniques.
| Parameter | Required? | Description
| --------- | ----------| -----------
| gearName
| Yes | The name of the Gear to register a directory for.
| absDir
| Yes | An absolute directory path where the Gear module can be found. Can be used in conjunction with relDir
.
| relDir
| No | An optional relative path to be taken from absDir
.
#####Example
var Gearbox = require('gearbox');
var box = new Gearbox();
box.registerGearPath('fancyGear', __dirname, '../gears');
###appendToPath(relDir, absPath)
If a Gear can't be found (because it's not been registered via registerGearPath
) then Gearbox will use an internal path looking for Gears.
The appendToPath
method simply adds another directory to this internal path.
- The directory added to the path can either signpost a Gear module directory (consider using the more explicit
registerGearPath
) or perhaps more usefully: a directory which contains many Gear module directories. In this instance, all the Gears in the containing directory are 'discoverable'.
| Parameter | Required? | Description
| --------- | ----------| -----------
| dir
| Yes | Either a relative or absolute directory path.
| absDir
| No | If provided it's assumed the contents of dir
is a relative directory path, and will be resolved from absDir
.
#####Example
var Gearbox = require('gearbox');
var box = new Gearbox();
box.appendToPath('./dev-gears', __dirname);
###discover(callback)
Once the internal path has all the required directories appended to it (via appendToPath
) then calling discover
will trawl the various directories and add any Gears it finds.
| Parameter | Required? | Description
| --------- | ----------| -----------
| callback
| Yes | Simple function(err)
callback.
#####Example
var Gearbox = require('gearbox');
var box = new Gearbox();
box.appendToPath('./test-gears', __dirname);
box.discover (
function (err) {
// Some filesystem fail
}
);
###get(gearName, instanceConfig, callback)
Gets a fully instantiated Gear instance, with all dependencies injected and any Gear-inheritance taken care of.
- If the Gear behaves like a singletons (e.g. a logger) the single Gearbox-wide instance will be returned - otherwise a fresh instance will be created.
| Parameter | Required? | Description
| --------- | ----------| -----------
| gearName
| Yes | Name of the Gear to get
| instanceConfig
| No | If the Gear is not a singleton, it's possible to specify config for the fresh instance here. This is a simple config object.
| callback
| Yes | Of the form function (err, gear)
where if successful gear
is the instantiated Gear instance.
#####Example
var Gearbox = require('gearbox');
var box = new Gearbox(); // Create a new Gearbox instance
// Get a 'car' gear
box.get('car', {maxSpeed: 128}, function(err, car) {
if (err) {
// Handle it.
}
else {
car.parkingBrake(false);
car.accelerateToSpeed(30);
}
});
Gears
Overview
Loading Gears
Gears are nothing more than Node.js modules which export an object with certain properties.
If you encounter a GearError: [unresolvedGear] Can't find module for name 'someMadeUpGearName'
then Gearbox has failed to require
a suitable Gear module.
Troubleshooting
For Gearbox to successfully load a Gear, at least one of the following must be true:
- The Gear is one of the few internal Gears.
- The directory containing the Gear's module has been registered with
gearbox.registerGearPath()
. - The directory containing the Gear's module is either directly in Gearbox's internal path, or a direct sub-directory from a directory specified in Gearbox's internal path.
- Either use
gearbox.appendToPath()
to add new directories to Gearbox's internal path - Or specify absolute directories as part of the Gearbox config object.#
- Regardless of which path building method is used, always be sure to finish by calling
gearbox.discover()
.
- Either use
- If Gearbox still has no joy in finding a module, then it will rely on Node's own module loading machinery. The specified Gear name will be converted into a module name in the following way:
- First the Gear name is dasherized.
- Then at least one prefix is prepended - Gearbox will always try the
gear-
prefix.- It's possible to provide alternative prefixes via the
modulePrefixes
property in Gearbox's configuration object.
- It's possible to provide alternative prefixes via the
- So looking for a Gear named
webServer
will ultimately result in Gearbox tryingrequire('gear-web-server')
.
Export object
There's not much to a Gear... just a simple Node.js module that exports a single object. For example:
module.exports = {
name: 'cow', // Name of the gear (camelcase)
create: 'each', // Gears can behave as singletons or instances
extending: 'farmAnimal', // Gear-inheritance is possible
dependencies: ['logger'], // Gear Dependency-Injection is possible
allowedParents: ['barn'], // Gears can be structured into trees
// Each Gear can have config supplied to it...
// values of which can be defaulted:
defaults: {
milkable: true
friendly: true
},
// Config values can be validated via JSON Schema:
schema: require('./cow-config-schema.json'),
// Gears can have the equivalent of a constructor:
initFunction: function (config, gears, callback) {
this.logger.info('Created ' + config.cowName + '!' );
},
// Gears can have their own methods:
methods: function (prototype) {
prototype.moo = function moo (loudness) {
if (loudness > 10} {
this.logger.info('MOO!');
}
else {
this.logger.info('Moo.');
}
};
}
};
This is the full list of properties you can export in a Gear module:
###name
- Required: Yes
- Type: String
#####Description The unique name of the Gear within a Gearbox instance. There are a few naming conventions to adhere to:
- Letters and numbers only
- Start with a lowercase letter
- Use camelcase for multi-word gear names
- Try to keep a Gear's name short and punchy
#####Example
name: 'farmAnimal'
###create
- Required: No
- Default:
each
- Type: String
#####Description When Gearbox creates a new Gear, there are a couple of options:
| Option | Description
| ------ | -----------
| one
| The Gear should be considered a singleton... only one Gear instance will ever be created. Any subsequent requests for a Gear of this type will return that same instance. For example the logger
Gear has a create
value of one
.
| each
| A new Gear instance will be created on each request. If a set of Gears were being arranged to form a web form, then textBox
would be an example of a Gear requiring a create
value of each
. This is the default behaviour.
#####Example
{
name: 'farmAnimal',
create: 'each'
}
###extending
- Required: No
- Type: String
#####Description
Gearbox supports Gear inheritance. For example a car
Gear can inherit properties from a more general vehicle
Gear.
To achieve all this, the value of extending
should simply be the name
of another gear, perhaps better termed as a 'Supergear' (e.g. a car
Gear could have an extending
value of vehicle
).
In this continuing example, vehicle
is just a normal Gear from which several properties will be harvested when creating car
Gears:
| Property | How it's inherited
| -------- | -------------------
| defaults
| The car
Gear's defaults
will be used as usual, but any other defaults
defined in vehicle
will be applied as well. In the case of both Gears defining the same default value, the car
Gear value will override that of vehicle
.
| allowedParents
| In the case of both Gears providing allowedParents
arrays, they are unioned together. If there's just one Gear providing an array, that's what is used.
| allowedChildren
| Just like allowedParents
, but for children.
| methods
| Exactly the same 'override' behaviour as used in defaults
, but applied to methods
instead.
| implicitGears
| If vehicle
provides implicitGears
, then that value is used, unless car
provides it - in which case there's no merging of the two arrays - the car
array just wins.
- It's quite feasible to have a
superCar
Gear, extending acar
Gear, which in-turn is extending avehicle
Gear. These rules are applied at each step along the chain.
#####Example
{
name: 'car',
extending: 'vehicle'
}
###dependencies
- Required: No
- Type: Array of strings
#####Description
Gearbox supports a Gear-based Dependency Injection mechanism - provide an array of Gear names via dependencies
, and fully-instantiated Gear instances will be available as simple properties when the Gear is created.
- Dependent Gears should be singletons (e.g. all the Gears referred to in the
dependencies
array should have their respectivecreate
value set toone
). - Dependency loops (
gearA
has a dependency ongearB
, yetgearB
has a dependency ongearA
) will cause an error. - The usual config machinery will be employed when creating dependencies.
#####Example
{
name: 'databaseTable',
dependencies: ['logger', 'relationalDatabase']
}
###allowedParents
- Required: No
- Type: Array of strings
#####Description
Blueprints provide a way to arrange Gears into a tree structure.
To help apply some integrity to these trees, placing a Gear 'under' a Gear whose type isn't named in the allowedParents
array will cause an error
(though allowedChildren
can potentially save the day).
- Include the special name
$root
in theallowedParents
array if the Gear is permissible on the root of a tree structure. - Withstanding the potential use of
allowedChildren
, if noallowedParents
is defined then trying to use the Gear as part of a tree will cause an error.
#####Example
{
name: 'databaseColumn',
allowedParents: ['databaseTable']
}
###allowedChildren
###defaults
###schema
###initFunction
###secondPassFunction
Tests
$ npm test
$ npm test --coverage