npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

awesome-module-manager

v1.2.0

Published

A module manager for large node applications

Downloads

36

Readme

Awesome Module Manager

This is a way to help achieve modularity in large NodeJS applications.

What is the problem ?

Generally you start a NodeJS project. And it becomes big. So you split it using NPM. However, NPM manages dependencies at the code level, but not at the application level. A complex and modular (web) application needs to provide lots of facilities:

  • open connections to the different data stores
  • register HTTP resources (assets, javascript, REST endpoints)
  • register subscribers on an event emitter, because of, you know, lazy coupling
  • ...

And maybe you will need, later, to replace a module by another, so you'll use dependency injection of some kind.

The Awsesome Module Manager aims to facilitate the organization of those kind of lerge apps. It falls into the same kind of software than The Architect.

How does it work

basics

You define states in your applications. You define the actions that the module should do in this state.

var mongo = require('mongodb')
var AwesomeModule = require('awesome-module');
var AwesomeModuleManager = require('awesome-module-manager');

var manager = new AwesomeModuleManager();
// create the datastore_connect state: all states depend of the
// lib state, but we specify it anyway
manager.registerState('datastore_connect', ['lib']);

//create a module
var mongoModule = new AwesomeModule('datastore.mongo', {
  states: {
    // the "lib" state is always defined: it is where your module should
    // expose its API: it's the equivalent of var lib = require('themodule');
    lib: function(dependencies, callback) {
      var api = { connected: false };
      return callback(null, api);
    },
    datastore_connect: function(dependencies, callback) {
      // "this" is the api object returned in the lib state
      var self = this;
      mongo.MongoClient.connect(
        "mongodb://localhost:27017/integration_tests",
        function(err, db) {
          if ( err ) { return callback(err); }
          self.db = db;
          self.connected = true;
          callback();
      });
    }
  }
});

// then register the module.
manager.registerModule(mongoModule);
// and finally fire the datastore_connect state !
manager.fire('datastore_connect', 'datastore.mongo');

dependencies

Now, let's say we want the mongodb connection string to come from our configuration system. We have to create a configuration module, and add a dependency of the mongo module to the config module.

Lib is a special state: the result of the lib state id what will be given to the requester module.

var AwesomeModule = require('awesome-module');
var Dependency = AwesomeModule.AwesomeModuleDependency;
var AwesomeModuleManager = require('awesome-module-manager');
var manager = new AwesomeModuleManager();
manager.registerState('datastore_connect', ['lib']);

// create the configuration module
var configModule = new AwesomeModule('basic.config', {
  states: {
    lib: function(dependencies, callback) {
      var api = require('../../config.json');
      // Here we return the api object. This is the object that
      // will be given to dependent modules
      return callback(null, api);
    }
  }
});

var mongoModule = new AwesomeModule('datastore.mongo', {
  dependencies: [
    // tell the system we depends on basic.config
    // and we want it aliased as "conf"
    new Dependency(Dependency.TYPE_NAME, 'basic.config', 'conf')
  ],
  states: {
    lib: function(dependencies, callback) {
      var api = { connected: false };
      return callback(null, api);
    },
    datastore_connect: function(dependencies, callback) {
      var self = this;
      // dependencies contains the dependencies we require
      // here config will contain the "api" object exported in the lib state
      // of the "basic.config" module.
      var config = dependencies('conf');
      mongo.MongoClient.connect(
        config.mongoUrl,
        function(err, db) {
          if ( err ) { return callback(err); }
          self.db = db;
          self.connected = true;
          callback();
      });
    }
  }
});

// then register the module.
manager.registerModule(mongoModule);
// and finally fire the datastore_connect state !
manager.fire('datastore_connect', 'datastore.mongo');

dependencies niceties

optional dependencies

A module can optionally require a dependency

new Dependency(Dependency.TYPE_NAME, 'basic.config', 'conf', true)

The injected dependencies('conf') may or may not contain the conf object.

dependencies by ability

Sometimes you don't want to express a dependency by it's name, but for the API it provides. For example, you may want a logger, and don't care that it's winston or another implementation.

Modules can tell the abilities they provide:

var someModule = new AwesomeModule('some.name', {
  abilities: ['interface.logger'],
  ...
});

And modules can express a dependency upon an ability :

var otherModule = new AwesomeModule('other.name', {
  dependencies: [
    new Dependency(Dependency.TYPE_ABILITY, 'interface.logger', 'myLogger')
  ],
  ...
});

