-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Create Transaction decorator
- Loading branch information
1 parent
09e48ac
commit 4040089
Showing
20 changed files
with
202 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { applyDecorators, UseInterceptors } from '@nestjs/common'; | ||
|
||
import { TransactionInterceptor } from '../middleware/transaction-interceptor'; | ||
|
||
/** | ||
* @description | ||
* Runs the decorated method in a TypeORM transaction. It works by creating a transctional | ||
* QueryRunner which gets attached to the RequestContext object. When the RequestContext | ||
* is the passed to the {@link TransactionalConnection} `getRepository()` method, this | ||
* QueryRunner is used to execute the queries within this transaction. | ||
* | ||
* @docsCategory request | ||
* @docsPage Decorators | ||
*/ | ||
export const Transaction = applyDecorators(UseInterceptors(TransactionInterceptor)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
packages/core/src/api/middleware/transaction-interceptor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; | ||
import { Observable, of } from 'rxjs'; | ||
import { tap } from 'rxjs/operators'; | ||
|
||
import { REQUEST_CONTEXT_KEY, TRANSACTION_MANAGER_KEY } from '../../common/constants'; | ||
import { TransactionalConnection } from '../../service/transaction/transactional-connection'; | ||
import { parseContext } from '../common/parse-context'; | ||
import { RequestContext } from '../common/request-context'; | ||
|
||
/** | ||
* @description | ||
* Used by the {@link Transaction} decorator to create a transactional query runner | ||
* and attach it to the RequestContext. | ||
*/ | ||
@Injectable() | ||
export class TransactionInterceptor implements NestInterceptor { | ||
constructor(private connection: TransactionalConnection) {} | ||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { | ||
const { isGraphQL, req } = parseContext(context); | ||
const ctx = (req as any)[REQUEST_CONTEXT_KEY]; | ||
if (ctx) { | ||
return of(this.withTransaction(ctx, () => next.handle().toPromise())); | ||
} else { | ||
return next.handle(); | ||
} | ||
} | ||
|
||
/** | ||
* @description | ||
* Executes the `work` function within the context of a transaction. | ||
*/ | ||
private async withTransaction<T>(ctx: RequestContext, work: () => T): Promise<T> { | ||
const queryRunnerExists = !!(ctx as any)[TRANSACTION_MANAGER_KEY]; | ||
if (queryRunnerExists) { | ||
// If a QueryRunner already exists on the RequestContext, there must be an existing | ||
// outer transaction in progress. In that case, we just execute the work function | ||
// as usual without needing to further wrap in a transaction. | ||
return work(); | ||
} | ||
const queryRunner = this.connection.rawConnection.createQueryRunner(); | ||
await queryRunner.startTransaction(); | ||
(ctx as any)[TRANSACTION_MANAGER_KEY] = queryRunner.manager; | ||
|
||
try { | ||
const result = await work(); | ||
if (queryRunner.isTransactionActive) { | ||
await queryRunner.commitTransaction(); | ||
} | ||
return result; | ||
} catch (error) { | ||
if (queryRunner.isTransactionActive) { | ||
await queryRunner.rollbackTransaction(); | ||
} | ||
throw error; | ||
} finally { | ||
await queryRunner.release(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { AdministratorService } from './services/administrator.service'; | ||
import { ChannelService } from './services/channel.service'; | ||
import { GlobalSettingsService } from './services/global-settings.service'; | ||
import { PaymentMethodService } from './services/payment-method.service'; | ||
import { RoleService } from './services/role.service'; | ||
import { ShippingMethodService } from './services/shipping-method.service'; | ||
import { TaxRateService } from './services/tax-rate.service'; | ||
|
||
/** | ||
* Only used internally to run the various service init methods in the correct | ||
* sequence on bootstrap. | ||
*/ | ||
@Injectable() | ||
export class InitializerService { | ||
constructor( | ||
private channelService: ChannelService, | ||
private roleService: RoleService, | ||
private administratorService: AdministratorService, | ||
private taxRateService: TaxRateService, | ||
private shippingMethodService: ShippingMethodService, | ||
private paymentMethodService: PaymentMethodService, | ||
private globalSettingsService: GlobalSettingsService, | ||
) {} | ||
|
||
async onModuleInit() { | ||
// IMPORTANT - why manually invoke these init methods rather than just relying on | ||
// Nest's "onModuleInit" lifecycle hook within each individual service class? | ||
// The reason is that the order of invokation matters. By explicitly invoking the | ||
// methods below, we can e.g. guarantee that the default channel exists | ||
// (channelService.initChannels()) before we try to create any roles (which assume that | ||
// there is a default Channel to work with. | ||
await this.globalSettingsService.initGlobalSettings(); | ||
await this.channelService.initChannels(); | ||
await this.roleService.initRoles(); | ||
await this.administratorService.initAdministrators(); | ||
await this.taxRateService.initTaxRates(); | ||
await this.shippingMethodService.initShippingMethods(); | ||
await this.paymentMethodService.initPaymentMethods(); | ||
} | ||
} |
Oops, something went wrong.