From 044f0e33b0d10f609e87a4868bcbd9eeef0f4a4d Mon Sep 17 00:00:00 2001 From: Igor Savin Date: Wed, 7 Feb 2024 12:00:07 +0200 Subject: [PATCH] Support passing AwilixContainer explicitly (#126) --- README.md | 24 +++---- lib/classic/index.d.ts | 1 - lib/classic/index.js | 70 ------------------ lib/fastifyAwilixPlugin.js | 30 ++++++-- lib/index.d.ts | 4 ++ lib/index.js | 9 ++- lib/index.test-d.ts | 8 ++- package.json | 6 +- test/awilixManager.spec.js | 36 +++++----- test/fastifyAwilixPlugin.dispose.spec.js | 56 ++++++++++----- test/fastifyAwilixPlugin.spec.js | 92 +++++++++++++++++------- 11 files changed, 181 insertions(+), 155 deletions(-) delete mode 100644 lib/classic/index.d.ts delete mode 100644 lib/classic/index.js diff --git a/README.md b/README.md index 189e08f..bf0698d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![NPM Version][npm-image]][npm-url] [![Build Status](https://github.com/fastify/fastify-awilix/workflows/ci/badge.svg)](https://github.com/fastify/fastify-awilix/actions) -Dependency injection support for fastify framework, using [awilix](https://github.com/jeffijoe/awilix) +Dependency injection support for fastify framework, using [awilix](https://github.com/jeffijoe/awilix). ## Getting started @@ -26,7 +26,11 @@ app.register(fastifyAwilixPlugin, { disposeOnClose: true, disposeOnResponse: tru Then, register some modules for injection: ```js -const { diContainer } = require('@fastify/awilix') +const { + diContainer, // this is an alias for diContainerProxy + diContainerClassic, // this instance will be used for `injectionMode = 'CLASSIC'` + diContainerProxy // this instance will be used by default +} = require('@fastify/awilix') const { asClass, asFunction, Lifetime } = require('awilix') // Code from the previous example goes here @@ -74,6 +78,10 @@ app.post('/', async (req, res) => { ## Plugin options +`injectionMode` - whether to use PROXY or CLASSIC injection mode. See [awilix documentation](https://www.npmjs.com/package/awilix#injection-modes) for details. Default is 'PROXY'. + +`container` - pre-created AwilixContainer instance that should be used by the plugin. By default plugin uses its own instance. Note that you can't specify both `injectionMode` and `container` parameters at the same time. + `disposeOnClose` - automatically invoke configured `dispose` for app-level `diContainer` hooks when the fastify instance is closed. Disposal is triggered within `onClose` fastify hook. Default value is `true` @@ -178,18 +186,6 @@ await app.register(fastifyAwilixPlugin, { asyncInit: true, asyncDispose: true, e await app.ready() ``` -## Using classic injection mode - -If you prefer [classic injection](https://github.com/jeffijoe/awilix#injection-modes), you can use it like this: - -```javascript -const { fastifyAwilixPlugin } = require('@fastify/awilix/lib/classic') -const fastify = require('fastify') - -app = fastify({ logger: true }) -app.register(fastifyAwilixPlugin, { disposeOnClose: true, disposeOnResponse: true }) -``` - ## Advanced DI configuration For more advanced use-cases, check the official [awilix documentation](https://github.com/jeffijoe/awilix) diff --git a/lib/classic/index.d.ts b/lib/classic/index.d.ts deleted file mode 100644 index e22ddd3..0000000 --- a/lib/classic/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export { Cradle, RequestCradle, diContainer, FastifyAwilixOptions, fastifyAwilixPlugin} from '../' diff --git a/lib/classic/index.js b/lib/classic/index.js deleted file mode 100644 index 2b3bf96..0000000 --- a/lib/classic/index.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict' - -const awilix = require('awilix') -const fp = require('fastify-plugin') -const { AwilixManager } = require('awilix-manager') - -const diContainer = awilix.createContainer({ - injectionMode: 'CLASSIC', -}) - -function plugin(fastify, opts, next) { - const awilixManager = new AwilixManager({ - diContainer, - asyncDispose: opts.asyncDispose, - asyncInit: opts.asyncInit, - eagerInject: opts.eagerInject, - }) - const disposeOnResponse = opts.disposeOnResponse === true || opts.disposeOnResponse === undefined - const disposeOnClose = opts.disposeOnClose === true || opts.disposeOnClose === undefined - - fastify.decorate('awilixManager', awilixManager) - fastify.decorate('diContainer', diContainer) - fastify.decorateRequest('diScope', null) - - fastify.addHook('onRequest', (request, reply, done) => { - request.diScope = diContainer.createScope() - done() - }) - - if (opts.asyncInit || opts.eagerInject) { - fastify.addHook('onReady', (done) => { - awilixManager.executeInit().then(done, done) - }) - } - - if (disposeOnClose) { - fastify.addHook('onClose', (instance, done) => { - if (instance.awilixManager.config.asyncDispose) { - instance.awilixManager - .executeDispose() - .then(() => { - return instance.diContainer.dispose() - }) - .then(done, done) - } else { - instance.diContainer.dispose().then(done, done) - } - }) - } - - if (disposeOnResponse) { - fastify.addHook('onResponse', (request, reply, done) => { - if (request.diScope) { - request.diScope.dispose().then(done, done) - } - }) - } - - next() -} - -const fastifyAwilixPlugin = fp(plugin, { - fastify: '4.x', - name: '@fastify/awilix', -}) - -module.exports = { - diContainer, - fastifyAwilixPlugin, -} diff --git a/lib/fastifyAwilixPlugin.js b/lib/fastifyAwilixPlugin.js index b385598..ee62073 100644 --- a/lib/fastifyAwilixPlugin.js +++ b/lib/fastifyAwilixPlugin.js @@ -4,13 +4,31 @@ const awilix = require('awilix') const { AwilixManager } = require('awilix-manager') const fp = require('fastify-plugin') -const diContainer = awilix.createContainer({ +const diContainerProxy = awilix.createContainer({ injectionMode: 'PROXY', }) +const diContainerClassic = awilix.createContainer({ + injectionMode: 'CLASSIC', +}) + function plugin(fastify, opts, next) { + if (opts.container && opts.injectionMode) { + return next( + new Error( + 'If you are passing pre-created container explicitly, you cannot specify injection mode', + ), + ) + } + + const resolvedContainer = opts.container + ? opts.container + : opts.injectionMode === 'CLASSIC' + ? diContainerClassic + : diContainerProxy + const awilixManager = new AwilixManager({ - diContainer, + diContainer: resolvedContainer, asyncDispose: opts.asyncDispose, asyncInit: opts.asyncInit, eagerInject: opts.eagerInject, @@ -19,11 +37,11 @@ function plugin(fastify, opts, next) { const disposeOnClose = opts.disposeOnClose === true || opts.disposeOnClose === undefined fastify.decorate('awilixManager', awilixManager) - fastify.decorate('diContainer', diContainer) + fastify.decorate('diContainer', resolvedContainer) fastify.decorateRequest('diScope', null) fastify.addHook('onRequest', (request, reply, done) => { - request.diScope = diContainer.createScope() + request.diScope = resolvedContainer.createScope() done() }) @@ -65,6 +83,8 @@ const fastifyAwilixPlugin = fp(plugin, { }) module.exports = { - diContainer, + diContainer: diContainerProxy, + diContainerProxy, + diContainerClassic, fastifyAwilixPlugin, } diff --git a/lib/index.d.ts b/lib/index.d.ts index 82f09df..c05f4ad 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -20,6 +20,8 @@ declare module 'fastify' { export type FastifyAwilixOptions = { disposeOnResponse?: boolean disposeOnClose?: boolean + injectionMode?: 'PROXY' | 'CLASSIC' + container?: AwilixContainer asyncInit?: boolean asyncDispose?: boolean eagerInject?: boolean @@ -28,3 +30,5 @@ export type FastifyAwilixOptions = { export const fastifyAwilixPlugin: FastifyPluginCallback> export const diContainer: AwilixContainer +export const diContainerClassic: AwilixContainer +export const diContainerProxy: AwilixContainer diff --git a/lib/index.js b/lib/index.js index 1614b8c..c18c2f5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,8 +1,15 @@ 'use strict' -const { diContainer, fastifyAwilixPlugin } = require('./fastifyAwilixPlugin') +const { + diContainer, + fastifyAwilixPlugin, + diContainerProxy, + diContainerClassic, +} = require('./fastifyAwilixPlugin') module.exports = { diContainer, + diContainerClassic, + diContainerProxy, fastifyAwilixPlugin, } diff --git a/lib/index.test-d.ts b/lib/index.test-d.ts index 74d811e..36c2729 100644 --- a/lib/index.test-d.ts +++ b/lib/index.test-d.ts @@ -1,12 +1,16 @@ import { asValue, AwilixContainer } from 'awilix' import fastify, { FastifyInstance } from 'fastify' -import { diContainer, FastifyAwilixOptions, fastifyAwilixPlugin, Cradle, RequestCradle } from './index' -import { diContainer as diContainerClassic } from './classic' +import { diContainer, diContainerClassic, FastifyAwilixOptions, fastifyAwilixPlugin, Cradle, RequestCradle } from './index' import { expectAssignable, expectNotType, expectType } from 'tsd' expectAssignable({}) expectAssignable({ disposeOnClose: false }) +expectAssignable({ container: diContainer }) +expectAssignable({ container: diContainerClassic }) +expectAssignable({ injectionMode: 'CLASSIC' }) +expectAssignable({ injectionMode: 'PROXY' }) + expectAssignable({ disposeOnResponse: false }) expectAssignable({ asyncInit: false, asyncDispose: false }) expectAssignable({ asyncInit: true, asyncDispose: true }) diff --git a/package.json b/package.json index 15654c9..7fece2d 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ "@types/node": "^20.8.6", "awilix": "^10.0.1", "eslint": "^8.51.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "fastify": "^4.24.2", "jest": "^29.7.0", - "prettier": "^3.0.3", + "prettier": "^3.2.5", "tsd": "^0.30.0" }, "homepage": "http://github.com/fastify/fastify-awilix", diff --git a/test/awilixManager.spec.js b/test/awilixManager.spec.js index e2bf2c6..35b47e2 100644 --- a/test/awilixManager.spec.js +++ b/test/awilixManager.spec.js @@ -2,12 +2,7 @@ const { asClass } = require('awilix') const fastify = require('fastify') -const { diContainer, fastifyAwilixPlugin } = require('../lib') - -const { - diContainer: diContainerClassic, - fastifyAwilixPlugin: fastifyAwilixPluginClassic, -} = require('../lib/classic') +const { diContainer, diContainerClassic, fastifyAwilixPlugin } = require('../lib') let isInittedGlobal = false let isDisposedGlobal = false @@ -32,20 +27,18 @@ class AsyncInitSetClass { const variations = [ { - name: 'PROXY', - plugin: fastifyAwilixPlugin, + injectionMode: 'PROXY', container: diContainer, }, { - name: 'CLASSIC', - plugin: fastifyAwilixPluginClassic, + injectionMode: 'CLASSIC', container: diContainerClassic, }, ] describe('awilixManager', () => { variations.forEach((variation) => { - describe(variation.name, () => { + describe(variation.injectionMode, () => { let app beforeEach(() => { isInittedGlobal = false @@ -66,10 +59,13 @@ describe('awilixManager', () => { asClass(InitSetClass, { lifetime: 'SINGLETON', eagerInject: true, - }) + }), ) app = fastify({ logger: false }) - await app.register(variation.plugin, { eagerInject: true }) + await app.register(fastifyAwilixPlugin, { + eagerInject: true, + injectionMode: variation.injectionMode, + }) await app.ready() expect(isInittedGlobal).toBe(true) @@ -81,10 +77,13 @@ describe('awilixManager', () => { asClass(AsyncInitSetClass, { lifetime: 'SINGLETON', asyncInit: 'init', - }) + }), ) app = fastify({ logger: false }) - await app.register(variation.plugin, { asyncInit: true }) + await app.register(fastifyAwilixPlugin, { + asyncInit: true, + injectionMode: variation.injectionMode, + }) await app.ready() expect(isInittedGlobal).toBe(true) @@ -96,10 +95,13 @@ describe('awilixManager', () => { asClass(AsyncInitSetClass, { lifetime: 'SINGLETON', asyncDispose: 'dispose', - }) + }), ) app = fastify({ logger: false }) - await app.register(variation.plugin, { asyncDispose: true }) + await app.register(fastifyAwilixPlugin, { + asyncDispose: true, + injectionMode: variation.injectionMode, + }) await app.ready() await app.close() diff --git a/test/fastifyAwilixPlugin.dispose.spec.js b/test/fastifyAwilixPlugin.dispose.spec.js index d0cf946..a158a5c 100644 --- a/test/fastifyAwilixPlugin.dispose.spec.js +++ b/test/fastifyAwilixPlugin.dispose.spec.js @@ -2,11 +2,7 @@ const fastify = require('fastify') const { asClass, Lifetime } = require('awilix') -const { fastifyAwilixPlugin, diContainer } = require('../lib') -const { - fastifyAwilixPlugin: fastifyAwilixPluginClassic, - diContainer: diContainerClassic, -} = require('../lib/classic') +const { fastifyAwilixPlugin, diContainer, diContainerClassic } = require('../lib') class UserRepository { constructor() { @@ -23,13 +19,11 @@ let storedUserRepositoryScoped const variations = [ { - name: 'PROXY', - plugin: fastifyAwilixPlugin, + injectionMode: 'PROXY', container: diContainer, }, { - name: 'CLASSIC', - plugin: fastifyAwilixPluginClassic, + injectionMode: 'CLASSIC', container: diContainerClassic, }, ] @@ -45,7 +39,7 @@ describe('fastifyAwilixPlugin', () => { }) variations.forEach((variation) => { - describe(variation.name, () => { + describe(variation.injectionMode, () => { const endpoint = async (req, res) => { const userRepository = app.diContainer.resolve('userRepository') storedUserRepository = userRepository @@ -72,7 +66,11 @@ describe('fastifyAwilixPlugin', () => { it('dispose app-scoped singletons on closing app correctly', async () => { app = fastify({ logger: true }) - app.register(variation.plugin, { disposeOnClose: true, disposeOnResponse: false }) + app.register(fastifyAwilixPlugin, { + disposeOnClose: true, + disposeOnResponse: false, + injectionMode: variation.injectionMode, + }) variation.container.register({ userRepository: asClass(UserRepository, { lifetime: Lifetime.SINGLETON, @@ -93,7 +91,11 @@ describe('fastifyAwilixPlugin', () => { it('do not dispose app-scoped singletons on sending response', async () => { app = fastify({ logger: true }) - app.register(variation.plugin, { disposeOnClose: false, disposeOnResponse: true }) + app.register(fastifyAwilixPlugin, { + disposeOnClose: false, + disposeOnResponse: true, + injectionMode: variation.injectionMode, + }) variation.container.register({ userRepository: asClass(UserRepository, { lifetime: Lifetime.SINGLETON, @@ -118,7 +120,11 @@ describe('fastifyAwilixPlugin', () => { return reply.send('OK') }) - await app.register(variation.plugin, { disposeOnClose: false, disposeOnResponse: true }) + await app.register(fastifyAwilixPlugin, { + disposeOnClose: false, + disposeOnResponse: true, + injectionMode: variation.injectionMode, + }) variation.container.register({ userRepository: asClass(UserRepository, { lifetime: Lifetime.SINGLETON, @@ -146,7 +152,11 @@ describe('fastifyAwilixPlugin', () => { it('dispose request-scoped singletons on sending response', async () => { app = fastify({ logger: true }) - app.register(variation.plugin, { disposeOnClose: false, disposeOnResponse: true }) + app.register(fastifyAwilixPlugin, { + disposeOnClose: false, + disposeOnResponse: true, + injectionMode: variation.injectionMode, + }) variation.container.register({ userRepository: asClass(UserRepository, { @@ -179,7 +189,11 @@ describe('fastifyAwilixPlugin', () => { it('do not dispose request-scoped singletons twice on closing app', async () => { app = fastify({ logger: true }) - app.register(variation.plugin, { disposeOnClose: true, disposeOnResponse: true }) + app.register(fastifyAwilixPlugin, { + disposeOnClose: true, + disposeOnResponse: true, + injectionMode: variation.injectionMode, + }) variation.container.register({ userRepository: asClass(UserRepository, { @@ -221,7 +235,11 @@ describe('fastifyAwilixPlugin', () => { }) app = fastify({ logger: true }) - app.register(variation.plugin, { disposeOnClose: true, disposeOnResponse: true }) + app.register(fastifyAwilixPlugin, { + disposeOnClose: true, + disposeOnResponse: true, + injectionMode: variation.injectionMode, + }) variation.container.register({ userRepository: asClass(UserRepository, { lifetime: Lifetime.SINGLETON, @@ -247,7 +265,11 @@ describe('fastifyAwilixPlugin', () => { }) app = fastify({ logger: true }) - app.register(variation.plugin, { disposeOnClose: false, disposeOnResponse: false }) + app.register(fastifyAwilixPlugin, { + disposeOnClose: false, + disposeOnResponse: false, + injectionMode: variation.injectionMode, + }) variation.container.register({ userRepository: asClass(UserRepository, { lifetime: Lifetime.SINGLETON, diff --git a/test/fastifyAwilixPlugin.spec.js b/test/fastifyAwilixPlugin.spec.js index 072010a..96ce138 100644 --- a/test/fastifyAwilixPlugin.spec.js +++ b/test/fastifyAwilixPlugin.spec.js @@ -2,36 +2,60 @@ const fastify = require('fastify') const { asValue, asFunction, asClass, Lifetime } = require('awilix') -const { fastifyAwilixPlugin, diContainer } = require('../lib') -const { - fastifyAwilixPlugin: fastifyAwilixPluginClassic, - diContainer: diContainerClassic, -} = require('../lib/classic') +const { diContainer, diContainerClassic, fastifyAwilixPlugin } = require('../lib') + +class UserServiceClassic { + constructor(userRepository, maxUserName, maxEmail) { + this.userRepository = userRepository + this.maxUserName = maxUserName + this.maxEmail = maxEmail + } +} + +class UserServiceProxy { + constructor({ userRepository, maxUserName, maxEmail }) { + this.userRepository = userRepository + this.maxUserName = maxUserName + this.maxEmail = maxEmail + } +} const variations = [ { - name: 'PROXY', - plugin: fastifyAwilixPlugin, + injectionMode: 'PROXY', + container: diContainer, + optsContainer: undefined, + optsInjectionMode: undefined, + UserService: UserServiceProxy, + }, + { + injectionMode: 'PROXY', container: diContainer, - UserService: class UserService { - constructor({ userRepository, maxUserName, maxEmail }) { - this.userRepository = userRepository - this.maxUserName = maxUserName - this.maxEmail = maxEmail - } - }, + optsContainer: undefined, + optsInjectionMode: 'PROXY', + UserService: UserServiceProxy, }, { - name: 'CLASSIC', - plugin: fastifyAwilixPluginClassic, + injectionMode: 'CLASSIC', container: diContainerClassic, - UserService: class UserService { - constructor(userRepository, maxUserName, maxEmail) { - this.userRepository = userRepository - this.maxUserName = maxUserName - this.maxEmail = maxEmail - } - }, + optsContainer: undefined, + optsInjectionMode: 'CLASSIC', + UserService: UserServiceClassic, + }, + + { + injectionMode: 'PROXY', + container: diContainer, + optsContainer: diContainer, + optsInjectionMode: undefined, + UserService: UserServiceProxy, + }, + { + injectionMode: 'CLASSIC', + container: diContainerClassic, + optsContainer: diContainerClassic, + optsInjectionMode: undefined, + UserService: UserServiceClassic, }, ] @@ -42,7 +66,7 @@ describe('fastifyAwilixPlugin', () => { }) variations.forEach((variation) => { - describe(variation.name, () => { + describe(variation.injectionMode, () => { describe('inject singleton', () => { it('injects correctly', async () => { class UserRepository { @@ -73,7 +97,10 @@ describe('fastifyAwilixPlugin', () => { }) } - app.register(variation.plugin) + app.register(fastifyAwilixPlugin, { + injectionMode: variation.optsInjectionMode, + container: variation.optsContainer, + }) variation.container.register({ userService: asClass(variation.UserService), userRepository: asClass(UserRepository, { lifetime: Lifetime.SINGLETON }), @@ -93,4 +120,19 @@ describe('fastifyAwilixPlugin', () => { }) }) }) + + describe('constructor', () => { + it('throws an error if both injection mode and container are specified', async () => { + app = fastify({ logger: true }) + + await expect(() => + app.register(fastifyAwilixPlugin, { + injectionMode: 'CLASSIC', + container: diContainerClassic, + }), + ).rejects.toThrow( + /If you are passing pre-created container explicitly, you cannot specify injection mode/, + ) + }) + }) })