Skip to content

Commit

Permalink
feat: add option to encapsulate (#199)
Browse files Browse the repository at this point in the history
* add option to encapsulate

* don't coerce truthy values for `encapsulate`

* update docs

* Apply suggestions from code review

Co-authored-by: Manuel Spigolon <behemoth89@gmail.com>

* add test

* test: more use cases

Co-authored-by: Manuel Spigolon <behemoth89@gmail.com>
  • Loading branch information
dwickern and Eomm committed Oct 13, 2022
1 parent 35a6288 commit bdbe525
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 8 deletions.
25 changes: 25 additions & 0 deletions README.md
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion plugin.d.ts
Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions plugin.js
Expand Up @@ -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
Expand All @@ -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

Expand Down
6 changes: 4 additions & 2 deletions plugin.test-d.ts
Expand Up @@ -29,7 +29,8 @@ expectAssignable<FastifyPluginCallback>(fp(pluginCallback, {
reply: [ '', testSymbol ],
request: [ '', testSymbol ]
},
dependencies: [ '' ]
dependencies: [ '' ],
encapsulate: true
}))

const pluginCallbackWithOptions: FastifyPluginCallback<Options> = (fastify, options, next) => {
Expand Down Expand Up @@ -66,7 +67,8 @@ expectAssignable<FastifyPluginAsync>(fp(pluginAsync, {
reply: [ '', testSymbol ],
request: [ '', testSymbol ]
},
dependencies: [ '' ]
dependencies: [ '' ],
encapsulate: true
}))

const pluginAsyncWithOptions: FastifyPluginAsync<Options> = async (fastify, options) => {
Expand Down
142 changes: 142 additions & 0 deletions test/test.js
Expand Up @@ -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()
}
}
})

0 comments on commit bdbe525

Please sign in to comment.