From 9542cdb3b375aae72f990379ae5210c2949ae5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0vanda?= <46406259+Papooch@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:51:42 +0200 Subject: [PATCH] feat(transactional): enable adapters to opt out of transactional proxy support (#160) --- .../01-transactional/index.md | 8 ++++-- packages/transactional/src/lib/interfaces.ts | 12 ++++++++- .../src/lib/plugin-transactional.ts | 12 +++++++++ .../test/inject-transaction.spec.ts | 27 +++++++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md b/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md index 358a8330..410356fa 100644 --- a/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md +++ b/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md @@ -212,7 +212,11 @@ class AccountService { When a transaction is not active, the `Transaction` instance refers to the default non-transactional instance. However, if the CLS context is _not active_, the `Transaction` instance will be `undefined` instead, which could cause runtime errors. -Therefore, this feature works reliably only when the CLS context is active _prior to starting the transaction_, which should be the case in most cases, however, for that reason, this is an opt-in feature that must be explicitly enabled with the `enableTransactionProxy: true` option of the `ClsPluginTransactional` constructor. +Therefore, this feature works reliably only when the CLS context is active _prior to starting the transaction_. + +Additionally, _some adapters do not support this feature_ due to the nature of how transactions work in the library they implement. + +For these reasons, this is an opt-in feature that must be explicitly enabled with the `enableTransactionProxy: true` option of the `ClsPluginTransactional` constructor. ```ts new ClsPluginTransactional({ @@ -223,7 +227,7 @@ new ClsPluginTransactional({ // highlight-start enableTransactionProxy: true, // highlight-end -}), +}); ``` ::: diff --git a/packages/transactional/src/lib/interfaces.ts b/packages/transactional/src/lib/interfaces.ts index 20de5cd7..06b85420 100644 --- a/packages/transactional/src/lib/interfaces.ts +++ b/packages/transactional/src/lib/interfaces.ts @@ -20,7 +20,6 @@ export interface MergedTransactionalAdapterOptions connectionName: string | undefined; enableTransactionProxy: boolean; defaultTxOptions: Partial; - onModuleInit?: () => void | Promise; } export type TransactionalOptionsAdapterFactory = ( @@ -59,6 +58,15 @@ export interface TransactionalAdapter TTx, TOptions >; + + /** + * Whether this adapter support the {@link TransactionalPluginOptions.enableTransactionProxy} option. + * + * The default is `true`. Set to `false` to explicitly forbid this feature. + * + * When set to `false`, and {@link TransactionalPluginOptions.enableTransactionProxy} is `true`, an error will be thrown. + */ + supportsTransactionProxy?: boolean; } export interface TransactionalPluginOptions { @@ -78,6 +86,8 @@ export interface TransactionalPluginOptions { * Whether to enable injecting the Transaction instance directly using `@InjectTransaction()` * * Default: `false` + * + * Note: Not all adapters support this feature, please refer to the docs for the adapter you are using. */ enableTransactionProxy?: boolean; } diff --git a/packages/transactional/src/lib/plugin-transactional.ts b/packages/transactional/src/lib/plugin-transactional.ts index f730a16e..6eec8c8b 100644 --- a/packages/transactional/src/lib/plugin-transactional.ts +++ b/packages/transactional/src/lib/plugin-transactional.ts @@ -4,6 +4,7 @@ import { getTransactionToken } from './inject-transaction.decorator'; import { MergedTransactionalAdapterOptions, OptionalLifecycleHooks, + TransactionalAdapter, TransactionalPluginOptions, } from './interfaces'; import { @@ -60,6 +61,9 @@ export class ClsPluginTransactional implements ClsPlugin { this.exports.push(transactionHostToken); if (options.enableTransactionProxy) { + if (options.adapter.supportsTransactionProxy === false) { + throw new TransactionProxyUnsupportedError(options.adapter); + } const transactionProxyToken = getTransactionToken( options.connectionName, ); @@ -98,3 +102,11 @@ export class ClsPluginTransactional implements ClsPlugin { }; } } + +export class TransactionProxyUnsupportedError extends Error { + constructor(adapter: TransactionalAdapter) { + super( + `The adapter ${adapter.constructor.name} does not support the "Transaction Proxy" feature, please disable the "enableTransactionProxy" option.`, + ); + } +} diff --git a/packages/transactional/test/inject-transaction.spec.ts b/packages/transactional/test/inject-transaction.spec.ts index b5bd3d05..d91898eb 100644 --- a/packages/transactional/test/inject-transaction.spec.ts +++ b/packages/transactional/test/inject-transaction.spec.ts @@ -8,6 +8,7 @@ import { Transaction, Transactional, TransactionHost, + TransactionProxyUnsupportedError, } from '../src'; import { MockDbConnection, @@ -201,3 +202,29 @@ describe('InjectTransaction with multiple named connections', () => { }); }); }); + +class TransactionAdapterMockWithoutTransactionProxySupport extends TransactionAdapterMock { + supportsTransactionProxy = false; +} + +describe('Using enableTransactionProxy when the adapter does not support it', () => { + it('should throw an error', async () => { + const modulePromise = () => + Test.createTestingModule({ + imports: [ + ClsModule.forRoot({ + plugins: [ + new ClsPluginTransactional({ + enableTransactionProxy: true, + adapter: + new TransactionAdapterMockWithoutTransactionProxySupport( + { connectionToken: MockDbConnection1 }, + ), + }), + ], + }), + ], + }).compile(); + expect(modulePromise).toThrowError(TransactionProxyUnsupportedError); + }); +});