modulapp

Modular application framework


Keywords
module, plugin, architecture, architect, dependency
License
MIT
Install
npm install modulapp@0.0.1

Documentation

Modulapp

Modular application framework for node.js

npm npm npm node David

Travis build Coveralls David

Overview

Modulapp is a framework for defining application in a modular way. It provides an App class and a Module class.

Module concept

Basically a module is an autonomous part of the application. It could be a npm package or it could be a dedicated file in the app's repository. A module can have dependencies (ie. other modules) and can also provides its own API to other dependants.

A module is an instance of the Module class (which extends EventEmitter). You can add any property and functions to that instance.

Modulapp allows to define hooks for a module. The lifecycle of a module has 4 steps: setup, enable, disable and destroy. A specific behavior of the module can be defined during those 4 steps. It's during those hooks that dependencies and options are injected to the module.

The App instance is managing the lifecycle of all modules and dealing with the dependencies and options.

App concept

The App manages the dependencies and the lifecycle of the modules.

An app is an instance of the App class (which extends EventEmitter). The modules must be declared to the app. Then the lifecycle of the app can be performed. The app's lifecycle is composed of 5 steps:

  • resolve: Checks the modules and resolve the dependency tree
  • setup: Synchronously execute the setup hook of every module following the dependency tree order
  • start: Synchronously execute the enable hook of every module following the dependency tree order
  • stop: Synchronously execute the disable hook of every module following the dependency tree reverse order
  • destroy: Synchronously execute the destroy hook of every module following the dependency tree reverse order

As the modules are processed synchronously following the dependency tree, the dependencies of a particular module have already been processed before it.

Usage

Install from npm

npm install modulapp

To write a module, import the Module class.

const Module = require('modulapp').Module;

To write an app, import the App class.

const App = require('modulapp').App;

Example

Module class

// myModule.js
const Module = require('modulapp').Module;
let myModule = new Module("myModule");

// define the dependencies by referencing other modulapp modules' id
myModule.dependencies = ['server', 'db'];

// custom property
myModule.foo = 'foo';

// custom function
myModule.bar = function() {
    myModule.emit('bar'); // Module extends EventEmitter
};

// overwrite the setup hook
myModule.setup = function(app, imports, options, done) {
    console.log('setting up myModule');
    done(null); // executing the done callback is required
};

// overwrite the enable hook
myModule.enable = function(app, imports, options, done) {
    console.log('enabling myModule');

        let db = imports.db;
        db.on('disconnected', () => {
            // Do some stuff on event
        });

        let server = imports.server
        server.doSomething((err) => {
            if (err) {
                done(err);  // if something wrong return done with the error
            } else {
                done(null);
            }
        });
};

// myModule.disable can also be overwritten
// myModule.destroy can also be overwritten

module.exports = myModule;

App class

// app.js
const App = require('modulapp').App;
let app = new App();

// Get the modules
let server = require('./server.js');
let db = require('./db.js');
let myModule = require('./myModule.js');

// set the configuration
app.addConfig(server, db, myModule);

// start the application
// start() will itself execute resolve() and setup() if not done
app.start((err) => {
    if (err) {
        console.log(err);
    } else {
        console.log('app started!');
    }
});

someListener.on('destroy app', () => {
    app.stop((err) => {
        if (err) {
            console.log(err);
        } else {
            app.destroy((err) => {
                if (err) {
                    console.log(err);
                } else {
                    console.log('app destroyed!');
                }
            });
        }
    });
});

API References

Classes

AppEventEmitter

Class representing an App.

ModuleEventEmitter

Class representing a Module.

App ⇐ EventEmitter

Class representing an App.

Kind: global class
Extends: EventEmitter
Emits: resolving, resolved, setting_up, setup, starting, started, stopping, stopped, destroying, destroyed
Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

new App([config], [options])

Provide a new instance of App. The config and options are optionnal.

Throws:

  • Error ERR_APP_011 if options is not an Object.
  • Error ERR_APP_013 if a module is not a Module instance.
Param Type Default Description
[config] Module | Array.<Module> [] The list of modules.
[options] Object {} Options for the modules.

Example

const App = require('modulapp').App;
let app = new App();

let serverModule = require('./server.js');
let dbModule = require('./db.js');

app.addConfig(serverModule, dbModule);

app.start((err) => {
    console.log('App started!');
});

app.id : String

