From 8f36ab698931a1b901d4cd50a0097336c470f842 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Wed, 21 Sep 2022 09:37:40 -0700 Subject: [PATCH 1/6] add option to encapsulate --- README.md | 23 +++++++++++++++++++++++ plugin.d.ts | 3 ++- plugin.js | 7 ++----- plugin.test-d.ts | 6 ++++-- test/test.js | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 32c2224..ebb1767 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,29 @@ module.exports = fp(plugin, { }) ``` +#### Encapsulate +You can optionally keep the plugin encapsulated. +```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..e05f818 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 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..247e6d8 100644 --- a/test/test.js +++ b/test/test.js @@ -237,3 +237,38 @@ 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.notOk(err) + t.ok(fastify.hasDecorator('accessible')) + t.ok(fastify.hasDecorator('alsoAccessible')) + t.notOk(fastify.hasDecorator('encapsulated')) + }) +}) From 61cb402dd2cdf8c27866021be7338a1cb1d72290 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Wed, 21 Sep 2022 15:05:51 -0700 Subject: [PATCH 2/6] don't coerce truthy values for `encapsulate` --- plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.js b/plugin.js index e05f818..d19a8e4 100644 --- a/plugin.js +++ b/plugin.js @@ -41,7 +41,7 @@ function plugin (fn, options = {}) { options.name = checkName(fn) + '-auto-' + count++ } - fn[Symbol.for('skip-override')] = !options.encapsulate + fn[Symbol.for('skip-override')] = options.encapsulate !== true fn[Symbol.for('fastify.display-name')] = options.name fn[Symbol.for('plugin-meta')] = options From 5a083e340c960f361740fc681ae930faa340a040 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Wed, 21 Sep 2022 15:05:58 -0700 Subject: [PATCH 3/6] update docs --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ebb1767..f6fad78 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,9 @@ module.exports = fp(plugin, { ``` #### Encapsulate -You can optionally keep the plugin encapsulated. +You can optionally keep the plugin encapsulated, which is the default behavior if you weren't using `fastify-plugin`. +This allows you to set the plugin's name and validate its dependencies without making the plugin accessible. +Read more about encapsulation [here](https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md). ```js const fp = require('fastify-plugin') From 934be7b6413924cf01e325ff147b982d723ca811 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Thu, 22 Sep 2022 08:28:59 -0700 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Manuel Spigolon --- README.md | 4 ++-- test/test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f6fad78..e297c77 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,9 @@ module.exports = fp(plugin, { ``` #### Encapsulate -You can optionally keep the plugin encapsulated, which is the default behavior if you weren't using `fastify-plugin`. + +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. -Read more about encapsulation [here](https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md). ```js const fp = require('fastify-plugin') diff --git a/test/test.js b/test/test.js index 247e6d8..f78ee71 100644 --- a/test/test.js +++ b/test/test.js @@ -266,7 +266,7 @@ test('should accept an option to encapsulate', t => { })) fastify.ready(err => { - t.notOk(err) + t.error(err) t.ok(fastify.hasDecorator('accessible')) t.ok(fastify.hasDecorator('alsoAccessible')) t.notOk(fastify.hasDecorator('encapsulated')) From ccf7879593b1b5c93de7d966f68dabf8c6972228 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Thu, 22 Sep 2022 08:32:17 -0700 Subject: [PATCH 5/6] add test --- test/test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/test.js b/test/test.js index f78ee71..32f5347 100644 --- a/test/test.js +++ b/test/test.js @@ -272,3 +272,18 @@ test('should accept an option to encapsulate', t => { 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") + }) +}) From 00d2bdd015a97c8b7f81b5598e1bfc8d46365cc3 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Wed, 12 Oct 2022 18:42:32 +0200 Subject: [PATCH 6/6] test: more use cases --- test/test.js | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/test/test.js b/test/test.js index 32f5347..01f8279 100644 --- a/test/test.js +++ b/test/test.js @@ -287,3 +287,95 @@ test('should check dependencies when encapsulated', t => { 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() + } + } +})