Skip to content

Commit

Permalink
feat: add option to inject tx directly as Proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
Papooch committed Feb 16, 2024
1 parent fbb27dc commit 373aa64
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 25 deletions.
2 changes: 2 additions & 0 deletions packages/transactional/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ export * from './lib/transaction-host';
export * from './lib/transactional.decorator';
export * from './lib/plugin-transactional';
export * from './lib/propagation';
export * from './lib/inject-transaction.decorator';
export {
TransactionalAdapterOptions,
TransactionalOptionsAdapterFactory,
TransactionalAdapter,
TransactionalPluginOptions,
Transaction,
} from './lib/interfaces';
23 changes: 23 additions & 0 deletions packages/transactional/src/lib/inject-transaction.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Inject } from '@nestjs/common';
const TRANSACTION_TOKEN = Symbol('TRANSACTION_TOKEN');

/**
* Get injection token for the Transaction instance.
* If name is omitted, the default instance is used.
*/
export function getTransactionToken(connectionName?: string) {
return connectionName
? Symbol.for(`${TRANSACTION_TOKEN.description}_${connectionName}`)
: TRANSACTION_TOKEN;
}

/**
* Inject the Transaction instance directly (that is the `tx` property of the TransactionHost)
*
* Optionally, you can provide a connection name to inject a named instance.
*
* A shorthand for `Inject(getTransactionToken(connectionName))`
*/
export function InjectTransaction(connectionName?: string) {
return Inject(getTransactionToken(connectionName));
}
14 changes: 12 additions & 2 deletions packages/transactional/src/lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ export interface TransactionalAdapterOptions<TTx, TOptions> {
getFallbackInstance: () => TTx;
}

export interface TransactionalAdapterOptionsWithName<TTx, TOptions>
export interface MergedTransactionalAdapterOptions<TTx, TOptions>
extends TransactionalAdapterOptions<TTx, TOptions> {
connectionName: string;
connectionName: string | undefined;
enableTransactionProxy: boolean;
}

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

export type TTxFromAdapter<TAdapter> = TAdapter extends TransactionalAdapter<
Expand All @@ -63,3 +70,6 @@ export type TOptionsFromAdapter<TAdapter> =
TAdapter extends TransactionalAdapter<any, any, infer TOptions>
? TOptions
: never;

