diff --git a/README.md b/README.md index 32c2224..e297c77 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,31 @@ module.exports = fp(plugin, { }) ``` +#### Encapsulate + +By default, `fastify-plugin` breaks the [encapsulation](https://github.com/fastify/fastify/blob/HEAD/docs/Reference/Encapsulation.md) but you can optionally keep the plugin encapsulated. +This allows you to set the plugin's name and validate its dependencies without making the plugin accessible. +```js +const fp = require('fastify-plugin') + +function plugin (fastify, opts, next) { + // the decorator is not accessible outside this plugin + fastify.decorate('util', function() {}) + next() +} + +module.exports = fp(plugin, { + name: 'my-encapsulated-plugin', + fastify: '4.x', + decorators: { + fastify: ['plugin1', 'plugin2'], + reply: ['compress'] + }, + dependencies: ['plugin1-name', 'plugin2-name'], + encapsulate: true +}) +``` + #### Bundlers and Typescript `fastify-plugin` adds a `.default` and `[name]` property to the passed in function. The type definition would have to be updated to leverage this. diff --git a/plugin.d.ts b/plugin.d.ts index be6e583..1b33011 100644 --- a/plugin.d.ts +++ b/plugin.d.ts @@ -65,7 +65,8 @@ export interface PluginMetadata { request?: (string | symbol)[] }, /** The plugin dependencies */ - dependencies?: string[] + dependencies?: string[], + encapsulate?: boolean } // Exporting PluginOptions for backward compatibility after renaming it to PluginMetadata diff --git a/plugin.js b/plugin.js index a298df1..d19a8e4 100644 --- a/plugin.js +++ b/plugin.js @@ -18,10 +18,6 @@ function plugin (fn, options = {}) { ) } - fn[Symbol.for('skip-override')] = true - - const pluginName = (options && options.name) || checkName(fn) - if (typeof options === 'string') { options = { fastify: options @@ -42,9 +38,10 @@ function plugin (fn, options = {}) { if (!options.name) { autoName = true - options.name = pluginName + '-auto-' + count++ + options.name = checkName(fn) + '-auto-' + count++ } + fn[Symbol.for('skip-override')] = options.encapsulate !== true fn[Symbol.for('fastify.display-name')] = options.name fn[Symbol.for('plugin-meta')] = options diff --git a/plugin.test-d.ts b/plugin.test-d.ts index e77e1d1..40e346d 100644 --- a/plugin.test-d.ts +++ b/plugin.test-d.ts @@ -29,7 +29,8 @@ expectAssignable(fp(pluginCallback, { reply: [ '', testSymbol ], request: [ '', testSymbol ] }, - dependencies: [ '' ] + dependencies: [ '' ], + encapsulate: true })) const pluginCallbackWithOptions: FastifyPluginCallback = (fastify, options, next) => { @@ -66,7 +67,8 @@ expectAssignable(fp(pluginAsync, { reply: [ '', testSymbol ], request: [ '', testSymbol ] }, - dependencies: [ '' ] + dependencies: [ '' ], + encapsulate: true })) const pluginAsyncWithOptions: FastifyPluginAsync = async (fastify, options) => { diff --git a/test/test.js b/test/test.js index fc75588..01f8279 100644 --- a/test/test.js +++ b/test/test.js @@ -237,3 +237,145 @@ test('should check fastify dependency graph - decorateReply', t => { t.equal(err.message, "The decorator 'plugin2' required by 'test' is not present in Reply") }) }) + +test('should accept an option to encapsulate', t => { + t.plan(4) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, next) => { + fastify.decorate('accessible', true) + next() + }, { + name: 'accessible-plugin' + })) + + fastify.register(fp((fastify, opts, next) => { + fastify.decorate('alsoAccessible', true) + next() + }, { + name: 'accessible-plugin2', + encapsulate: false + })) + + fastify.register(fp((fastify, opts, next) => { + fastify.decorate('encapsulated', true) + next() + }, { + name: 'encapsulated-plugin', + encapsulate: true + })) + + fastify.ready(err => { + t.error(err) + t.ok(fastify.hasDecorator('accessible')) + t.ok(fastify.hasDecorator('alsoAccessible')) + t.notOk(fastify.hasDecorator('encapsulated')) + }) +}) + +test('should check dependencies when encapsulated', t => { + t.plan(1) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, next) => next(), { + name: 'test', + dependencies: ['missing-dependency-name'], + encapsulate: true + })) + + fastify.ready(err => { + t.equal(err.message, "The dependency 'missing-dependency-name' of plugin 'test' is not registered") + }) +}) + +test('should check version when encapsulated', t => { + t.plan(1) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, next) => next(), { + name: 'test', + fastify: '<=2.10.0', + encapsulate: true + })) + + fastify.ready(err => { + t.match(err.message, /fastify-plugin: test - expected '<=2.10.0' fastify version, '\d.\d.\d' is installed/) + }) +}) + +test('should check decorators when encapsulated', t => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', 'foo') + + fastify.register(fp((fastify, opts, next) => next(), { + fastify: '4.x', + name: 'test', + encapsulate: true, + decorators: { fastify: ['plugin1', 'plugin2'] } + })) + + fastify.ready(err => { + t.equal(err.message, "The decorator 'plugin2' required by 'test' is not present in Fastify") + }) +}) + +test('plugin name when encapsulated', async t => { + const fastify = Fastify() + + fastify.register(function plugin (instance, opts, next) { + next() + }) + + fastify.register(fp(getFn('hello'), { + fastify: '4.x', + name: 'hello', + encapsulate: true + })) + + fastify.register(function plugin (fastify, opts, next) { + fastify.register(fp(getFn('deep'), { + fastify: '4.x', + name: 'deep', + encapsulate: true + })) + + fastify.register(fp(function genericPlugin (fastify, opts, next) { + t.equal(fastify.pluginName, 'deep-deep', 'should be deep-deep') + + fastify.register(fp(getFn('deep-deep-deep'), { + fastify: '4.x', + name: 'deep-deep-deep', + encapsulate: true + })) + + fastify.register(fp(getFn('deep-deep -> not-encapsulated-2'), { + fastify: '4.x', + name: 'not-encapsulated-2' + })) + + next() + }, { + fastify: '4.x', + name: 'deep-deep', + encapsulate: true + })) + + fastify.register(fp(getFn('plugin -> not-encapsulated'), { + fastify: '4.x', + name: 'not-encapsulated' + })) + + next() + }) + + await fastify.ready() + + function getFn (expectedName) { + return function genericPlugin (fastify, opts, next) { + t.equal(fastify.pluginName, expectedName, `should be ${expectedName}`) + next() + } + } +})