The id of the app, randomly generated.

Kind: instance property of App
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

console.log(app.id); // -> '123456789'
app.id = 'anotherId'; // -> throw Error read-only

app.config : Array.<Module>

The configuration of the app. Getting config never return null, at least an empty Array []. Setting null or undefined replaces the current config by an empty Array []. Setting a module will build an Array with that single module.

Kind: instance property of App
Throws:

  • Error ERR_APP_013 if a module is not a Module instance.
  • Error ERR_APP_014 if the module is not in created status.

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Default Description
[newConfig] Module | Array.<Module> [] The new config.

Example

console.log(app.config); // -> [loggerModule]
app.config = [serverModule, dbModule]; // -> [serverModule, dbModule]
app.config = null; // -> []
app.config = serverModule; // -> [serverModule]

app.options : Object

The options of the app's modules. Read-write property. Getting options never return null, at least an empty Object {}. Setting null or undefined replaces the current options by an empty Object {}.

Kind: instance property of App
Throws:

  • Error ERR_APP_009 if the app is not in created status.
  • Error ERR_APP_008 if not an Object

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Default Description
[newOptions] Object {} The new options.

Example

console.log(app.options); // -> {server: {port: 8080}}
app.options = {server: {host: 'localhost'}}; // -> {server: {host: 'localhost'}}
app.options = null; // -> {}

app.status : String

The status of the module. The value is part of the supported status.

Kind: instance property of App
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

console.log(app.status); // -> 'created'
app.status = App.status.RESOLVED; // -> throw Error read-only

app.addOptions([options])

Add options to the app's modules. Merge with existing options.

Kind: instance method of App
Throws:

  • Error ERR_APP_008 if the options parameter is not an Object.

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Default Description
[options] Object {} The options to add.

Example

console.log(app.options); // -> {server: {port: 8080}}
app.addOptions({server: {host: 'localhost'}}); // -> {server: {port: 8080, host: 'localhost'}}
app.addOptions(null); // -> {server: {port: 8080, host: 'localhost'}}
app.addOptions(); // -> {server: {port: 8080, host: 'localhost'}}
app.addOptions({db: {host: '127.0.0.0'}}); // -> {server: {port: 8080, host: 'localhost'}, db: {host: '127.0.0.0'}}

app.addConfig([...config])

Add modules to the app's configuration. Merge with existing config and check the new modules and remove duplicates and null.

Kind: instance method of App
Throws:

  • Error ERR_APP_013 if a module is not a Module instance.

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
[...config] Module | Array.<Module> The modules to add.

Example

console.log(app.config); // -> [loggerModule]
app.addConfig([serverModule]); // -> [loggerModule, serverModule]
app.addConfig(null); // -> [loggerModule, serverModule]
app.addConfig(); // -> [loggerModule, serverModule]
app.addConfig(socketModule); // -> [loggerModule, serverModule, socketModule]
app.addConfig(socketModule, utilsModule, [dbModule, serverModule]); // -> [loggerModule, serverModule, socketModule, utilsModule, dbModule]

"resolving"

Resolving event. When the app is about to be resolved.

Kind: event emitted by App
Category: events
Since: 1.0.0

"resolved"

Resolved event. When the app has been resolved.

Kind: event emitted by App
Category: events
Since: 1.0.0

"setting_up"

Setting up event. When the app is about to be setup.

Kind: event emitted by App
Category: events
Since: 1.0.0

"setup"

Setup event. When the app has been setup.

Kind: event emitted by App
Category: events
Since: 1.0.0

"starting"

Starting event. When the app is about to be started.

Kind: event emitted by App
Category: events
Since: 1.0.0

"started"

Started event. When the app has been started.

Kind: event emitted by App
Category: events
Since: 1.0.0

"stopping"

Stopping event. When the app is about to be stopped.

Kind: event emitted by App
Category: events
Since: 1.0.0

"stopped"

Stopped event. When the app has been stopped.

Kind: event emitted by App
Category: events
Since: 1.0.0

"destroying"

Destroying event. When the app is about to be destroyed.

Kind: event emitted by App
Category: events
Since: 1.0.0

"destroyed"

Destroyed event. When the app has been destroyed.

Kind: event emitted by App
Category: events
Since: 1.0.0

app.resolve([callback])

Resolve the dependencies of the modules. This method is synchronous, callback is not required.

