diff --git a/boot.js b/boot.js index 050b97e..23baf2b 100644 --- a/boot.js +++ b/boot.js @@ -11,8 +11,9 @@ const { AVV_ERR_READY_TIMEOUT } = require('./lib/errors') const { TimeTree } = require('./lib/time-tree') -const Plugin = require('./plugin') +const { Plugin } = require('./plugin') const { debug } = require('./lib/debug') +const { loadPlugin } = require('./lib/load-plugin') const kAvvio = Symbol('kAvvio') const kThenifyDoNotWrap = Symbol('kThenifyDoNotWrap') @@ -154,7 +155,7 @@ function Boot (server, opts, done) { }) }) - Plugin.loadPlugin.call(this, this._root, (err) => { + loadPlugin(this, this._root, (err) => { debug('root plugin ready') try { this.emit('preReady') diff --git a/lib/load-plugin.js b/lib/load-plugin.js new file mode 100644 index 0000000..5afa854 --- /dev/null +++ b/lib/load-plugin.js @@ -0,0 +1,42 @@ +'use strict' + +/** + * @callback LoadPluginCallback + * @param {Error} [err] + */ + +/** + * Load a plugin + * + * @param {*} instance + * @param {*} plugin + * @param {LoadPluginCallback} callback + */ +function loadPlugin (instance, plugin, callback) { + if (typeof plugin.func.then === 'function') { + plugin.func.then((fn) => { + if (typeof fn.default === 'function') { + fn = fn.default + } + plugin.func = fn + loadPlugin(instance, plugin, callback) + }, callback) + return + } + + const last = instance._current[0] + + // place the plugin at the top of _current + instance._current.unshift(plugin) + + plugin.exec((last && last.server) || instance._server, (err) => { + plugin.finish(err, (err) => { + instance._current.shift() + callback(err) + }) + }) +} + +module.exports = { + loadPlugin +} diff --git a/plugin.js b/plugin.js index 975b477..5918dfc 100644 --- a/plugin.js +++ b/plugin.js @@ -4,6 +4,7 @@ const fastq = require('fastq') const EE = require('events').EventEmitter const inherits = require('util').inherits const { debug } = require('./lib/debug') +const { loadPlugin } = require('./lib/load-plugin') const { createPromise } = require('./lib/create-promise') const { AVV_ERR_READY_TIMEOUT } = require('./lib/errors') const { getPluginName } = require('./lib/get-plugin-name') @@ -213,40 +214,16 @@ Plugin.prototype.finish = function (err, cb) { this.q.resume() } -// delays plugin loading until the next tick to ensure any bound `_after` callbacks have a chance -// to run prior to executing the next plugin -function loadPluginNextTick (toLoad, cb) { - const parent = this - process.nextTick(loadPlugin.bind(parent), toLoad, cb) -} - -// loads a plugin -function loadPlugin (toLoad, cb) { - if (typeof toLoad.func.then === 'function') { - toLoad.func.then((fn) => { - if (typeof fn.default === 'function') { - fn = fn.default - } - toLoad.func = fn - loadPlugin.call(this, toLoad, cb) - }, cb) - return - } - - const last = this._current[0] - - // place the plugin at the top of _current - this._current.unshift(toLoad) - - toLoad.exec((last && last.server) || this._server, (err) => { - toLoad.finish(err, (err) => { - this._current.shift() - cb(err) - }) - }) +/** + * Delays plugin loading until the next tick to ensure any bound `_after` callbacks have a chance + * to run prior to executing the next plugin + */ +function loadPluginNextTick (plugin, callback) { + process.nextTick(loadPlugin, this, plugin, callback) } function noop () {} -module.exports = Plugin -module.exports.loadPlugin = loadPlugin +module.exports = { + Plugin +} diff --git a/test/lib/load-plugin.test.js b/test/lib/load-plugin.test.js new file mode 100644 index 0000000..9d09f46 --- /dev/null +++ b/test/lib/load-plugin.test.js @@ -0,0 +1,112 @@ +'use strict' + +const { test } = require('tap') +const boot = require('../..') +const { loadPlugin } = require('../../lib/load-plugin') +const { Plugin } = require('../../plugin') + +test('successfully load a plugin with sync function', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, function (instance, opts, done) { + done() + }, false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err, undefined) + }) +}) + +test('catch an error when loading a plugin with sync function', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, function (instance, opts, done) { + done(Error('ArbitraryError')) + }, false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err.message, 'ArbitraryError') + }) +}) + +test('successfully load a plugin with async function', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, async function (instance, opts) { }, false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err, undefined) + }) +}) + +test('catch an error when loading a plugin with async function', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, async function (instance, opts) { + throw Error('ArbitraryError') + }, false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err.message, 'ArbitraryError') + }) +}) + +test('successfully load a plugin when function is a Promise, which resolves to a function', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, new Promise(resolve => resolve(function (instance, opts, done) { + done() + })), false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err, undefined) + }) +}) + +test('catch an error when loading a plugin when function is a Promise, which resolves to a function', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, new Promise(resolve => resolve(function (instance, opts, done) { + done(Error('ArbitraryError')) + })), false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err.message, 'ArbitraryError') + }) +}) + +test('successfully load a plugin when function is a Promise, which resolves to a function, which is wrapped in default', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, new Promise(resolve => resolve({ + default: function (instance, opts, done) { + done() + } + })), false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err, undefined) + }) +}) + +test('catch an error when loading a plugin when function is a Promise, which resolves to a function, which is wrapped in default', (t) => { + t.plan(1) + const app = boot({}) + + const plugin = new Plugin(app, new Promise(resolve => resolve({ + default: function (instance, opts, done) { + done(Error('ArbitraryError')) + } + })), false, 0) + + loadPlugin(app, plugin, function (err) { + t.equal(err.message, 'ArbitraryError') + }) +})