export type Transaction<TAdapter extends TransactionalAdapter<any, any, any>> =
TTxFromAdapter<TAdapter>;
37 changes: 30 additions & 7 deletions packages/transactional/src/lib/plugin-transactional.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Provider } from '@nestjs/common';
import { ClsPlugin } from 'nestjs-cls';
import { TransactionalPluginOptions } from './interfaces';
import { ClsModule, ClsPlugin } from 'nestjs-cls';
import { getTransactionToken } from './inject-transaction.decorator';
import {
MergedTransactionalAdapterOptions,
TransactionalPluginOptions,
} from './interfaces';
import {
TRANSACTIONAL_ADAPTER_OPTIONS,
TRANSACTION_CONNECTION,
Expand All @@ -10,14 +14,14 @@ import { getTransactionHostToken, TransactionHost } from './transaction-host';
export class ClsPluginTransactional implements ClsPlugin {
name: string;
providers: Provider[];
imports?: any[];
exports?: any[];
imports: any[] = [];
exports: any[] = [];

constructor(options: TransactionalPluginOptions<any, any, any>) {
this.name = options.connectionName
? `cls-plugin-transactional-${options.connectionName}`
: 'cls-plugin-transactional';
this.imports = options.imports;
this.imports.push(...(options.imports ?? []));
const transactionHostToken = getTransactionHostToken(
options.connectionName,
);
Expand All @@ -29,12 +33,16 @@ export class ClsPluginTransactional implements ClsPlugin {
{
provide: TRANSACTIONAL_ADAPTER_OPTIONS,
inject: [TRANSACTION_CONNECTION],
useFactory: (connection: any) => {
useFactory: (
connection: any,
): MergedTransactionalAdapterOptions<any, any> => {
const adapterOptions =
options.adapter.optionsFactory(connection);
return {
...adapterOptions,
connectionName: options.connectionName,
enableTransactionProxy:
options.enableTransactionProxy ?? false,
};
},
},
Expand All @@ -43,6 +51,21 @@ export class ClsPluginTransactional implements ClsPlugin {
useClass: TransactionHost,
},
];
this.exports = [transactionHostToken];
this.exports.push(transactionHostToken);

if (options.enableTransactionProxy) {
const transactionProxyToken = getTransactionToken(
options.connectionName,
);
this.imports.push(
ClsModule.forFeatureAsync({
provide: transactionProxyToken,
inject: [transactionHostToken],
useFactory: (txHost: TransactionHost) => txHost.tx,
type: 'function',
global: true,
}),
);
}
}
}
8 changes: 4 additions & 4 deletions packages/transactional/src/lib/symbols.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const TRANSACTION_CONNECTION = Symbol('TRANSACTION_CONNECTION');
export const TRANSACTIONAL_ADAPTER_OPTIONS = Symbol('TRANSACTIONAL_OPTIONS');

const TRANSACTIONAL_INSTANCE = Symbol('TRANSACTIONAL_CLIENT');
const TRANSACTION_CLS_KEY = Symbol('TRANSACTION_CLS_KEY');

export const getTransactionalInstanceSymbol = (connectionName?: string) =>
export const getTransactionClsKey = (connectionName?: string) =>
connectionName
? Symbol.for(`${TRANSACTIONAL_INSTANCE.toString()}_${connectionName}`)
: TRANSACTIONAL_INSTANCE;
? Symbol.for(`${TRANSACTION_CLS_KEY.description}_${connectionName}`)
: TRANSACTION_CLS_KEY;
26 changes: 15 additions & 11 deletions packages/transactional/src/lib/transaction-host.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Inject, Injectable, Logger } from '@nestjs/common';
import { ClsServiceManager } from 'nestjs-cls';
import { getTransactionToken } from './inject-transaction.decorator';
import {
TOptionsFromAdapter,
TransactionalAdapterOptionsWithName,
MergedTransactionalAdapterOptions,
TTxFromAdapter,
} from './interfaces';
import {
Expand All @@ -11,25 +12,22 @@ import {
TransactionNotActiveError,
TransactionPropagationError,
} from './propagation';
import {
getTransactionalInstanceSymbol,
TRANSACTIONAL_ADAPTER_OPTIONS,
} from './symbols';
import { getTransactionClsKey, TRANSACTIONAL_ADAPTER_OPTIONS } from './symbols';

@Injectable()
export class TransactionHost<TAdapter = never> {
private readonly cls = ClsServiceManager.getClsService();
private readonly logger = new Logger(TransactionHost.name);
private readonly transactionalInstanceSymbol: symbol;
private readonly transactionInstanceSymbol: symbol;

constructor(
@Inject(TRANSACTIONAL_ADAPTER_OPTIONS)
private readonly _options: TransactionalAdapterOptionsWithName<
private readonly _options: MergedTransactionalAdapterOptions<
TTxFromAdapter<TAdapter>,
TOptionsFromAdapter<TAdapter>
>,
) {
this.transactionalInstanceSymbol = getTransactionalInstanceSymbol(
this.transactionInstanceSymbol = getTransactionClsKey(
this._options.connectionName,
);
}
Expand All @@ -47,7 +45,7 @@ export class TransactionHost<TAdapter = never> {
return this._options.getFallbackInstance();
}
return (
this.cls.get(this.transactionalInstanceSymbol) ??
this.cls.get(this.transactionInstanceSymbol) ??
this._options.getFallbackInstance()
);
}
Expand Down Expand Up @@ -216,11 +214,17 @@ export class TransactionHost<TAdapter = never> {
if (!this.cls.isActive()) {
return false;
}
return !!this.cls.get(this.transactionalInstanceSymbol);
return !!this.cls.get(this.transactionInstanceSymbol);
}

private setTxInstance(txInstance?: TTxFromAdapter<TAdapter>) {
this.cls.set(this.transactionalInstanceSymbol, txInstance);
this.cls.set(this.transactionInstanceSymbol, txInstance);
if (this._options.enableTransactionProxy) {
this.cls.setProxy(
getTransactionToken(this._options.connectionName),
txInstance,
);
}
}
}

Expand Down
Loading

0 comments on commit 373aa64

Please sign in to comment.