Dependency callback

A module can specify that it wants a callback to be fired when a dependency is fulfilled and both the module and its dependency reach a certain state.

var AMD = AwesomeModule.AwesomeModuleDependency;
var exampleModule = new AwesomeModule('example', {
  states: {
    lib: function(dependencies, callback) {
      console.log('example module lib');
      return callback(null, {foo: function(){}});
    }
    start: function(dependencies, callback) {
      console.log('example module start');
      return callback();
    }
  }
});

var dependency = new AMD(AMD.TYPE_NAME, 'example', 'example', true);
dependency.on('start', function(deps, callback){
  console.log('dependent module start callback');
  deps('example').foo();
});

var dependentModule = new AwesomeModule('dependent', {
  states: {
    lib: function(dependencies, callback) {
      console.log('dependent module lib')
      return callback(null, {bar: function(){}});
    },
    start: function(dependencies, callback) {
      console.log('dependent module start')
      return callback();
    }
  },
  dependencies: [dependency]
});

var manager = new AwesomeModuleManager();
manager.registerState('start', ['lib']);


manager.registerModule(exampleModule);
manager.fire('start', 'example');
manager.registerModule(dependentModule);
manager.fire('start', 'dependant');

will echo

example module lib
example module start
dependent module lib
dependent module start
dependent module start callback

Now, the same code, but with a different loading order, will also work:

var manager = new AwesomeModuleManager();
manager.registerState('start', ['lib']);

manager.registerModule(dependentModule);
manager.fire('start', 'dependant');
manager.registerModule(exampleModule);
manager.fire('start', 'example');

will echo

dependent module lib
dependent module start
example module lib
example module start
dependent module start callback

Module loaders

basics

You can teach the module manager new ways to load modules. Right now it provides two methods:

  • the code loader

Here we create the exampleModule programmatically, and then register a code loader for it. This is basically what the AwesomeModuleManager registerModule does under the hood.

var AwesomeModuleManager = require('awesome-module-manager');
var exampleModule = new AwesomeModule('example.module');

mm = new AwesomeModuleManager();
var codeLoader1 = mm.loaders.code(exampleModule);
mm.registerLoader(codeLoader1);

mm.fire('lib', 'example.module');
  • the filesystem loader

This loader lookup the modules in a given path. The module name should correspond with the folder name inside of the path. A require(path/modulename) shoudl return an AwesomeModule.

var AwesomeModuleManager = require('awesome-module-manager');
mm = new AwesomeModuleManager();
var fsLoader1 = mm.loaders.filesystem('/some/path');
mm.registerLoader(fsLoader1);

mm.fire('lib, ''test.module');
// the module manager will check if there is a "test.module" folder inside /some/path.
// if found, it will try : var module = require('/some/path/test.module');
// and then check that module is an awesome module

The module manager loaders are middleware : they are called in order, until one of them find the module.

trusted and untrusted loaders

In such a system, maybe you'll have to load one day third party modules. The loader API allows you to flag a loader as being trusted or untrusted. This information will be propagated when a module requires another module (see Module API proxy section below).

Be default, loaders are configured as untrusted.

var AwesomeModuleManager = require('awesome-module-manager');
mm = new AwesomeModuleManager();

// create an untrusted loader. All modules loaded through it (so, that are found under /some/path) will be flagged as untrusted.
var fsLoader1 = mm.loaders.filesystem('/some/path');
mm.registerLoader(fsLoader1);

// create a trusted loader. All modules loaded through it will be flagged as trusted.
var fsLoader2 = mm.loaders.filesystem('/my/modules', true);
mm.registerLoader(fsLoader2);

Creating your own loader

It's kind of easy to create your own awesome module loader. You have to provide a function that takes as arguments a module name, and a callback. This function tries to load the module, and then fires the callback, either with the module, or with nothing. Here is an example for a "require" loader:

function load(modName, callback) {
  var mod;
  try {
    mod = require(modName);
  } catch(e) {
    // not finding a module through a loader is not to be considered as an error
    // the function will just call the callback with an empty "mod" argument
  }
  return callback(null, mod);
}

You then create an AwesomeModuleLoader instance, passing the loader name, the loading function, and whether thtis loader is a trusted one.

var AwesomeModuleManager = require('awesome-module-manager');
var requireloader = new AwesomeModuleManager.AwesomeModuleLoader('requireLoader', load, true);
mm = new AwesomeModuleManager();
mm.appendLoader(requireloader);

