diff --git a/README.md b/README.md index d374c15..1e585ea 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ npm i @fastify/redis Add it to your project with `register` and you are done! -### Create a new Redis Client +### Create a new Redis/Dragonfly Client Under the hood [ioredis](https://github.com/luin/ioredis) is used as client, the ``options`` that you pass to `register` will be passed to the Redis client. @@ -79,6 +79,48 @@ fastify.listen({ port: 3000 }, err => { }) ``` +### Accessing the Dragonfly Client + +Once you have registered your plugin, you can access the Dragonfly client via `fastify.dragonfly`. + +The client is automatically closed when the fastify instance is closed. + +```js +'use strict' + +const Fastify = require('fastify') +const fastifyRedis = require('@fastify/redis') + +const fastify = Fastify({ logger: true }) + +fastify.register(fastifyRedis, { + host: '127.0.0.1', + password: 'your strong password here', + port: 6379, // Dragonfly port + family: 4 // 4 (IPv4) or 6 (IPv6), + isDragonfly: true +}) + +fastify.get('/foo', (req, reply) => { + const { dragonfly } = fastify + dragonfly.get(req.query.key, (err, val) => { + reply.send(err || val) + }) +}) + +fastify.post('/foo', (req, reply) => { + const { dragonfly } = fastify + dragonfly.set(req.body.key, req.body.value, (err) => { + reply.send(err || { status: 'ok' }) + }) +}) + +fastify.listen({ port: 3000 }, err => { + if (err) throw err + console.log(`server listening on ${fastify.server.address().port}`) +}) +``` + ### Using an existing Redis client You may also supply an existing *Redis* client instance by passing an options diff --git a/index.js b/index.js index 25299d4..733a681 100644 --- a/index.js +++ b/index.js @@ -4,21 +4,23 @@ const fp = require('fastify-plugin') const Redis = require('ioredis') function fastifyRedis (fastify, options, next) { - const { namespace, url, closeClient = false, ...redisOptions } = options + const { namespace, url, closeClient = false, isDragonfly = false, ...redisOptions } = options + + const decoratorName = isDragonfly ? 'dragonfly' : 'redis' let client = options.client || null if (namespace) { - if (!fastify.redis) { - fastify.decorate('redis', Object.create(null)) + if (!fastify[decoratorName]) { + fastify.decorate(decoratorName, Object.create(null)) } - if (fastify.redis[namespace]) { - return next(new Error(`Redis '${namespace}' instance namespace has already been registered`)) + if (fastify[decoratorName][namespace]) { + return next(new Error(`${decoratorName} '${namespace}' instance namespace has already been registered`)) } const closeNamedInstance = (fastify) => { - return fastify.redis[namespace].quit() + return fastify[decoratorName][namespace].quit() } if (client) { @@ -39,14 +41,14 @@ function fastifyRedis (fastify, options, next) { fastify.addHook('onClose', closeNamedInstance) } - fastify.redis[namespace] = client + fastify[decoratorName][namespace] = client } else { - if (fastify.redis) { + if (fastify[decoratorName]) { return next(new Error('@fastify/redis has already been registered')) } else { if (client) { if (closeClient === true) { - fastify.addHook('onClose', close) + fastify.addHook('onClose', close(decoratorName)) } } else { try { @@ -59,10 +61,10 @@ function fastifyRedis (fastify, options, next) { return next(err) } - fastify.addHook('onClose', close) + fastify.addHook('onClose', close(decoratorName)) } - fastify.decorate('redis', client) + fastify.decorate(decoratorName, client) } } @@ -122,8 +124,10 @@ function fastifyRedis (fastify, options, next) { } } -function close (fastify) { - return fastify.redis.quit() +function close (decoratorName) { + return function (fastify) { + return fastify[decoratorName].quit() + } } module.exports = fp(fastifyRedis, { diff --git a/test/index.test.js b/test/index.test.js index 75d966f..3801339 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -7,191 +7,205 @@ const test = t.test const Fastify = require('fastify') const fastifyRedis = require('..') -t.beforeEach(async () => { - const fastify = Fastify() +const decoratorNames = ['redis', 'dragonfly'] - fastify.register(fastifyRedis, { - host: '127.0.0.1' - }) +decoratorNames.forEach(decoratorName => { + const isDragonfly = decoratorName === 'dragonfly' - await fastify.ready() - await fastify.redis.flushall() - await fastify.close() -}) + t.beforeEach(async () => { + const fastify = Fastify() + + fastify.register(fastifyRedis, { + host: '127.0.0.1', + isDragonfly + }) -test('fastify.redis should exist', (t) => { - t.plan(2) - const fastify = Fastify() - fastify.register(fastifyRedis, { - host: '127.0.0.1' + await fastify.ready() + await fastify[decoratorName].flushall() + await fastify.close() }) - fastify.ready((err) => { - t.error(err) - t.ok(fastify.redis) + test(`fastify.${decoratorName} should exist`, (t) => { + t.plan(2) + const fastify = Fastify() + fastify.register(fastifyRedis, { + host: '127.0.0.1', + isDragonfly + }) - fastify.close() + fastify.ready((err) => { + t.error(err) + t.ok(fastify[decoratorName]) + + fastify.close() + }) }) -}) -test('fastify.redis should support url', (t) => { - t.plan(3) - const fastify = Fastify() + test(`fastify.${decoratorName} should support url`, (t) => { + t.plan(3) + const fastify = Fastify() - const fastifyRedis = proxyquire('..', { - ioredis: function Redis (path, options) { - t.equal(path, 'redis://127.0.0.1') - t.same(options, { - otherOption: 'foo' - }) - this.quit = () => {} - this.info = cb => cb(null, 'info') - this.on = function (name, handler) { - if (name === 'ready') { - handler(null, 'ready') + const fastifyRedis = proxyquire('..', { + ioredis: function Redis(path, options) { + t.equal(path, 'redis://127.0.0.1') + t.same(options, { + otherOption: 'foo' + }) + this.quit = () => { } + this.info = cb => cb(null, 'info') + this.on = function (name, handler) { + if (name === 'ready') { + handler(null, 'ready') + } + + return this } + this.status = 'ready' + this.off = function () { return this } return this } - this.status = 'ready' - this.off = function () { return this } - - return this - } - }) + }) - fastify.register(fastifyRedis, { - url: 'redis://127.0.0.1', - otherOption: 'foo' - }) + fastify.register(fastifyRedis, { + url: 'redis://127.0.0.1', + otherOption: 'foo', + isDragonfly + }) - fastify.ready((err) => { - t.error(err) - fastify.close() + fastify.ready((err) => { + t.error(err) + fastify.close() + }) }) -}) -test('fastify.redis should be the redis client', (t) => { - t.plan(4) - const fastify = Fastify() + test(`fastify.${decoratorName} should be the redis client`, (t) => { + t.plan(4) + const fastify = Fastify() - fastify.register(fastifyRedis, { - host: '127.0.0.1' - }) - - fastify.ready((err) => { - t.error(err) + fastify.register(fastifyRedis, { + host: '127.0.0.1', + isDragonfly + }) - fastify.redis.set('key', 'value', (err) => { + fastify.ready((err) => { t.error(err) - fastify.redis.get('key', (err, val) => { + + fastify[decoratorName].set('key', 'value', (err) => { t.error(err) - t.equal(val, 'value') + fastify[decoratorName].get('key', (err, val) => { + t.error(err) + t.equal(val, 'value') - fastify.close() + fastify.close() + }) }) }) }) -}) -test('fastify.redis.test namespace should exist', (t) => { - t.plan(3) + test(`fastify.${decoratorName}.test namespace should exist`, (t) => { + t.plan(3) - const fastify = Fastify() - fastify.register(fastifyRedis, { - host: '127.0.0.1', - namespace: 'test' - }) + const fastify = Fastify() + fastify.register(fastifyRedis, { + host: '127.0.0.1', + namespace: 'test', + isDragonfly + }) - fastify.ready((err) => { - t.error(err) - t.ok(fastify.redis) - t.ok(fastify.redis.test) + fastify.ready((err) => { + t.error(err) + t.ok(fastify[decoratorName]) + t.ok(fastify[decoratorName].test) - fastify.close() + fastify.close() + }) }) -}) -test('fastify.redis.test should be the redis client', (t) => { - t.plan(4) - const fastify = Fastify() - - fastify.register(fastifyRedis, { - host: '127.0.0.1', - namespace: 'test' - }) + test(`fastify.${decoratorName}.test should be the redis client`, (t) => { + t.plan(4) + const fastify = Fastify() - fastify.ready((err) => { - t.error(err) + fastify.register(fastifyRedis, { + host: '127.0.0.1', + namespace: 'test', + isDragonfly + }) - fastify.redis.test.set('key_namespace', 'value_namespace', (err) => { + fastify.ready((err) => { t.error(err) - fastify.redis.test.get('key_namespace', (err, val) => { + + fastify[decoratorName].test.set('key_namespace', 'value_namespace', (err) => { t.error(err) - t.equal(val, 'value_namespace') + fastify[decoratorName].test.get('key_namespace', (err, val) => { + t.error(err) + t.equal(val, 'value_namespace') - fastify.close() + fastify.close() + }) }) }) }) -}) -test('promises support', (t) => { - t.plan(2) - const fastify = Fastify() + test('promises support', (t) => { + t.plan(2) + const fastify = Fastify() - fastify.register(fastifyRedis, { - host: '127.0.0.1' - }) + fastify.register(fastifyRedis, { + host: '127.0.0.1', + isDragonfly + }) - fastify.ready((err) => { - t.error(err) + fastify.ready((err) => { + t.error(err) - fastify.redis - .set('key', 'value') - .then(() => { - return fastify.redis.get('key') - }) - .then((val) => { - t.equal(val, 'value') - fastify.close() - }) - .catch((err) => t.fail(err)) + fastify[decoratorName] + .set('key', 'value') + .then(() => { + return fastify[decoratorName].get('key') + }) + .then((val) => { + t.equal(val, 'value') + fastify.close() + }) + .catch((err) => t.fail(err)) + }) }) -}) -test('custom ioredis client that is already connected', (t) => { - t.plan(10) - const fastify = Fastify() - const Redis = require('ioredis') - const redis = new Redis({ host: 'localhost', port: 6379 }) + test('custom ioredis client that is already connected', (t) => { + t.plan(10) + const fastify = Fastify() + const Redis = require('ioredis') + const redis = new Redis({ host: 'localhost', port: 6379 }) - // use the client now, so that it is connected and ready - redis.set('key', 'value', (err) => { - t.error(err) - redis.get('key', (err, val) => { + // use the client now, so that it is connected and ready + redis.set('key', 'value', (err) => { t.error(err) - t.equal(val, 'value') - - fastify.register(fastifyRedis, { - client: redis, - lazyConnect: false - }) - - fastify.ready((err) => { + redis.get('key', (err, val) => { t.error(err) - t.equal(fastify.redis, redis) + t.equal(val, 'value') - fastify.redis.set('key2', 'value2', (err) => { + fastify.register(fastifyRedis, { + client: redis, + lazyConnect: false, + isDragonfly + }) + + fastify.ready((err) => { t.error(err) - fastify.redis.get('key2', (err, val) => { - t.error(err) - t.equal(val, 'value2') + t.equal(fastify[decoratorName], redis) - fastify.close(function (err) { + fastify[decoratorName].set('key2', 'value2', (err) => { + t.error(err) + fastify[decoratorName].get('key2', (err, val) => { t.error(err) - fastify.redis.quit(function (err) { + t.equal(val, 'value2') + + fastify.close(function (err) { t.error(err) + fastify[decoratorName].quit(function (err) { + t.error(err) + }) }) }) }) @@ -199,40 +213,41 @@ test('custom ioredis client that is already connected', (t) => { }) }) }) -}) -test('custom ioredis client that is already connected', (t) => { - t.plan(10) - const fastify = Fastify() - const Redis = require('ioredis') - const redis = new Redis({ host: 'localhost', port: 6379 }) + test('custom ioredis client that is already connected', (t) => { + t.plan(10) + const fastify = Fastify() + const Redis = require('ioredis') + const redis = new Redis({ host: 'localhost', port: 6379 }) - // use the client now, so that it is connected and ready - redis.set('key', 'value', (err) => { - t.error(err) - redis.get('key', (err, val) => { + // use the client now, so that it is connected and ready + redis.set('key', 'value', (err) => { t.error(err) - t.equal(val, 'value') - - fastify.register(fastifyRedis, { - client: redis, - namespace: 'foo' - }) - - fastify.ready((err) => { + redis.get('key', (err, val) => { t.error(err) - t.equal(fastify.redis.foo, redis) + t.equal(val, 'value') + + fastify.register(fastifyRedis, { + client: redis, + namespace: 'foo', + isDragonfly + }) - fastify.redis.foo.set('key2', 'value2', (err) => { + fastify.ready((err) => { t.error(err) - fastify.redis.foo.get('key2', (err, val) => { - t.error(err) - t.equal(val, 'value2') + t.equal(fastify[decoratorName].foo, redis) - fastify.close(function (err) { + fastify[decoratorName].foo.set('key2', 'value2', (err) => { + t.error(err) + fastify[decoratorName].foo.get('key2', (err, val) => { t.error(err) - fastify.redis.foo.quit(function (err) { + t.equal(val, 'value2') + + fastify.close(function (err) { t.error(err) + fastify[decoratorName].foo.quit(function (err) { + t.error(err) + }) }) }) }) @@ -240,253 +255,268 @@ test('custom ioredis client that is already connected', (t) => { }) }) }) -}) -test('If closeClient is enabled, close the client.', (t) => { - t.plan(10) - const fastify = Fastify() - const Redis = require('ioredis') - const redis = new Redis({ host: 'localhost', port: 6379 }) + test('If closeClient is enabled, close the client.', (t) => { + t.plan(10) + const fastify = Fastify() + const Redis = require('ioredis') + const redis = new Redis({ host: 'localhost', port: 6379 }) - redis.set('key', 'value', (err) => { - t.error(err) - redis.get('key', (err, val) => { + redis.set('key', 'value', (err) => { t.error(err) - t.equal(val, 'value') - - fastify.register(fastifyRedis, { - client: redis, - closeClient: true - }) - - fastify.ready((err) => { + redis.get('key', (err, val) => { t.error(err) - t.equal(fastify.redis, redis) + t.equal(val, 'value') + + fastify.register(fastifyRedis, { + client: redis, + closeClient: true, + isDragonfly + }) - fastify.redis.set('key2', 'value2', (err) => { + fastify.ready((err) => { t.error(err) - fastify.redis.get('key2', (err, val) => { + t.equal(fastify[decoratorName], redis) + + fastify[decoratorName].set('key2', 'value2', (err) => { t.error(err) - t.equal(val, 'value2') + fastify[decoratorName].get('key2', (err, val) => { + t.error(err) + t.equal(val, 'value2') - const originalQuit = fastify.redis.quit - fastify.redis.quit = (callback) => { - t.pass('redis client closed') - originalQuit.call(fastify.redis, callback) - } + const originalQuit = fastify[decoratorName].quit + fastify[decoratorName].quit = (callback) => { + t.pass('redis client closed') + originalQuit.call(fastify[decoratorName], callback) + } - fastify.close(function (err) { - t.error(err) + fastify.close(function (err) { + t.error(err) + }) }) }) }) }) }) }) -}) -test('If closeClient is enabled, close the client namespace.', (t) => { - t.plan(10) - const fastify = Fastify() - const Redis = require('ioredis') - const redis = new Redis({ host: 'localhost', port: 6379 }) + test('If closeClient is enabled, close the client namespace.', (t) => { + t.plan(10) + const fastify = Fastify() + const Redis = require('ioredis') + const redis = new Redis({ host: 'localhost', port: 6379 }) - redis.set('key', 'value', (err) => { - t.error(err) - redis.get('key', (err, val) => { + redis.set('key', 'value', (err) => { t.error(err) - t.equal(val, 'value') - - fastify.register(fastifyRedis, { - client: redis, - namespace: 'foo', - closeClient: true - }) - - fastify.ready((err) => { + redis.get('key', (err, val) => { t.error(err) - t.equal(fastify.redis.foo, redis) + t.equal(val, 'value') + + fastify.register(fastifyRedis, { + client: redis, + namespace: 'foo', + closeClient: true, + isDragonfly + }) - fastify.redis.foo.set('key2', 'value2', (err) => { + fastify.ready((err) => { t.error(err) - fastify.redis.foo.get('key2', (err, val) => { + t.equal(fastify[decoratorName].foo, redis) + + fastify[decoratorName].foo.set('key2', 'value2', (err) => { t.error(err) - t.equal(val, 'value2') + fastify[decoratorName].foo.get('key2', (err, val) => { + t.error(err) + t.equal(val, 'value2') - const originalQuit = fastify.redis.foo.quit - fastify.redis.foo.quit = (callback) => { - t.pass('redis client closed') - originalQuit.call(fastify.redis.foo, callback) - } + const originalQuit = fastify[decoratorName].foo.quit + fastify[decoratorName].foo.quit = (callback) => { + t.pass('redis client closed') + originalQuit.call(fastify[decoratorName].foo, callback) + } - fastify.close(function (err) { - t.error(err) + fastify.close(function (err) { + t.error(err) + }) }) }) }) }) }) }) -}) -test('fastify.redis.test should throw with duplicate connection namespaces', (t) => { - t.plan(1) + test(`fastify.${decoratorName}.test should throw with duplicate connection namespaces`, (t) => { + t.plan(1) - const namespace = 'test' + const namespace = 'test' - const fastify = Fastify() - t.teardown(() => fastify.close()) - - fastify - .register(fastifyRedis, { - host: '127.0.0.1', - namespace - }) - .register(fastifyRedis, { - host: '127.0.0.1', - namespace - }) + const fastify = Fastify() + t.teardown(() => fastify.close()) - fastify.ready((err) => { - t.equal(err.message, `Redis '${namespace}' instance namespace has already been registered`) - }) -}) - -test('Should throw when trying to register multiple instances without giving a namespace', (t) => { - t.plan(1) - - const fastify = Fastify() - t.teardown(() => fastify.close()) + fastify + .register(fastifyRedis, { + host: '127.0.0.1', + namespace, + isDragonfly + }) + .register(fastifyRedis, { + host: '127.0.0.1', + namespace, + isDragonfly + }) - fastify - .register(fastifyRedis, { - host: '127.0.0.1' - }) - .register(fastifyRedis, { - host: '127.0.0.1' + fastify.ready((err) => { + t.equal(err.message, `${decoratorName} '${namespace}' instance namespace has already been registered`) }) - - fastify.ready((err) => { - t.equal(err.message, '@fastify/redis has already been registered') }) -}) - -test('Should not throw within different contexts', (t) => { - t.plan(1) - const fastify = Fastify() - t.teardown(() => fastify.close()) + test('Should throw when trying to register multiple instances without giving a namespace', (t) => { + t.plan(1) - fastify.register(function (instance, options, next) { - instance.register(fastifyRedis, { - host: '127.0.0.1' - }) - next() - }) + const fastify = Fastify() + t.teardown(() => fastify.close()) - fastify.register(function (instance, options, next) { - instance + fastify .register(fastifyRedis, { host: '127.0.0.1', - namespace: 'test1' + isDragonfly }) .register(fastifyRedis, { host: '127.0.0.1', - namespace: 'test2' + isDragonfly }) - next() - }) - fastify.ready((error) => { - t.error(error) + fastify.ready((err) => { + t.equal(err.message, '@fastify/redis has already been registered') + }) }) -}) -// Skipped because it makes TAP crash -test('Should throw when trying to connect on an invalid host', { skip: true }, (t) => { - t.plan(1) + test('Should not throw within different contexts', (t) => { + t.plan(1) - const fastify = Fastify({ pluginTimeout: 20000 }) - t.teardown(() => fastify.close()) + const fastify = Fastify() + t.teardown(() => fastify.close()) - fastify - .register(fastifyRedis, { - host: 'invalid_host' + fastify.register(function (instance, options, next) { + instance.register(fastifyRedis, { + host: '127.0.0.1', + isDragonfly + }) + next() }) - fastify.ready((err) => { - t.ok(err) + fastify.register(function (instance, options, next) { + instance + .register(fastifyRedis, { + host: '127.0.0.1', + namespace: 'test1', + isDragonfly + }) + .register(fastifyRedis, { + host: '127.0.0.1', + namespace: 'test2', + isDragonfly + }) + next() + }) + + fastify.ready((error) => { + t.error(error) + }) }) -}) -test('Should successfully create a Redis client when registered with a `url` option and without a `client` option in a namespaced instance', async t => { - t.plan(2) + // Skipped because it makes TAP crash + test('Should throw when trying to connect on an invalid host', { skip: true }, (t) => { + t.plan(1) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + const fastify = Fastify({ pluginTimeout: 20000 }) + t.teardown(() => fastify.close()) - await fastify.register(fastifyRedis, { - url: 'redis://127.0.0.1', - namespace: 'test' + fastify + .register(fastifyRedis, { + host: 'invalid_host', + isDragonfly + }) + + fastify.ready((err) => { + t.ok(err) + }) }) - await fastify.ready() - t.ok(fastify.redis) - t.ok(fastify.redis.test) -}) + test('Should successfully create a Redis client when registered with a `url` option and without a `client` option in a namespaced instance', async t => { + t.plan(2) -test('Should be able to register multiple namespaced @fastify/redis instances', async t => { - t.plan(3) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + await fastify.register(fastifyRedis, { + url: 'redis://127.0.0.1', + namespace: 'test', + isDragonfly + }) - await fastify.register(fastifyRedis, { - url: 'redis://127.0.0.1', - namespace: 'one' + await fastify.ready() + t.ok(fastify[decoratorName]) + t.ok(fastify[decoratorName].test) }) - await fastify.register(fastifyRedis, { - url: 'redis://127.0.0.1', - namespace: 'two' - }) + test('Should be able to register multiple namespaced @fastify/redis instances', async t => { + t.plan(3) - await fastify.ready() - t.ok(fastify.redis) - t.ok(fastify.redis.one) - t.ok(fastify.redis.two) -}) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) -test('Should throw when @fastify/redis is initialized with an option that makes Redis throw', (t) => { - t.plan(1) + await fastify.register(fastifyRedis, { + url: 'redis://127.0.0.1', + namespace: 'one', + isDragonfly + }) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + await fastify.register(fastifyRedis, { + url: 'redis://127.0.0.1', + namespace: 'two', + isDragonfly + }) - // This will throw a `TypeError: this.options.Connector is not a constructor` - fastify.register(fastifyRedis, { - Connector: 'should_fail' + await fastify.ready() + t.ok(fastify[decoratorName]) + t.ok(fastify[decoratorName].one) + t.ok(fastify[decoratorName].two) }) - fastify.ready(err => { - t.ok(err) - }) -}) + test('Should throw when @fastify/redis is initialized with an option that makes Redis throw', (t) => { + t.plan(1) -test('Should throw when @fastify/redis is initialized with a namespace and an option that makes Redis throw', (t) => { - t.plan(1) + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) - const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + // This will throw a `TypeError: this.options.Connector is not a constructor` + fastify.register(fastifyRedis, { + Connector: 'should_fail', + isDragonfly + }) - // This will throw a `TypeError: this.options.Connector is not a constructor` - fastify.register(fastifyRedis, { - Connector: 'should_fail', - namespace: 'fail' + fastify.ready(err => { + t.ok(err) + }) }) - fastify.ready(err => { - t.ok(err) + test('Should throw when @fastify/redis is initialized with a namespace and an option that makes Redis throw', (t) => { + t.plan(1) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + // This will throw a `TypeError: this.options.Connector is not a constructor` + fastify.register(fastifyRedis, { + Connector: 'should_fail', + namespace: 'fail', + isDragonfly + }) + + fastify.ready(err => { + t.ok(err) + }) }) }) diff --git a/types/index.d.ts b/types/index.d.ts index f218904..5e9cf39 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -6,6 +6,7 @@ type FastifyRedisPluginType = FastifyPluginCallback { expectAssignable(app.redis) + expectAssignable(app.dragonfly) expectType(app.redis) + expectType(app.dragonfly) expectAssignable(app.redis) expectType(app.redis.one) expectType(app.redis.two) + expectAssignable(app.dragonfly) + expectType(app.dragonfly.one) + expectType(app.dragonfly.two) }) expectDeprecated({} as FastifyRedisPlugin)