Plugin
Hemera's plugin system based on the powerful Avvio package. Avvio is fully reentrant and graph-based. You can load components/plugins within plugins, and be still sure that things will happen in the right order.
Plugins should encourage you to encapsulate a domain specific context in a reusable piece of software. Seperate plugins by a different topic name.
Plugin helper library
Before we get into the plugin system of hemera you have to install a package called hemera-plugin
. This package can do some things for you:
- Check the bare-minimum version of Hemera
- Provide consistent interface to register plugins even when the api is changed
- Pass metadata to intialize your plugin with correct dependencies, default options and name
- Skip plugin encapsulation
Create a plugin
const hp = require('hemera-plugin')
const myPlugin = hp((hemera, opts, done) => {
const topic = 'math'
hemera.add(
{
topic,
cmd: 'add'
},
function(req, cb) {
cb(null, {
result: req.a + req.b
})
}
)
done()
}, '>=5.0.0')
module.exports = myPlugin
Plugins are encapsulated "scoped" by default. If you define an extension inside it will only effects the actions in your plugin. You can disable it if you set the hemera plugin option
scoped:false
.
Break encapsulation
Sometimes it is still useful to write a plugin which effects child as well as sibling scopes. You can archive this with plugin option scoped: false
property. This approach is used in payload validators hemera-joi
or authentication hemera-jwt
to provide an overall validation mechanism.
const hp = require('hemera-plugin')
const myPlugin = hp(
(hemera, opts, done) => {
const topic = 'math'
hemera.ext('onActFinished', function(ctx, next) {
// some code
next()
})
done()
},
{ scoped: false }
)
hemera.use(myPlugin)
Scoped sensitive settings are topic aware
If you create a topic for the first time inside a plugin and overwrite one of the settings.
They are executed with the plugin scope even if you call the pattern outside of the plugin and define different settings. This encourage the practice to use a unique topic per plugin and separates the responsibility.
Register child plugins
You can load plugins inside other plugins. Be ware that Scoped sensitive settings are effected.
const hp = require('hemera-plugin')
const myPlugin = hp((hemera, opts, done) => {
const topic = 'math'
hemera.ext('onActFinished', function(ctx, next) {
// some code
next()
})
hemera.use(
hp((hemera, opts, done) => {
// some code
// this plugin will be effected by the 'onActFinished' extension
done()
})
)
done()
})
Decorators
Decorators are something special. Even if you create a plugin scope you can decorate the root hemera instance. Decorators are primarly used to expose data or functionality to other plugins.
const hp = require('hemera-plugin')
const myPlugin = hp((hemera, opts, done) => {
const topic = 'math'
hemera.decorate('test', 1)
done()
})
hemera.use(myPlugin)
hemera.ready(() => console.log(hemera.test))
Expose
If you want to share data inside a plugin you can use expose()
it will effects all sibling and child scopes. Expose should be used to control the inner workings of the plugin.
const hp = require('hemera-plugin')
const myPlugin = hp((hemera, opts, done) => {
const topic = 'math'
hemera.expose('cache', new Map())
hemera.cache.set('key', 'value')
hemera.use(myPlugin2) // cache is available in this plugin too
done()
})
hemera.use(myPlugin)
hemera.ready()
Global registration
Sometimes it's still useful to write a plugin which effects sibling and child scopes. You can disable the creation of a plugin scope with the scoped: false
property.
This approach is used in payload validators hemera-joi
or authentication plugins like hemera-jwt
.
const hp = require('hemera-plugin')
const myPlugin = hp(
(hemera, opts, done) => {
const topic = 'math'
hemera.ext('onRequest', function(hemera, request, reply, next) {
// some code
next()
})
done()
},
{
scoped: false
}
)
hemera.use(myPlugin)
Scoped sensitive settings
Add plugin metadata
const hp = require('hemera-plugin')
const myPlugin = hp(
async (hemera, opts) => {
const topic = 'math'
hemera.add(
{
topic,
cmd: 'add'
},
function(req, cb) {
cb(null, {
result: req.a + req.b
})
}
)
},
{
hemera: '0.x', // bare-minimum version of Hemera
name: 'my-plugin', // name of your plugin, will be used e.g for logging purposes
options: { host: 'localhost', port: 8003 }, // default options for your plugin
dependencies: ['plugin2'],
decorators: ['joi']
}
)
hemera.use(myPlugin)
Plugin dependencies
You can declare plugins and decorators as dependencies. The constrains are checked when all plugins are registered.
const hp = require('hemera-plugin')
const myPlugin = hp(
(hemera, opts, done) => {
const Joi = hemera.joi
done()
},
{
dependencies: ['plugin2'],
decorators: ['joi']
}
)
hemera.use(myPlugin)
hemera.ready(err => {
// The dependency 'hemera-joi' is not registered
})
Async / Await
You can also pass an async function and omit the done
callback.
const hp = require('hemera-plugin')
const myPlugin = hp(async (hemera, opts) => {
const topic = 'math'
hemera.add(
{
topic,
cmd: 'add'
},
function(req, cb) {
cb(null, {
result: req.a + req.b
})
}
)
})
Plugin registration
A plugin must be registered before the ready
function is called. The ready function will initialize all plugins. Default plugin options are preserved if you don't overwrite them.
hemera.use(plugin, { a: 1 })
After
Calls a function after all previous registrations are loaded, including all their dependencies. This can be used to defer the registration of a plugin.
hemera.use(plugin).after(cb)
// or
hemera.use(plugin)
hemera.after(cb)
Plugin timeout
If you need to handle a lot of plugins you could run into timeout issues e.g if you forgot to call the callback of a plugin or when no promise was resolved. These errors are hard to track. In order to find out the cause you can specify a pluginTimeout
.
pluginTimeout
is the number of millis to wait before a plugin is marked as "frozen".
The error object has a property fn
which contains your plugin function and all associated informations about your plugin.
new Hemera(nats, { pluginTimeout: 100 })
hemera.use(plugin)
hemera.use(err => {
err.fn.name // the name of the plugin function
err.fn[Symbol.for('plugin-meta')] // all plugin informations like name or options
})