diff --git a/API.md b/API.md
index 865cbefd9..36dfac6b2 100755
--- a/API.md
+++ b/API.md
@@ -1,262 +1,3 @@
-# v18.4.x API Reference
-
-
-
-- [Server](#server)
- - [`server([options])`](#server())
- - [Server options](#server.options)
- - [`server.options.address`](#server.options.address)
- - [`server.options.app`](#server.options.app)
- - [`server.options.autoListen`](#server.options.autolisten)
- - [`server.options.cache`](#server.options.cache)
- - [`server.options.compression`](#server.options.compression)
- - [`server.options.compression.minBytes`](#server.options.compression.minBytes)
- - [`server.options.debug`](#server.options.debug)
- - [`server.options.host`](#server.options.host)
- - [`server.options.listener`](#server.options.listener)
- - [`server.options.load`](#server.options.load)
- - [`server.options.mime`](#server.options.mime)
- - [`server.options.operations`](#server.options.operations)
- - [`server.options.plugins`](#server.options.plugins)
- - [`server.options.port`](#server.options.port)
- - [`server.options.query`](#server.options.query)
- - [`server.options.query.parser`](#server.options.query.parser)
- - [`server.options.router`](#server.options.router)
- - [`server.options.routes`](#server.options.routes)
- - [`server.options.state`](#server.options.state)
- - [`server.options.tls`](#server.options.tls)
- - [`server.options.uri`](#server.options.uri)
- - [Server properties](#server-properties)
- - [`server.app`](#server.app)
- - [`server.auth.api`](#server.auth.api)
- - [`server.auth.settings.default`](#server.auth.settings.default)
- - [`server.decorations`](#server.decorations)
- - [`server.events`](#server.events)
- - [`'log'` Event](#server.events.log)
- - [`'request'` Event](#server.events.request)
- - [`'response'` Event](#server.events.response)
- - [`'route'` Event](#server.events.route)
- - [`'start'` Event](#server.events.start)
- - [`'stop'` Event](#server.events.stop)
- - [`server.info`](#server.info)
- - [`server.listener`](#server.listener)
- - [`server.load`](#server.load)
- - [`server.methods`](#server.methods)
- - [`server.mime`](#server.mime)
- - [`server.plugins`](#server.plugins)
- - [`server.realm`](#server.realm)
- - [`server.registrations`](#server.registrations)
- - [`server.settings`](#server.settings)
- - [`server.states`](#server.states)
- - [`server.states.settings`](#server.states.settings)
- - [`server.states.cookies`](#server.states.cookies)
- - [`server.states.names`](#server.states.names)
- - [`server.type`](#server.type)
- - [`server.version`](#server.version)
- - [`server.auth.default(options)`](#server.auth.default())
- - [`server.auth.scheme(name, scheme)`](#server.auth.scheme())
- - [Authentication scheme](#authentication-scheme)
- - [`server.auth.strategy(name, scheme, [options])`](#server.auth.strategy())
- - [`await server.auth.test(strategy, request)`](#server.auth.test())
- - [`await server.auth.verify(request)`](#server.auth.verify())
- - [`server.bind(context)`](#server.bind())
- - [`server.cache(options)`](#server.cache())
- - [`await server.cache.provision(options)`](#server.cache.provision())
- - [`server.control(server)`](#server.control())
- - [`server.decoder(encoding, decoder)`](#server.decoder())
- - [`server.decorate(type, property, method, [options])`](#server.decorate())
- - [`server.dependency(dependencies, [after])`](#server.dependency())
- - [`server.encoder(encoding, encoder)`](#server.encoder())
- - [`server.event(events)`](#server.event())
- - [`await server.events.emit(criteria, data)`](#server.events.emit())
- - [`server.events.on(criteria, listener)`](#server.events.on())
- - [`server.events.once(criteria, listener)`](#server.events.once())
- - [`await server.events.once(criteria)`](#server.events.once.await())
- - [`server.expose(key, value)`](#server.expose())
- - [`server.expose(obj)`](#server.expose.obj())
- - [`server.ext(events)`](#server.ext())
- - [`server.ext(event, method, [options])`](#server.ext.args())
- - [`await server.initialize()`](#server.initialize())
- - [`await server.inject(options)`](#server.inject())
- - [`server.log(tags, [data, [timestamp]])`](#server.log())
- - [`server.lookup(id)`](#server.lookup())
- - [`server.match(method, path, [host])`](#server.match())
- - [`server.method(name, method, [options])`](#server.method())
- - [`server.method(methods)`](#server.method.array())
- - [`server.path(relativeTo)`](#server.path())
- - [`await server.register(plugins, [options])`](#server.register())
- - [`server.route(route)`](#server.route())
- - [Path parameters](#path-parameters)
- - [Path matching order](#path-matching-order)
- - [Catch all route](#catch-all-route)
- - [`server.rules(processor, [options])`](#server.rules())
- - [`await server.start()`](#server.start())
- - [`server.state(name, [options])`](#server.state())
- - [`server.states.add(name, [options])`](#server.states.add())
- - [`await server.states.format(cookies)`](#server.states.format())
- - [`await server.states.parse(header)`](#server.states.parse())
- - [`await server.stop([options])`](#server.stop())
- - [`server.table([host])`](#server.table())
- - [`server.validator(validator)`](#server.validator())
-- [Route options](#route-options)
- - [`route.options.app`](#route.options.app)
- - [`route.options.auth`](#route.options.auth)
- - [`route.options.auth.access`](#route.options.auth.access)
- - [`route.options.auth.access.scope`](#route.options.auth.access.scope)
- - [`route.options.auth.access.entity`](#route.options.auth.access.entity)
- - [`route.options.auth.mode`](#route.options.auth.mode)
- - [`route.options.auth.payload`](#route.options.auth.payload)
- - [`route.options.auth.strategies`](#route.options.auth.strategies)
- - [`route.options.auth.strategy`](#route.options.auth.strategy)
- - [`route.options.bind`](#route.options.bind)
- - [`route.options.cache`](#route.options.cache)
- - [`route.options.compression`](#route.options.compression)
- - [`route.options.cors`](#route.options.cors)
- - [`route.options.description`](#route.options.description)
- - [`route.options.ext`](#route.options.ext)
- - [`route.options.files`](#route.options.files)
- - [`route.options.handler`](#route.options.handler)
- - [`route.options.id`](#route.options.id)
- - [`route.options.isInternal`](#route.options.isInternal)
- - [`route.options.json`](#route.options.json)
- - [`route.options.jsonp`](#route.options.jsonp)
- - [`route.options.log`](#route.options.log)
- - [`route.options.notes`](#route.options.notes)
- - [`route.options.payload`](#route.options.payload)
- - [`route.options.payload.allow`](#route.options.payload.allow)
- - [`route.options.payload.compression`](#route.options.payload.compression)
- - [`route.options.payload.defaultContentType`](#route.options.payload.defaultContentType)
- - [`route.options.payload.failAction`](#route.options.payload.failAction)
- - [`route.options.payload.maxBytes`](#route.options.payload.maxBytes)
- - [`route.options.payload.multipart`](#route.options.payload.multipart)
- - [`route.options.payload.output`](#route.options.payload.output)
- - [`route.options.payload.override`](#route.options.payload.override)
- - [`route.options.payload.parse`](#route.options.payload.parse)
- - [`route.options.payload.protoAction`](#route.options.payload.protoAction)
- - [`route.options.payload.timeout`](#route.options.payload.timeout)
- - [`route.options.payload.uploads`](#route.options.payload.uploads)
- - [`route.options.plugins`](#route.options.plugins)
- - [`route.options.pre`](#route.options.pre)
- - [`route.options.response`](#route.options.response)
- - [`route.options.response.disconnectStatusCode`](#route.options.response.disconnectStatusCode)
- - [`route.options.response.emptyStatusCode`](#route.options.response.emptyStatusCode)
- - [`route.options.response.failAction`](#route.options.response.failAction)
- - [`route.options.response.modify`](#route.options.response.modify)
- - [`route.options.response.options`](#route.options.response.options)
- - [`route.options.response.ranges`](#route.options.response.ranges)
- - [`route.options.response.sample`](#route.options.response.sample)
- - [`route.options.response.schema`](#route.options.response.schema)
- - [`route.options.response.status`](#route.options.response.status)
- - [`route.options.rules`](#route.options.rules)
- - [`route.options.security`](#route.options.security)
- - [`route.options.state`](#route.options.state)
- - [`route.options.tags`](#route.options.tags)
- - [`route.options.timeout`](#route.options.timeout)
- - [`route.options.timeout.server`](#route.options.timeout.server)
- - [`route.options.timeout.socket`](#route.options.timeout.socket)
- - [`route.options.validate`](#route.options.validate)
- - [`route.options.validate.errorFields`](#route.options.validate.errorFields)
- - [`route.options.validate.failAction`](#route.options.validate.failAction)
- - [`route.options.validate.headers`](#route.options.validate.headers)
- - [`route.options.validate.options`](#route.options.validate.options)
- - [`route.options.validate.params`](#route.options.validate.params)
- - [`route.options.validate.payload`](#route.options.validate.payload)
- - [`route.options.validate.query`](#route.options.validate.query)
- - [`route.options.validate.state`](#route.options.validate.state)
-- [Request lifecycle](#request-lifecycle)
- - [Lifecycle methods](#lifecycle-methods)
- - [Lifecycle workflow](#lifecycle-workflow)
- - [Takeover response](#takeover-response)
- - [`failAction` configuration](#lifecycle-failAction)
- - [Errors](#errors)
- - [Error transformation](#error-transformation)
- - [Response Toolkit](#response-toolkit)
- - [Toolkit properties](#toolkit-properties)
- - [`h.abandon`](#h.abandon)
- - [`h.close`](#h.close)
- - [`h.context`](#h.context)
- - [`h.continue`](#h.continue)
- - [`h.realm`](#h.realm)
- - [`h.request`](#h.request)
- - [`h.authenticated(data)`](#h.authenticated())
- - [`h.entity(options)`](#h.entity())
- - [`h.redirect(uri)`](#h.redirect())
- - [`h.response([value])`](#h.response())
- - [`h.state(name, value, [options])`](#h.state())
- - [`h.unauthenticated(error, [data])`](#h.unauthenticated())
- - [`h.unstate(name, [options])`](#h.unstate())
- - [Response object](#response-object)
- - [Response properties](#response-properties)
- - [`response.app`](#response.app)
- - [`response.events`](#response.events)
- - [`response.headers`](#response.headers)
- - [`response.plugins`](#response.plugins)
- - [`response.settings`](#response.settings)
- - [`response.settings.passThrough`](#response.settings.passThrough)
- - [`response.settings.stringify`](#response.settings.stringify)
- - [`response.settings.ttl`](#response.settings.ttl)
- - [`response.settings.varyEtag`](#response.settings.varyEtag)
- - [`response.source`](#response.source)
- - [`response.statusCode`](#response.statusCode)
- - [`response.variety`](#response.variety)
- - [`response.bytes(length)`](#response.bytes())
- - [`response.charset(charset)`](#response.charset())
- - [`response.code(statusCode)`](#response.code())
- - [`response.message(httpMessage)`](#response.message())
- - [`response.compressed(encoding)`](#response.compressed())
- - [`response.created(uri)`](#response.created())
- - [`response.encoding(encoding)`](#response.encoding())
- - [`response.etag(tag, options)`](#response.etag())
- - [`response.header(name, value, options)`](#response.header())
- - [`response.location(uri)`](#response.location())
- - [`response.redirect(uri)`](#response.redirect())
- - [`response.replacer(method)`](#response.replacer())
- - [`response.spaces(count)`](#response.spaces())
- - [`response.state(name, value, [options])`](#response.state())
- - [`response.suffix(suffix)`](#response.suffix())
- - [`response.ttl(msec)`](#response.ttl())
- - [`response.type(mimeType)`](#response.type())
- - [`response.unstate(name, [options])`](#response.unstate())
- - [`response.vary(header)`](#response.vary())
- - [`response.takeover()`](#response.takeover())
- - [`response.temporary(isTemporary)`](#response.temporary())
- - [`response.permanent(isPermanent)`](#response.permanent())
- - [`response.rewritable(isRewritable)`](#response.rewritable())
-- [Request](#request)
- - [Request properties](#request-properties)
- - [`request.app`](#request.app)
- - [`request.auth`](#request.auth)
- - [`request.events`](#request.events)
- - [`request.headers`](#request.headers)
- - [`request.info`](#request.info)
- - [`request.logs`](#request.logs)
- - [`request.method`](#request.method)
- - [`request.mime`](#request.mime)
- - [`request.orig`](#request.orig)
- - [`request.params`](#request.params)
- - [`request.paramsArray`](#request.paramsArray)
- - [`request.path`](#request.path)
- - [`request.payload`](#request.payload)
- - [`request.plugins`](#request.plugins)
- - [`request.pre`](#request.pre)
- - [`request.response`](#request.response)
- - [`request.preResponses`](#request.preResponses)
- - [`request.query`](#request.query)
- - [`request.raw`](#request.raw)
- - [`request.route`](#request.route)
- - [`request.server`](#request.server)
- - [`request.state`](#request.state)
- - [`request.url`](#request.url)
- - [`request.generateResponse(source, [options])`](#request.generateResponse())
- - [`request.active()`](#request.active())
- - [`request.log(tags, [data])`](#request.log())
- - [`request.route.auth.access(request)`](#request.route.auth.access())
- - [`request.setMethod(method)`](#request.setMethod())
- - [`request.setUrl(url, [stripTrailingSlash]`](#request.setUrl())
-- [Plugins](#plugins)
-
-
-
## Server
The server object is the main application container. The server manages all incoming requests
@@ -1919,12 +1660,17 @@ async function example() {
}
```
-### `server.expose(key, value)`
+### `server.expose(key, value, [options])`
Used within a plugin to expose a property via [`server.plugins[name]`](#server.plugins) where:
- `key` - the key assigned ([`server.plugins[name][key]`](#server.plugins)).
- `value` - the value assigned.
+- `options` - optional settings:
+ - `scope` - controls how to handle the presence of a plugin scope in the name (e.g. `@hapi/test`):
+ - `false` - the scope is removed (e.g. `@hapi/test` is changed to `test` under `server.plugins`). This is the default.
+ - `true` - the scope is retained as-is (e.g. `@hapi/test` is used as `server.plugins['@hapi/test']`).
+ - `'underscore'` - the scope is rewritten (e.g. `@hapi/test` is used as `server.plugins.hapi__test`).
Return value: none.
diff --git a/lib/server.js b/lib/server.js
index 430affcfd..0ba7a60c6 100755
--- a/lib/server.js
+++ b/lib/server.js
@@ -64,7 +64,7 @@ internals.Server = class {
modifiers: {
route: {}
},
- parent: (parent ? parent.realm : null),
+ parent: parent ? parent.realm : null,
plugin: name,
pluginOptions: {},
plugins: {},
@@ -218,11 +218,20 @@ internals.Server = class {
this._core.events.registerEvent(event);
}
- expose(key, value) {
+ expose(key, value, options = {}) {
Hoek.assert(this.realm.plugin, 'Cannot call expose() outside of a plugin');
- const plugin = this.realm.plugin;
+ let plugin = this.realm.plugin;
+ if (plugin[0] === '@' &&
+ options.scope !== true) {
+
+ plugin = plugin.replace(/^\@([^\/]+)\//, ($0, $1) => {
+
+ return !options.scope ? '' : `${$1}__`;
+ });
+ }
+
this._core.plugins[plugin] = this._core.plugins[plugin] || {};
if (typeof key === 'string') {
@@ -294,7 +303,7 @@ internals.Server = class {
delete settings.plugins;
delete settings.allowInternals;
- settings.authority = settings.authority || (this._core.info.host + ':' + this._core.info.port);
+ settings.authority = settings.authority || this._core.info.host + ':' + this._core.info.port;
}
Hoek.assert(!options.credentials, 'options.credentials no longer supported (use options.auth)');
diff --git a/test/server.js b/test/server.js
index f4c66e160..81098b1c5 100755
--- a/test/server.js
+++ b/test/server.js
@@ -826,6 +826,78 @@ describe('Server', () => {
expect(server.plugins.test1.add(1, 3)).to.equal(4);
expect(server.plugins.test1.glue('1', '3')).to.equal('13');
});
+
+ it('exposes an api (scope without scope)', async () => {
+
+ const server = Hapi.server();
+
+ const plugin = {
+ name: 'test1',
+ version: '1.0.0',
+ register: function (srv, options) {
+
+ srv.expose('x', { y: 1 }, { scope: true });
+ }
+ };
+
+ await server.register(plugin);
+ expect(server.plugins.test1.x.y).to.equal(1);
+ expect(server.registrations).to.equal({ test1: { version: '1.0.0', name: 'test1', options: undefined } });
+ });
+
+ it('exposes an api (drops scope by default)', async () => {
+
+ const server = Hapi.server();
+
+ const plugin = {
+ name: '@hapi/test1',
+ version: '1.0.0',
+ register: function (srv, options) {
+
+ srv.expose('x', { y: 1 });
+ }
+ };
+
+ await server.register(plugin);
+ expect(server.plugins.test1.x.y).to.equal(1);
+ expect(server.registrations).to.equal({ '@hapi/test1': { version: '1.0.0', name: '@hapi/test1', options: undefined } });
+ });
+
+ it('exposes an api (keeps scope)', async () => {
+
+ const server = Hapi.server();
+
+ const plugin = {
+ name: '@hapi/test1',
+ version: '1.0.0',
+ register: function (srv, options) {
+
+ srv.expose('x', { y: 1 }, { scope: true });
+ }
+ };
+
+ await server.register(plugin);
+ expect(server.plugins['@hapi/test1'].x.y).to.equal(1);
+ expect(server.registrations).to.equal({ '@hapi/test1': { version: '1.0.0', name: '@hapi/test1', options: undefined } });
+ });
+
+ it('exposes an api (rewrites scope)', async () => {
+
+ const server = Hapi.server();
+
+ const plugin = {
+ name: '@hapi/test1',
+ version: '1.0.0',
+ register: function (srv, options) {
+
+ srv.expose('x', { y: 1 }, { scope: 'underscore' });
+ }
+ };
+
+ await server.register(plugin);
+ expect(server.plugins.hapi__test1.x.y).to.equal(1);
+ expect(server.registrations).to.equal({ '@hapi/test1': { version: '1.0.0', name: '@hapi/test1', options: undefined } });
+ });
});
describe('ext()', () => {