Kind: instance method of App
Category: lifecycle management
Throws:

  • Error ERR_APP_001 if the app is started
  • Error ERR_APP_006 in case of dependency cycle
  • Error ERR_APP_007 in case of missing module

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
[callback] function The callback executed after resolving. Raised error is passed as first argument, no other argument: callback(err)

Example

const App = require('modulapp').App;

let serverModule = require('./server.js');
let dbModule = require('./db.js');

let app = new App([serverModule, dbModule]);

app.resolve((err) => {
    console.log('modules resolved!');
});

Example (resolve with callback)

app.resolve((err) => {
    console.error(err); // -> in case of error in resolve, the error is passed to the callback
});

Example (resolve with no callback)

try {
    app.resolve();
} catch (err) {
    console.error(err); // -> in case of error in resolve, the error is thrown
}

app.setup([callback])

Setup every modules following the dependency graph.

Kind: instance method of App
Category: lifecycle management
Access: public
See: Module#setup
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
[callback] function The callback executed after setting up. Raised error is passed as first argument, no other argument: callback(err)

Example

const App = require('modulapp').App;

let serverModule = require('./server.js');
let dbModule = require('./db.js');

let app = new App([serverModule, dbModule]);

app.setup((err) => {
    console.log('modules setup!');
});
// setup() will resolve() itself if not done before

app.start([callback])

Enable every modules following the dependency graph.

Kind: instance method of App
Category: lifecycle management
Access: public
See: Module#enable
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
[callback] function The callback executed after starting. Raised error is passed as first argument, no other argument: callback(err)

Example

const App = require('modulapp').App;

let serverModule = require('./server.js');
let dbModule = require('./db.js');

let app = new App([serverModule, dbModule]);

app.start((err) => {
    console.log('app started!');
});
// start() will setup() and resolve() itself if not done before

app.stop([callback])

Disable every modules following the dependency graph.

Kind: instance method of App
Category: lifecycle management
Access: public
See: Module#disable
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
[callback] function The callback executed after stopping. Raised error is passed as first argument, no other argument: callback(err)

Example

const App = require('modulapp').App;

let serverModule = require('./server.js');
let dbModule = require('./db.js');

let app = new App([serverModule, dbModule]);

app.start((err) => {
    console.log('app started!');

    // Do some stuff

    app.stop((err) => {
        console.log('app stopped!');
    });
});
// app has to be started to be stopped

app.destroy([callback])

Destroy every modules following the dependency graph.

Kind: instance method of App
Category: lifecycle management
Access: public
See: Module#destroy
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
[callback] function The callback executed after destroying. Raised error is passed as first argument, no other argument: callback(err)

Example

const App = require('modulapp').App;

let serverModule = require('./server.js');
let dbModule = require('./db.js');

let app = new App([serverModule, dbModule]);

app.start((err) => {
    console.log('app started!');

    // Do some stuff

    app.stop((err) => {
        console.log('app stopped!');

        // Do some stuff

        app.destroy((err) => {
            console.log('app destroyed!');
        });
    });
});
// app has to be stopped to be destroyed

App.events : enum

All supported events of App class.

{
    RESOLVING: 'resolving',
    RESOLVED: 'resolved',
    SETTING_UP: 'setting_up',
    SETUP: 'setup',
    STARTING: 'starting',
    STARTED: 'started',
    STOPPING: 'stopping',
    STOPPED: 'stopped',
    DESTROYING: 'destroying',
    DESTROYED: 'destroyed'
}

Kind: static enum of App
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

app.on(App.events.SETUP, () => {
    // define behavior when app has been setup
});

App.status : enum

All supported status of App class.

Don't confuse this static method App.status with the instance method status.

{
    CREATED: 'created',
    RESOLVED: 'resolved',
    SETUP: 'setup',
    STARTED: 'started',
    STOPPED: 'stopped'
}

Kind: static enum of App
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

if (app.status === App.status.ENABLED) {
    app.foo();
}

Module ⇐ EventEmitter

Class representing a Module.

Kind: global class
Extends: EventEmitter
Emits: setting_up, setup, enabling, enabled, disabling, disabled, destroying, destroyed
Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

new Module(id, [initialOptions])

Create a new instance of Module. The id parameter is required, it could be either a String or an Object representing the package.json.

In case of the package.json is provided, the id, version, dependencies and options will be extracted from this Object.

