From 723af56b7a50d04c315b82d97312b3d95e66ca3e Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Tue, 1 Oct 2013 00:47:42 -0700 Subject: [PATCH 1/3] Plugin deps after() method --- lib/ext.js | 51 ++++++++++++++++++++++++++++++++++-------------- lib/pack.js | 34 ++++++++++++++++++++++++++------ lib/server.js | 13 +++++++++++- test/unit/ext.js | 4 ++-- 4 files changed, 78 insertions(+), 24 deletions(-) diff --git a/lib/ext.js b/lib/ext.js index bc2646466..640cb18fc 100755 --- a/lib/ext.js +++ b/lib/ext.js @@ -10,20 +10,15 @@ var Utils = require('./utils'); var internals = {}; -/* - Extension functions use the following signature: function (request, next) { next(); } -*/ - -module.exports = internals.Ext = function () { - - this._events = { - onRequest: null, // New request, before handing over to the router (allows changes to the request method, url, etc.) - onPreAuth: null, // After cookie parse and before authentication (skipped if state error) - onPostAuth: null, // After authentication (and payload processing) and before validation (skipped if auth or payload error) - onPreHandler: null, // After validation and body parsing, before route handler (skipped if auth or validation error) - onPostHandler: null, // After route handler returns, before sending response (skipped if onPreHandler not called) - onPreResponse: null // Before response is sent (always called) - }; + +// Extension functions use the following signature: function (request, next) { next(); } + +module.exports = internals.Ext = function (events) { + + this._events = {}; + for (var i = 0, il = events.length;i Date: Tue, 1 Oct 2013 11:29:51 -0700 Subject: [PATCH 2/3] preStart pack ext --- docs/Reference.md | 69 +++++++++++++++---- lib/ext.js | 36 +++------- lib/pack.js | 52 ++++++++++---- test/integration/pack.js | 42 ++++++++--- test/integration/pack/--afterErr/index.js | 16 +++++ test/integration/pack/--afterErr/package.json | 7 ++ test/integration/pack/--deps1/index.js | 10 ++- test/integration/pack/--deps2/index.js | 2 + .../node_modules/--inner/lib/index.js | 2 +- test/integration/pack/--test1/lib/index.js | 4 +- 10 files changed, 168 insertions(+), 72 deletions(-) create mode 100755 test/integration/pack/--afterErr/index.js create mode 100755 test/integration/pack/--afterErr/package.json diff --git a/docs/Reference.md b/docs/Reference.md index baeb8cc59..e6c63ffed 100755 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -96,7 +96,8 @@ - [`plugin.app`](#pluginapp) - [`plugin.events`](#pluginevents) - [`plugin.log(tags, [data, [timestamp]])`](#pluginlogtags-data-timestamp) - - [`plugin.dependency(deps)`](#plugindependencydeps) + - [`plugin.dependency(deps, [after])`](#plugindependencydeps-after) + - [`plugin.after(method)`](#pluginaftermethod) - [`plugin.views(options)`](#pluginviewsoptions) - [`plugin.helper(name, method, [options])`](#pluginhelpername-method-options) - [`plugin.helpers`](#pluginhelpers) @@ -108,8 +109,8 @@ - [Selectable methods and properties](#selectable-methods-and-properties) - [`plugin.select(labels)`](#pluginselectlabels) - [`plugin.length`](#pluginlength) - - [`plugin.api(key, value)`](#pluginapikey-value) - - [`plugin.api(obj)`](#pluginapiobj) + - [`plugin.expose(key, value)`](#pluginexposekey-value) + - [`plugin.expose(obj)`](#pluginexposeobj) - [`plugin.route(options)`](#pluginrouteoptions) - [`plugin.route(routes)`](#pluginrouteroutes) - [`plugin.state(name, [options])`](#pluginstatename-options) @@ -281,7 +282,7 @@ Each instance of the `Server` object have the following properties: - `uri` - a string with the following format: 'protocol://host:port' (e.g. 'http://example.com:8080'). - `listener` - the node HTTP server object. - `pack` - the [`Pack`](#hapipack) object the server belongs to (automatically assigned when creating a server instance directly). -- `plugins` - an object where each key is a plugin name and the value is the API registered by that plugin using [`plugin.api()`](#pluginapikey-value). +- `plugins` - an object where each key is a plugin name and the value are the exposed properties by that plugin using [`plugin.expose()`](#pluginexposekey-value). - `settings` - an object containing the [server options](#server-options) after applying the defaults. ### `Server` methods @@ -2579,7 +2580,7 @@ Registers a list of plugins where: - `err` - an error returned from `exports.register()`. Note that incorrect usage, bad configuration, missing permissions, or namespace conflicts (e.g. among routes, helpers, state) will throw an error and will not return a callback. -Batch registration is required when plugins declare a [dependency](#plugindependencydeps), so that all the required dependencies are loaded in +Batch registration is required when plugins declare a [dependency](#plugindependencydeps-after), so that all the required dependencies are loaded in a single transaction (internal order does not matter). ```javascript @@ -2828,7 +2829,7 @@ exports.register = function (plugin, options, next) { return plugins; }; - plugin.api('plugins', listPlugins); + plugin.expose('plugins', listPlugins); next(); }; ``` @@ -2946,18 +2947,58 @@ exports.register = function (plugin, options, next) { }; ``` -#### `plugin.dependency(deps)` +#### `plugin.dependency(deps, [after])` Declares a required dependency upon other plugins where: - `deps` - a single string or array of strings of plugin names which must be registered in order for this plugin to operate. Plugins listed must be registered in the same pack transaction to allow validation of the dependency requirements. Does not provide version dependency which should be implemented using [npm peer dependencies](http://blog.nodejs.org/2013/02/07/peer-dependencies/). +- `after` - an optional function called after all the specified dependencies have been registered and before the servers start. The function is only + called if the pack servers are started. If a circular dependency is created, the call will assert (e.g. two plugins each has an `after` function + to be called after the other). The function signature is `function(plugin, next)` where: + - `plugin` - the [plugin interface](#plugin-interface) object. + - `next` - the callback function the method must call to return control over to the application and complete the registration process. The function + signature is `function(err)` where: + - `err` - internal plugin error condition, which is returned back via the [`pack.start(callback)`](#packstartcallback) callback. A plugin + registration error is considered an unrecoverable event which should terminate the application. ```javascript exports.register = function (plugin, options, next) { - plugin.dependency('yar'); + plugin.dependency('yar', after); + next(); +}; + +var after = function (plugin, next) { + + // Additional plugin registration logic + next(); +}; +``` + +#### `plugin.after(method)` + +Add a method to be called after all the required plugins have been registered and before the servers start. The function is only +called if the pack servers are started. Arguments: + +- `after` - the method with signature `function(plugin, next)` where: + - `plugin` - the [plugin interface](#plugin-interface) object. + - `next` - the callback function the method must call to return control over to the application and complete the registration process. The function + signature is `function(err)` where: + - `err` - internal plugin error condition, which is returned back via the [`pack.start(callback)`](#packstartcallback) callback. A plugin + registration error is considered an unrecoverable event which should terminate the application. + +```javascript +exports.register = function (plugin, options, next) { + + plugin.after(after); + next(); +}; + +var after = function (plugin, next) { + + // Additional plugin registration logic next(); }; ``` @@ -3165,9 +3206,9 @@ exports.register = function (plugin, options, next) { }; ``` -#### `plugin.api(key, value)` +#### `plugin.expose(key, value)` -Adds an plugin API to the `server.plugins[name]` ('name' of plugin) object of each selected pack server where: +Exposes a property via the `server.plugins[name]` ('name' of plugin) object of each selected pack server where: - `key` - the key assigned (`server.plugins[name][key]`). - `value` - the value assigned. @@ -3175,22 +3216,22 @@ Adds an plugin API to the `server.plugins[name]` ('name' of plugin) object of ea ```javascript exports.register = function (plugin, options, next) { - plugin.api('util', function () { console.log('something'); }); + plugin.expose('util', function () { console.log('something'); }); next(); }; ``` -#### `plugin.api(obj)` +#### `plugin.expose(obj)` Merges a deep copy of an object into to the existing content of the `server.plugins[name]` ('name' of plugin) object of each selected pack server where: -- `obj` - the object merged into the API container. +- `obj` - the object merged into the exposed properties container. ```javascript exports.register = function (plugin, options, next) { - plugin.api({ util: function () { console.log('something'); } }); + plugin.expose({ util: function () { console.log('something'); } }); next(); }; ``` diff --git a/lib/ext.js b/lib/ext.js index 640cb18fc..9ec88c2d2 100755 --- a/lib/ext.js +++ b/lib/ext.js @@ -80,47 +80,27 @@ internals.Ext.prototype.invoke = function (request, event, callback) { return Utils.nextTick(callback)(); } - var log = request.log.bind(request); + var log = (request ? request.log.bind(request) : null); Async.forEachSeries(handlers, function (ext, next) { - request.context = (ext.plugin ? ext.plugin.context : undefined); + if (request) { + request.context = (ext.plugin ? ext.plugin.context : undefined); + } internals.Ext.runProtected(log, event, next, function (enter, exit) { enter(function () { - ext.func(request, exit); + ext.func(request || ext.plugin.root, exit); }); }); }, function (err) { - delete request.context; - - return callback(err); - }); -}; - - -internals.Ext.prototype.invokePlugin = function (event, callback) { - - var handlers = this._events[event]; - if (!handlers) { - return Utils.nextTick(callback)(); - } - - Async.forEachSeries(handlers, function (ext, next) { - - internals.Ext.runProtected(null, event, next, function (enter, exit) { - - enter(function () { - - ext.func(ext.plugin.root, exit); - }); - }); - }, - function (err) { + if (request) { + delete request.context; + } return callback(err); }); diff --git a/lib/pack.js b/lib/pack.js index cd9099668..c51a5b27e 100755 --- a/lib/pack.js +++ b/lib/pack.js @@ -46,6 +46,7 @@ exports = module.exports = internals.Pack = function (options) { this._helpers = {}; // Helper functions this.list = {}; // Loaded plugins by name + this.plugins = {}; // Exposed plugin properties by name this.events = new Events.EventEmitter(); // Consolidated subscription to all servers' events this.app = options.app || {}; @@ -187,24 +188,15 @@ internals.Pack.prototype._register = function (plugin, permissions, options, cal return step(labels, selection.index); }, - api: function (/* key, value */) { + _expose: function (/* key, value */) { - var key = (arguments.length === 2 ? arguments[0] : null); - var value = (arguments.length === 2 ? arguments[1] : arguments[0]); - - selection.servers.forEach(function (server) { - - server.plugins[plugin.name] = server.plugins[plugin.name] || {}; - if (key) { - server.plugins[plugin.name][key] = value; - } - else { - Utils.merge(server.plugins[plugin.name], value); - } - }); + internals.expose(selection.servers, plugin, arguments); } }; + methods.expose = methods._expose; + methods.api = methods.expose; // Backwards compatibility + if (permissions.route) { methods.route = function (options) { @@ -244,6 +236,13 @@ internals.Pack.prototype._register = function (plugin, permissions, options, cal root.hapi = self.hapi; root.app = self.app; root.path = plugin.path; + root.plugins = self.plugins; + + root.expose = function (/* key, value */) { + + root._expose.apply(null, arguments); + internals.expose([self], plugin, arguments); + }; root.log = function (tags, data, timestamp) { @@ -268,6 +267,11 @@ internals.Pack.prototype._register = function (plugin, permissions, options, cal } }; + root.after = function (method) { + + self._ext._add('onPreStart', method, {}, env); + }; + root.loader = function (requireFunc) { Utils.assert(!requireFunc || typeof requireFunc === 'function', 'Argument must be null or valid function'); @@ -337,6 +341,24 @@ internals.Pack.prototype._register = function (plugin, permissions, options, cal }; +internals.expose = function (dests, plugin, args) { + + var key = (args.length === 2 ? args[0] : null); + var value = (args.length === 2 ? args[1] : args[0]); + + dests.forEach(function (dest) { + + dest.plugins[plugin.name] = dest.plugins[plugin.name] || {}; + if (key) { + dest.plugins[plugin.name][key] = value; + } + else { + Utils.merge(dest.plugins[plugin.name], value); + } + }); +}; + + internals.Pack.prototype._select = function (labels, subset) { var self = this; @@ -574,7 +596,7 @@ internals.Pack.prototype.start = function (callback) { var self = this; - this._ext.invokePlugin('onPreStart', function (err) { + this._ext.invoke(null, 'onPreStart', function (err) { if (err) { if (callback) { diff --git a/test/integration/pack.js b/test/integration/pack.js index df77c51e8..fb1ff7201 100755 --- a/test/integration/pack.js +++ b/test/integration/pack.js @@ -441,9 +441,9 @@ describe('Pack', function () { it('adds multiple ext functions with dependencies', function (done) { var pack = new Hapi.Pack(); - pack.server({ labels: ['a', 'b'] }); - pack.server({ labels: ['a', 'c'] }); - pack.server({ labels: ['c', 'b'] }); + pack.server(0, { labels: ['a', 'b'] }); + pack.server(0, { labels: ['a', 'c'] }); + pack.server(0, { labels: ['c', 'b'] }); var handler = function () { @@ -458,18 +458,24 @@ describe('Pack', function () { expect(err).to.not.exist; - pack._servers[0].inject({ method: 'GET', url: '/' }, function (res) { + pack.start(function (err) { + + expect(err).to.not.exist; + expect(pack.plugins['--deps1'].breaking).to.equal('bad'); + + pack._servers[0].inject({ method: 'GET', url: '/' }, function (res) { - expect(res.result).to.equal('|2|1|') + expect(res.result).to.equal('|2|1|') - pack._servers[1].inject({ method: 'GET', url: '/' }, function (res) { + pack._servers[1].inject({ method: 'GET', url: '/' }, function (res) { - expect(res.result).to.equal('|3|1|') + expect(res.result).to.equal('|3|1|') - pack._servers[2].inject({ method: 'GET', url: '/' }, function (res) { + pack._servers[2].inject({ method: 'GET', url: '/' }, function (res) { - expect(res.result).to.equal('|3|2|') - done(); + expect(res.result).to.equal('|3|2|') + done(); + }); }); }); }); @@ -578,6 +584,20 @@ describe('Pack', function () { }); }); + it('fails to start server when after method fails', function (done) { + + var server = new Hapi.Server(); + server.pack.require('./pack/--afterErr', function (err) { + + expect(err).to.not.exist; + server.start(function (err) { + + expect(err).to.exist; + done(); + }); + }); + }); + it('uses plugin cache interface', function (done) { var plugin = { @@ -586,7 +606,7 @@ describe('Pack', function () { register: function (plugin, options, next) { var cache = plugin.cache({ expiresIn: 10 }); - plugin.api({ + plugin.expose({ get: function (key, callback) { cache.get(key, function (err, value) { diff --git a/test/integration/pack/--afterErr/index.js b/test/integration/pack/--afterErr/index.js new file mode 100755 index 000000000..1b8c26b2b --- /dev/null +++ b/test/integration/pack/--afterErr/index.js @@ -0,0 +1,16 @@ +// Declare internals + +var internals = {}; + + +// Plugin registration + +exports.register = function (plugin, options, next) { + + plugin.after(function (plugin, finish) { + + finish(new Error('Not in the mood')); + }); + + return next(); +}; diff --git a/test/integration/pack/--afterErr/package.json b/test/integration/pack/--afterErr/package.json new file mode 100755 index 000000000..cc488f92f --- /dev/null +++ b/test/integration/pack/--afterErr/package.json @@ -0,0 +1,7 @@ +{ + "name": "--afterErr", + "description": "Test plugin module", + "version": "0.0.1", + "private": true, + "main": "index" +} diff --git a/test/integration/pack/--deps1/index.js b/test/integration/pack/--deps1/index.js index d546db489..9af5ad72b 100755 --- a/test/integration/pack/--deps1/index.js +++ b/test/integration/pack/--deps1/index.js @@ -7,7 +7,7 @@ var internals = {}; exports.register = function (plugin, options, next) { - plugin.dependency('--deps2'); + plugin.dependency('--deps2', internals.after); plugin.select('a').ext('onRequest', function (request, cont) { @@ -18,3 +18,11 @@ exports.register = function (plugin, options, next) { return next(); }; + + +internals.after = function (plugin, next) { + + plugin.expose('breaking', plugin.plugins['--deps2'].breaking); + + next(); +}; diff --git a/test/integration/pack/--deps2/index.js b/test/integration/pack/--deps2/index.js index 1edbf21e0..aa611b3bf 100755 --- a/test/integration/pack/--deps2/index.js +++ b/test/integration/pack/--deps2/index.js @@ -14,5 +14,7 @@ exports.register = function (plugin, options, next) { cont(); }, { after: '--deps3', before: '--deps1' }); + plugin.expose('breaking', 'bad'); + return next(); }; diff --git a/test/integration/pack/--loader/node_modules/--inner/lib/index.js b/test/integration/pack/--loader/node_modules/--inner/lib/index.js index 33e448f7d..123a1f47d 100755 --- a/test/integration/pack/--loader/node_modules/--inner/lib/index.js +++ b/test/integration/pack/--loader/node_modules/--inner/lib/index.js @@ -7,7 +7,7 @@ var internals = {}; exports.register = function (plugin, options, next) { - plugin.api('way-down', 42); + plugin.expose('way-down', 42); next(); }; diff --git a/test/integration/pack/--test1/lib/index.js b/test/integration/pack/--test1/lib/index.js index dc8449be0..15879065c 100755 --- a/test/integration/pack/--test1/lib/index.js +++ b/test/integration/pack/--test1/lib/index.js @@ -8,8 +8,8 @@ var internals = {}; exports.register = function (plugin, options, next) { plugin.select('test').route({ path: '/test1', method: 'GET', handler: function () { this.reply('testing123' + ((plugin.app && plugin.app.my) || '')); } }); - plugin.api(internals.math); - plugin.api('glue', internals.text.glue); + plugin.expose(internals.math); + plugin.expose('glue', internals.text.glue); return next(); }; From f03c1dc9180169760153c9ba2b787b28e51ccfb8 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Tue, 1 Oct 2013 11:47:58 -0700 Subject: [PATCH 3/3] clarify plugin.plugins --- docs/Reference.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/Reference.md b/docs/Reference.md index f562871cd..7838a1495 100755 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -95,6 +95,7 @@ - [`plugin.hapi`](#pluginhapi) - [`plugin.app`](#pluginapp) - [`plugin.events`](#pluginevents) + - [`plugin.plugins`](#pluginplugins) - [`plugin.log(tags, [data, [timestamp]])`](#pluginlogtags-data-timestamp) - [`plugin.dependency(deps, [after])`](#plugindependencydeps-after) - [`plugin.after(method)`](#pluginaftermethod) @@ -2949,6 +2950,19 @@ exports.register = function (plugin, options, next) { }; ``` +#### `plugin.plugins` + +An object where each key is a plugin name and the value are the exposed properties by that plugin using [`plugin.expose()`](#pluginexposekey-value) +when called at the plugin root level (without calling `plugin.select()`). + +```javascript +exports.register = function (plugin, options, next) { + + console.log(plugin.plugins.example.key); + next(); +}; +``` + #### `plugin.log(tags, [data, [timestamp]])` Emits a `'log'` event on the `pack.events` emitter using the same interface as [`server.log()`](#serverlogtags-data-timestamp). @@ -3222,9 +3236,10 @@ exports.register = function (plugin, options, next) { #### `plugin.expose(key, value)` -Exposes a property via the `server.plugins[name]` ('name' of plugin) object of each selected pack server where: +Exposes a property via `plugin.plugins[name]` (if added to the plugin root without first calling `plugin.select()`) and `server.plugins[name]` +('name' of plugin) object of each selected pack server where: -- `key` - the key assigned (`server.plugins[name][key]`). +- `key` - the key assigned (`server.plugins[name][key]` or `plugin.plugins[name][key]`). - `value` - the value assigned. ```javascript @@ -3237,8 +3252,8 @@ exports.register = function (plugin, options, next) { #### `plugin.expose(obj)` -Merges a deep copy of an object into to the existing content of the `server.plugins[name]` ('name' of plugin) object of each -selected pack server where: +Merges a deep copy of an object into to the existing content of `plugin.plugins[name]` (if added to the plugin root without first calling +`plugin.select()`) and `server.plugins[name]` ('name' of plugin) object of each selected pack server where: - `obj` - the object merged into the exposed properties container.