Module API proxy

Basics

You can adapt the API presented to the requesting module by adding a proxy method to the module.

var AwesomeModule = require('awesome-module');
var Dependency = AwesomeModule.AwesomeModuleDependency;
var AwesomeModuleManager = require('awesome-module-manager');
var manager = new AwesomeModuleManager();
manager.registerState('lib');

// this module will export different API, depending on the requester
var configModule = new AwesomeModule('basic.config', {
  states: {
    lib: function(dependencies, callback) {
      var api = {
        color: 'blue'
      }
      return callback(null, api);
    }
  },
  proxy: function(requesterName, trusted) {
    if (requesterName === 'basic.sun') {
      return {
        color: 'yellow';
      }
    } else {
      return this;
    }
  }
});

var skyModule = new AwesomeModule('basic.sky', {
  dependencies: [
    new Dependency(Dependency.TYPE_NAME, 'basic.config', 'conf')
  ],
  states: {
    lib: function(dependencies, callback) {
      var conf = dependencies('conf');
      console.log('in basic.sky module, conf color = ' + conf.color); //in basic.sky module, conf color = blue
      return callback(null, {});
    }
  }
});

var sunModule = new AwesomeModule('basic.sun', {
  dependencies: [
    new Dependency(Dependency.TYPE_NAME, 'basic.config', 'conf')
  ],
  states: {
    lib: function(dependencies, callback) {
      var conf = dependencies('conf');
      console.log('in basic.sun module, conf color = ' + conf.color); //in basic.sun module, conf color = yellow
      return callback(null, {});
    }
  }
});

manager.registerModule(skyModule);
manager.registerModule(sunModule);
// and finally fire the datastore_connect state !
manager.fire('lib', ['basic.sky', 'basic.sun']);

Use with trusted & untrusted loaders

The main interest of this feature is to be able to present a distinct API when a module is trusted, or untrusted.

var AwesomeModuleManager = require('awesome-module-manager');
var exampleModule = new AwesomeModule('example.module', {
  states: {
    lib: function(deps, callback) {
      var conf = deps('conf');
      conf.doSomethingTricky(); // will echo "I do not beliave you" because the associated loader codeLoader1 is untrusted
    }
  },
  dependencies: [ new Dependency(Dependency.TYPE_NAME, 'basic.config', 'conf') ]
});

var exampleModule2 = new AwesomeModule('example.module2', {
  states: {
    lib: function(deps, callback) {
      var conf = deps('conf');
      conf.doSomethingTricky(); // will echo "doing something tricky..." because the associated loader codeLoader2 is trusted
    }
  },
  dependencies: [ new Dependency(Dependency.TYPE_NAME, 'basic.config', 'conf') ]
});

var configModule = new AwesomeModule('basic.config', {
  states: {
    lib: function(dependencies, callback) {
      var api = {
        doSomethingTricky: function() { console.log('doing something tricky...'); }
      };
      return callback(null, api);
    }
  },
  proxy: function(requesterName, trusted) {
    if (!trusted) {
      return {
        doSomethingTricky: function() { console.log('I do not believe you !!!'); }
      };
    } else {
      return this;
    }
  }
});


mm = new AwesomeModuleManager();
var codeLoader1 = mm.loaders.code(exampleModule);
mm.registerLoader(codeLoader1);
var codeLoader2 = mm.loaders.code(exampleModule2);
mm.registerLoader(codeLoader2, true);
var confLoader = mm.loaders.code(configModule);
mm.registerLoader(confLoader);

mm.fire('lib', ['example.module', 'example.module2']);

events

An AwesomeModuleManager is also an event emitter.

Here is the list of emitted events:

| Event | Associated data | Description | |---|---|---| | loader:loadstart | name: the module name, context: the module loading context | fired when the manager starts the loading process for a module | | loader: loaderror | name: the module name, module: the module object, context: the module loading context, error: the error | fired when the manager encountered an error while trying to load a module | | loader:loaded | name: the module name, module: the module object, context: the module loading context | fired when a module finished loading successfully | | state:fire | state: the state name, module: the module object | fired when launching a certain state on a module | | state:fulfilled | state: the state name, module: the module object | fired when a certain state has been successfully reached on a module | | state:failed | state: the state name, module: the module object, error: the error object | fired when a certain state failed to complete, either because of a direct error thrown, or because the module returns an error object |