Throws:

  • Error ERR_MOD_001 if the id parameter is null or undefined
  • Error ERR_MOD_005 if something wrong on the dependency
Param Type Default Description
id String | Object Either a String id of the module or an Object representing the package.json
[initialOptions] Object {} Options for the module

Example

const Module = require('modulapp').Module;
let myModule = new Module('myModule');

myModule.foo = 'foo';
myModule.bar = function() {
    // this is a custom method
};

myModule.setup = function(app, options, imports, done) {
    // this is overriding the setup  method

    let logger = imports.logger;
    logger.log('setting up myModule');
    done(null);
};

module.exports = myModule;

Example (constructor with a String argument)

const Module = require('modulapp').Module;
let myModule = new Module('myModule');
console.log(myModule.id); // -> 'myModule'
console.log(myModule.status); // -> 'created'
console.log(myModule.version); // -> undefined
console.log(myModule.options); // -> {}
console.log(myModule.dependencies); // -> []

Example (constructor with a Object argument)

const packagejson = require('package.json');
// {
//    name: 'myModule',
//    version: '1.0.0',
//    module: {
//      dependencies: ['logger'],
//      options: {
//        port: 8080
//      }
//    }
// }

const Module = require('modulapp').Module;
let myModule = new Module('myModule');
console.log(myModule.id); // -> 'myModule'
console.log(myModule.status); //  ->'created'
console.log(myModule.version); // -> '1.0.0'
console.log(myModule.options); // -> {port: 8080}
console.log(myModule.dependencies); // -> ['logger']

module.id : String

The id of the module.

Kind: instance property of Module
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

console.log(myModule.id); // -> 'myModule'
myModule.id = 'anotherId'; // -> throw Error read-only

module.status : String

The status of the module. The value is part of the supported status.

Kind: instance property of Module
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

console.log(myModule.status); // -> 'created'
myModule.status = Module.status.SETUP; // -> throw Error read-only

module.version : String

The version of the module.

Kind: instance property of Module
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

console.log(myModule.version); // -> '1.0.0'
myModule.version = '1.0.1'; // -> throw Error read-only

module.options : Object

The options of the module. Getting options never return null, at least an empty Object {}. Setting null or undefined replaces the current options by an empty Object {}.

Kind: instance property of Module
Throws:

  • Error ERR_MOD_002 if the module is not in created status
  • Error ERR_MOD_004 if not an Object

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Default Description
[newOptions] Object {} The new options

Example

console.log(myModule.options); // -> {port: 8080}
myModule.options = {host: 'localhost'}; // -> {host: 'localhost'}
myModule.options = null; // -> {}

module.dependencies : Array.<String>

The dependencies of the module. Getting dependencies never return null, at least an empty Array []. Setting null or undefined replaces the current config by an empty Array []. Setting a String will build an Array with that single String.

Kind: instance property of Module
Throws:

  • Error ERR_MOD_003 if the module is not in created status
  • Error ERR_MOD_005 if a non-String dependency is found

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Default Description
[newDependencies] String | Array.<String> [] The new dependencies

Example

console.log(myModule.dependencies); // -> ['logger']
myModule.dependencies = ['server', 'db']; // -> ['server', 'db']
myModule.dependencies = null; // -> []
myModule.dependencies = 'server'; // -> ['server']

module.addOptions([options])

Add options to the module. Merge with existing options.

Kind: instance method of Module
Throws:

  • Error ERR_MOD_004 if the options parameter is not an Object

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Default Description
[options] Object {} The options to add

Example

console.log(myModule.options); // -> {port: 8080}
myModule.addOptions({host: 'localhost'}); // -> {port: 8080, host: 'localhost'}
myModule.addOptions(null); // -> {port: 8080, host: 'localhost'}
myModule.addOptions(); // -> {port: 8080, host: 'localhost'}

module.addDependencies([...dependencies])

Add dependencies to the module. Merge with existing dependencies and check the new dependencies, flatten the Array and remove duplicates and null.

Kind: instance method of Module
Throws:

  • Error ERR_MOD_005 if a non-String dependency is found

Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
[...dependencies] String | Array.<String> The dependencies to add

Example

