gearbox

0.0.7 • Public • Published

Gearbox

Build Status

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 a level of debug, and if a database Gear is required, it will be instantiated with a dataSource 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().
  • 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.
    • So looking for a Gear named webServer will ultimately result in Gearbox trying require('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 a car Gear, which in-turn is extending a vehicle 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 respective create value set to one).
  • Dependency loops (gearA has a dependency on gearB, yet gearB has a dependency on gearA) 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 the allowedParents array if the Gear is permissible on the root of a tree structure.
  • Withstanding the potential use of allowedChildren, if no allowedParents 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

License

MIT

Dependents (1)

Package Sidebar

Install

npm i gearbox

Weekly Downloads

7

Version

0.0.7

License

MIT

Last publish

Collaborators

  • timneedham