Skip to content

Commit 373aa64

Browse files
committed
feat: add option to inject tx directly as Proxy
1 parent fbb27dc commit 373aa64

File tree

8 files changed

+292
-25
lines changed

8 files changed

+292
-25
lines changed

packages/transactional/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ export * from './lib/transaction-host';
22
export * from './lib/transactional.decorator';
33
export * from './lib/plugin-transactional';
44
export * from './lib/propagation';
5+
export * from './lib/inject-transaction.decorator';
56
export {
67
TransactionalAdapterOptions,
78
TransactionalOptionsAdapterFactory,
89
TransactionalAdapter,
910
TransactionalPluginOptions,
11+
Transaction,
1012
} from './lib/interfaces';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Inject } from '@nestjs/common';
2+
const TRANSACTION_TOKEN = Symbol('TRANSACTION_TOKEN');
3+
4+
/**
5+
* Get injection token for the Transaction instance.
6+
* If name is omitted, the default instance is used.
7+
*/
8+
export function getTransactionToken(connectionName?: string) {
9+
return connectionName
10+
? Symbol.for(`${TRANSACTION_TOKEN.description}_${connectionName}`)
11+
: TRANSACTION_TOKEN;
12+
}
13+
14+
/**
15+
* Inject the Transaction instance directly (that is the `tx` property of the TransactionHost)
16+
*
17+
* Optionally, you can provide a connection name to inject a named instance.
18+
*
19+
* A shorthand for `Inject(getTransactionToken(connectionName))`
20+
*/
21+
export function InjectTransaction(connectionName?: string) {
22+
return Inject(getTransactionToken(connectionName));
23+
}

packages/transactional/src/lib/interfaces.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ export interface TransactionalAdapterOptions<TTx, TOptions> {
77
getFallbackInstance: () => TTx;
88
}
99

10-
export interface TransactionalAdapterOptionsWithName<TTx, TOptions>
10+
export interface MergedTransactionalAdapterOptions<TTx, TOptions>
1111
extends TransactionalAdapterOptions<TTx, TOptions> {
12-
connectionName: string;
12+
connectionName: string | undefined;
13+
enableTransactionProxy: boolean;
1314
}
1415