console.log(myModule.dependencies); // -> ['logger']
myModule.addDependencies(['server']); // -> ['logger', 'server']
myModule.addDependencies(null); // -> ['logger', 'server']
myModule.addDependencies(); // -> ['logger', 'server']
myModule.addDependencies('socket'); // -> ['logger', 'server', 'socket']
myModule.addDependencies('socket', 'utils', ['db', 'server']); // -> ['logger', 'server', 'socket', 'utils', 'db']

"setting_up"

Setting up event. When the module is beginning its setup.

Kind: event emitted by Module
Category: events

"setup"

Setup event. When the module has been setup.

Kind: event emitted by Module
Category: events

"enabling"

Enabling event. When the module is about to be enabled.

Kind: event emitted by Module
Category: events

"enabled"

Enabled event. When the module has been enabled.

Kind: event emitted by Module
Category: events

"disabling"

Disabling event. When the module is about to be disabled.

Kind: event emitted by Module
Category: events

"disabled"

Disabled event. When the module has been disabled.

Kind: event emitted by Module
Category: events

"destroying"

Destroying event. When the module is about to be destroyed.

Kind: event emitted by Module
Category: events

"destroyed"

Destroyed event. When the module has been destroyed.

Kind: event emitted by Module
Category: events

module.setup(app, options, imports, done)

The setup function of the module. Executed while the app is being setup. Could be overriden, does nothing by default. Once the app is resolved, this method is not available anymore.

Kind: instance method of Module
Category: lifecycle hooks
Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
app App The App instance
options Object The options of the module
imports Object The dependencies of the module
done function Callback to return passing any error as first argument done(err)

Example

const Module = require('modulapp').Module;
let myModule = new Module('myModule');

// override the default setup function
myModule.setup = function(app, options, imports, done) {
   // place your custom code to be executed when myModule is setup
}

module.enable(app, options, imports, done)

The enable function of the module. Executed while the app is being started. Could be overriden, does nothing by default. Once the app is resolved, this method is not available anymore.

Kind: instance method of Module
Category: lifecycle hooks
Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
app App The App instance
options Object The options of the module
imports Object The dependencies of the module
done function Callback to return passing any error as first argument done(err)

Example

const Module = require('modulapp').Module;
let myModule = new Module('myModule');

// override the default enable function
myModule.enable = function(app, options, imports, done) {
   // place your custom code to be executed when myModule is enable
}

module.disable(app, options, imports, done)

The disable function of the module. Executed while the app is being stopped. Could be overriden, does nothing by default. Once the app is resolved, this method is not available anymore.

Kind: instance method of Module
Category: lifecycle hooks
Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
app App The App instance
options Object The options of the module
imports Object The dependencies of the module
done function Callback to return passing any error as first argument done(err)

Example

const Module = require('modulapp').Module;
let myModule = new Module('myModule');

// override the default disable function
myModule.disable = function(app, options, imports, done) {
   // place your custom code to be executed when myModule is disabled
}

module.destroy(app, options, imports, done)

The destroy function of the module. Executed while the app is being destroyed. Could be overriden, does nothing by default. Once the app is resolved, this method is not available anymore.

Kind: instance method of Module
Category: lifecycle hooks
Access: public
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com

Param Type Description
app App The App instance
options Object The options of the module
imports Object The dependencies of the module
done function Callback to return passing any error as first argument done(err)

Example

const Module = require('modulapp').Module;
let myModule = new Module('myModule');

// override the default destroy function
myModule.destroy = function(app, options, imports, done) {
   // place your custom code to be executed when myModule is destroyed
}

Module.events : enum

All supported events of Module class.

{
    SETTING_UP: 'setting_up',
    SETUP: 'setup',
    ENABLING: 'enabling',
    ENABLED: 'enabled',
    DISABLING: 'disabling',
    DISABLED: 'disabled',
    DESTROYING: 'destroying',
    DESTROYED: 'destroyed'
}

Kind: static enum of Module
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

myModule.on(Module.events.SETUP, () => {
    // define behavior when myModule has been setup
});

Module.status : enum

All supported status of Module class.

Don't confuse this static method Module.status with the instance method status.

{
    CREATED: 'created',
    SETUP: 'setup',
    ENABLED: 'enabled',
    DISABLED: 'disabled',
    DESTROYED: 'destroyed'
}

Kind: static enum of Module
Access: public
Read only: true
Since: 1.0.0
Author: nauwep nauwep.dev@gmail.com
Example

if (myModule.status === Module.status.ENABLED) {
    myModule.foo();
}

License

MIT