Class: Plugin

Plugin

Instances of Plugin expose an API which can be accessed by the developer to define plugins, along with methods used by the Registry to manage plugin lifecycle.

The developer API can be used to:

  • register node modules as dependencies on the injector
  • include files in the plugin definition for managing large plugins
  • define dependencies using factory methods
  • alias dependencies
  • create adapters to determine which implementation of an interface to use at runtime
  • access dependencies for mutation without creating new dependencies
  • extend the plugin API
  • register callbacks for managing plugin lifecycle

Constructor

new Plugin(name, metadata)

Initialize a new Plugin instance.

Parameters:
Name Type Description
name string

The name of the plugin

metadata Object

The plugin metadata

Source:
Example

Metadata

{
  version: '0.0.1',
  dependencies: {
    '<PLUGIN NAME>': '>=1.2.3'
  }
}

Methods

adapter(name, fn) → {this}

Create factories that determine which implementation to use at injection time.

Parameters:
Name Type Description
name string

Dependency name

fn function

Factory function

Source:
Returns:
Type
this
Example

Usage

plugin
  .factory('RedisResource', function () {})
  .factory('MongoResource', function () {})
  .adapter('Resource', function (injector, settings) {
     // where settings.property is 'RedisResource' or 'MongoResource'
     return injector.get(settings.property)
  })
  .factory('User', function (Resource) {})

alias(alias, name) → {this}

Alias creates a reference to another item on the injector within its own injector object. When the alias is injected through the use of the injector.get() method it calls the aliased dependency and creates a reference to the instance in the dependency.value field.

Parameters:
Name Type Description
alias string

New dependency name

name string

Existing dependency name

Source:
Returns:
Type
this
Example

Usage

plugin
  .factory('myDependency', function () {
    // ...
  })
  .alias('myAlias', 'myDependency')

assembler(name, fn) → {this}

This can be used to define new types of components. For example, the core framework probably doesn't need any knowledge of Express routers, but if you wanted to define a specialized factory registrar for routers, you could do it like so:

app.plugin('server', {
  version: '0.0.0'
})
.initializer(function (plugin) {
  plugin.assembler('router', function (injector) {
    let plugin = this
    return function (name, factory) {
      injector.register({
        name,
        type: 'router',
        plugin: plugin.name,
        factory
      })
    })
  })
})

This makes a new dependency registrar called 'router' that can be used as follows:

app.plugin('other', {
  version: '0.0.0'
})
.initializer(function (plugin) {
  plugin.router('SomeRouter', function (Router, SomeResource) {
    let router = new Router()

    router.get('endpoint', function () {
      SomeResource
        .list(req.query)
        .then(function (results) {
          res.json(results)
        })
        .catch(error => next(error))
    })

    return router
  })
})

The dependency inject can then be queried by this new "type" value.

injector.filter({ type: 'router' })
Parameters:
Name Type Description
name string

Dependency name

fn function

Assembler function

Source:
To Do:
  • there should possibly be a way to create a starter method automatically for an assembler to save that boilerplate
Returns:
Type
this

extension(name, fn) → {this}

The idea of an extension is that you can access some component to use it's API without registering anything on the injector.

This is useful for things like modifying a model's schema or registering event handlers on an event emitter.

Parameters:
Name Type Description
name string

Dependency name

fn function

Mutator function

Source:
Returns:
Type
this
Examples

Extending Data Schema

// Given a plugin created as follows
app.plugin('Default API', <METADATA>)
  .initializer(function (plugin) {
    .factory('User', function (Resource) {
      class User extends Resource {
        static get schema () {
          return Object.assign({}, super.schema, {
            name: { type: 'string' },
            email: { type: 'string', format: 'email' }
          })
        }
      }

      return User
    })
  })

app.plugin('My Project', <METADATA>)
  .initializer(function (plugin) {
    plugin.extension('UserExtension', function (User) {
      User.extendSchema({
        domainSpecificAttribute: { type: 'whatever', ... }
      })
    })
  })