1516
export type TransactionalOptionsAdapterFactory<TConnection, TTx, TOptions> = (
@@ -49,6 +50,12 @@ export interface TransactionalPluginOptions<TConnection, TTx, TOptions> {
4950
* An optional name of the connection. Useful when there are multiple TransactionalPlugins registered in the app.
5051
*/
5152
connectionName?: string;
53+
/**
54+
* Whether to enable injecting the Transaction instance directly using `@InjectTransaction()`
55+
*
56+
* Default: `true`
57+
*/
58+
enableTransactionProxy?: boolean;
5259
}
5360

5461
export type TTxFromAdapter<TAdapter> = TAdapter extends TransactionalAdapter<
@@ -63,3 +70,6 @@ export type TOptionsFromAdapter<TAdapter> =
6370
TAdapter extends TransactionalAdapter<any, any, infer TOptions>
6471
? TOptions
6572
: never;
73+
74+
export type Transaction<TAdapter extends TransactionalAdapter<any, any, any>> =
75+
TTxFromAdapter<TAdapter>;
Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Provider } from '@nestjs/common';
2-
import { ClsPlugin } from 'nestjs-cls';
3-
import { TransactionalPluginOptions } from './interfaces';
2+
import { ClsModule, ClsPlugin } from 'nestjs-cls';
3+
import { getTransactionToken } from './inject-transaction.decorator';
4+
import {
5+
MergedTransactionalAdapterOptions,
6+
TransactionalPluginOptions,
7+
} from './interfaces';
48
import {
59
TRANSACTIONAL_ADAPTER_OPTIONS,
610
TRANSACTION_CONNECTION,
@@ -10,14 +14,14 @@ import { getTransactionHostToken, TransactionHost } from './transaction-host';
1014
export class ClsPluginTransactional implements ClsPlugin {
1115
name: string;
1216
providers: Provider[];
13-
imports?: any[];
14-
exports?: any[];
17+
imports: any[] = [];
18+
exports: any[] = [];
1519

1620
constructor(options: TransactionalPluginOptions<any, any, any>) {
1721
this.name = options.connectionName
1822
? `cls-plugin-transactional-${options.connectionName}`
1923
: 'cls-plugin-transactional';
20-
this.imports = options.imports;
24+
this.imports.push(...(options.imports ?? []));
2125
const transactionHostToken = getTransactionHostToken(
2226
options.connectionName,
2327
);
@@ -29,12 +33,16 @@ export class ClsPluginTransactional implements ClsPlugin {
2933
{
3034
provide: TRANSACTIONAL_ADAPTER_OPTIONS,
3135
inject: [TRANSACTION_CONNECTION],
32-
useFactory: (connection: any) => {
36+
useFactory: (
37+
connection: any,
38+
): MergedTransactionalAdapterOptions<any, any> => {
3339
const adapterOptions =
3440
options.adapter.optionsFactory(connection);
3541
return {
3642
...adapterOptions,
3743
connectionName: options.connectionName,
44+
enableTransactionProxy:
45+
options.enableTransactionProxy ?? false,
3846
};
3947
},
4048
},
@@ -43,6 +51,21 @@ export class ClsPluginTransactional implements ClsPlugin {
4351
useClass: TransactionHost,
4452
},
4553
];
46-
this.exports = [transactionHostToken];
54+
this.exports.push(transactionHostToken);
55+
56+
if (options.enableTransactionProxy) {
57+
const transactionProxyToken = getTransactionToken(
58+
options.connectionName,
59+
);
60+
this.imports.push(
61+
ClsModule.forFeatureAsync({
62+
provide: transactionProxyToken,
63+
inject: [transactionHostToken],
64+
useFactory: (txHost: TransactionHost) => txHost.tx,
65+
type: 'function',
66+
global: true,
67+
}),
68+
);
69+
}
4770
}
4871
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export const TRANSACTION_CONNECTION = Symbol('TRANSACTION_CONNECTION');
22
export const TRANSACTIONAL_ADAPTER_OPTIONS = Symbol('TRANSACTIONAL_OPTIONS');
33

4-
const TRANSACTIONAL_INSTANCE = Symbol('TRANSACTIONAL_CLIENT');
4+
const TRANSACTION_CLS_KEY = Symbol('TRANSACTION_CLS_KEY');
55

6-
export const getTransactionalInstanceSymbol = (connectionName?: string) =>
6+
export const getTransactionClsKey = (connectionName?: string) =>
77
connectionName
8-
? Symbol.for(`${TRANSACTIONAL_INSTANCE.toString()}_${connectionName}`)
9-
: TRANSACTIONAL_INSTANCE;
8+
? Symbol.for(`${TRANSACTION_CLS_KEY.description}_${connectionName}`)
9+
: TRANSACTION_CLS_KEY;

packages/transactional/src/lib/transaction-host.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Inject, Injectable, Logger } from '@nestjs/common';
22
import { ClsServiceManager } from 'nestjs-cls';
3+
import { getTransactionToken } from './inject-transaction.decorator';
34
import {
45
TOptionsFromAdapter,
5-
TransactionalAdapterOptionsWithName,
6+
MergedTransactionalAdapterOptions,
67
TTxFromAdapter,
78
} from './interfaces';
89
import {
@@ -11,25 +12,22 @@ import {
1112
TransactionNotActiveError,
1213
TransactionPropagationError,
1314
} from './propagation';
14-
import {
15-
getTransactionalInstanceSymbol,
16-
TRANSACTIONAL_ADAPTER_OPTIONS,
17-
} from './symbols';
15+
import { getTransactionClsKey, TRANSACTIONAL_ADAPTER_OPTIONS } from './symbols';
1816

1917
@Injectable()
2018
export class TransactionHost<TAdapter = never> {
2119
private readonly cls = ClsServiceManager.getClsService();
2220
private readonly logger = new Logger(TransactionHost.name);
23-
private readonly transactionalInstanceSymbol: symbol;
21+
private readonly transactionInstanceSymbol: symbol;
2422

2523
constructor(
2624
@Inject(TRANSACTIONAL_ADAPTER_OPTIONS)
27-
private readonly _options: TransactionalAdapterOptionsWithName<
25+
private readonly _options: MergedTransactionalAdapterOptions<
2826
TTxFromAdapter<TAdapter>,
2927
TOptionsFromAdapter<TAdapter>
3028
>,
3129
) {
32-
this.transactionalInstanceSymbol = getTransactionalInstanceSymbol(
30+
this.transactionInstanceSymbol = getTransactionClsKey(
3331
this._options.connectionName,
3432
);
3533
}
@@ -47,7 +45,7 @@ export class TransactionHost<TAdapter = never> {
4745
return this._options.getFallbackInstance();
4846
}
4947
return (
50-
this.cls.get(this.transactionalInstanceSymbol) ??
48+
this.cls.get(this.transactionInstanceSymbol) ??
5149
this._options.getFallbackInstance()
5250
);
5351
}
@@ -216,11 +214,17 @@ export class TransactionHost<TAdapter = never> {
216214
if (!this.cls.isActive()) {
217215
return false;
218216
}
219-
return !!this.cls.get(this.transactionalInstanceSymbol);
217+
return !!this.cls.get(this.transactionInstanceSymbol);
220218
}
221219

222220
private setTxInstance(txInstance?: TTxFromAdapter<TAdapter>) {
223-
this.cls.set(this.transactionalInstanceSymbol, txInstance);
221+
this.cls.set(this.transactionInstanceSymbol, txInstance);
222+
if (this._options.enableTransactionProxy) {
223+
this.cls.setProxy(
224+
getTransactionToken(this._options.connectionName),
225+
txInstance,
226+
);
227+
}
224228
}
225229
}
226230

0 commit comments

Comments
 (0)