Adding Event Handler

app.plugin(<NAME>, <METADATA>)
  .initializer(function (plugin) {
    plugin.factory('emitter', function () {
      return new EventEmitter()
    })
  })

app.plugin('My Project', <METADATA>)
  .initializer(function (plugin) {
    plugin.extension('CustomEventHandlers', function (emitter) {
      emitter.on('ready', function (event) {
        // do something
      })
    })
  })

factory(name, fn) → {this}

The factory method registers a new dependency on the injector, validates it, and determines which other dependencies it requires.

The first argument is the name of the new dependency and the second argument is a function that returns the value of the dependency. However, this dependency is not invoked at the time the dependency is registered.

Getting a dependency from the Injector invokes the function and stores the return value.

Parameters:
Name Type Description
name string

Dependency name

fn function

Factory function

Source:
Returns:
Type
this
Example

Usage

plugin
  .factory('one', function () {
    return 1
  })
  .factory('two', function () {
    return 2
  })
  .factory('oneplustwo', function (one, two) {
    return one + two
  })

include(filename) → {this}

The bootstrapping process searches through the plugins directory looking for plugins to load. To make a new plugin you simply create a sub-directory with an index.js file. Additional files in the directory are incorporated using the include method. This allows developers to separate out one plugin into an arbitrary number of files.

Parameters:
Name Type Description
filename string

Relative path to included file.

Source:
Returns:
Type
this
Example

Usage

// index.js
'use strict'

module.exports = function (sunstone) {
  app.plugin('MyResource', {
    version: '0.0.1',
    dependencies: {
      'Server': '0.0.1'
    }
  })
  .initializer(function (plugin) {
    plugin
      .include('./other')
      .include('./yetanother')
  })
}

// other.js
'use strict'

module.exports = function (plugin) {

  plugin
    .factory('MyModel', function (a, b) {
      // ...
    })
    .router('MyModelRouter', function (MyModel) {
      // ...
    })

}

initialize() → {this}

Invoke the initializer function, if it exists.

Source:
Returns:
Type
this

initializer(callback) → {this}

Register an initializer function.

Parameters:
Name Type Description
callback callback

Initializer function

Source:
Returns:
Type
this
Example

Usage

module.exports = function (app) {
  app.plugin('MyResource', {
    version: '0.0.1',
    dependencies: {
      'Server': '0.0.1'
    }
  })
  .initializer(function (plugin) {
    .include('./other')
    .include('./yetanother')
  })
}

require(modules) → {this}

Adds dependencies to the injector by loading node modules. Accepts a string, array or object. By passing an object you can alias the package name.

Parameters:
Name Type Description
modules string | Array | Object

Modules to require

Source:
Returns:
Type
this
Example

Usage

app.plugin(<NAME>, <METADATA>)
  .initializer(function (plugin) {
    plugin.require('express')

    plugin.require([
      'crypto',
      'ioredis'
    ])

    plugin.require({
      '_': 'lodash',
      'fs': 'fs-extra',
      'myLibrary': './myLibrary'
    })
  })

start() → {this}

Invoke the starter function, if it exists.

Source:
Returns:
Type
this

starter(callback) → {this}

Register an starter function.

Parameters:
Name Type Description
callback function

Starter function

Source:
Returns:
Type
this
Example

Usage

app.plugin(<NAME>, <METADATA>)
  .initializer(function (plugin) {
    plugin.starter(function (injector, server) {
      injector
        .filter({ plugin: this.name, type: 'router' })
        .values()
        .forEach(router => {
          router.mount(server)
        })
    })
  })

stop() → {this}

Invoke the stopper function, if it exists.

Source:
Returns:
Type
this

stopper(callback) → {this}

Register an initializer function.

Parameters:
Name Type Description
callback function

Stopper function

Source:
Returns:
Type
this
Example

Usage

app.plugin(<NAME>, <METADATA>)
  .initializer(function (plugin) {
    plugin.stopper(function (injector, server) {
      // code to disable plugin
    })
  })