feat(medusa): Export transaction related methods to the transactionBaseService

This commit is contained in:
adrien2p
2022-04-17 20:52:48 +02:00
parent 1499bc52e3
commit 99146b7403
2 changed files with 150 additions and 136 deletions

View File

@@ -1,6 +1,5 @@
import { MedusaError } from "medusa-core-utils"
import { EntityManager, FindOperator, In, Raw } from "typeorm"
import { IsolationLevel } from "typeorm/driver/types/IsolationLevel"
import { FindConfig, Writable } from "../types/common"
type Selector<TEntity> = { [key in keyof TEntity]?: unknown }
@@ -24,24 +23,6 @@ export class BaseService<
this.container_ = container
}
withTransaction(transactionManager?: EntityManager): this | TChild {
if (!transactionManager) {
return this
}
const cloned = new (<any>this.constructor)(
{
...this.container_,
manager: transactionManager,
},
this.configModule
)
cloned.transactionManager_ = transactionManager
return cloned as TChild
}
/**
* Used to build TypeORM queries.
* @param selector The selector
@@ -191,122 +172,6 @@ export class BaseService<
return rawId
}
shouldRetryTransaction(
err: { code: string } | Record<string, unknown>
): boolean {
if (!(err as { code: string })?.code) {
return false
}
const code = (err as { code: string })?.code
return code === "40001" || code === "40P01"
}
/**
* Wraps some work within a transactional block. If the service already has
* a transaction manager attached this will be reused, otherwise a new
* transaction manager is created.
* @param work - the transactional work to be done
* @param isolationOrErrorHandler - the isolation level to be used for the work.
* @param maybeErrorHandlerOrDontFail Potential error handler
* @return the result of the transactional work
*/
async atomicPhase_<TResult, TError>(
work: (transactionManager: EntityManager) => Promise<TResult | never>,
isolationOrErrorHandler?:
| IsolationLevel
| ((error: TError) => Promise<never | TResult | void>),
maybeErrorHandlerOrDontFail?: (
error: TError
) => Promise<never | TResult | void>
): Promise<never | TResult> {
let errorHandler = maybeErrorHandlerOrDontFail
let isolation:
| IsolationLevel
| ((error: TError) => Promise<never | TResult | void>)
| undefined
| null = isolationOrErrorHandler
let dontFail = false
if (typeof isolationOrErrorHandler === "function") {
isolation = null
errorHandler = isolationOrErrorHandler
dontFail = !!maybeErrorHandlerOrDontFail
}
if (this.transactionManager_) {
const doWork = async (m: EntityManager): Promise<never | TResult> => {
this.manager_ = m
this.transactionManager_ = m
try {
return await work(m)
} catch (error) {
if (errorHandler) {
const queryRunner = this.transactionManager_.queryRunner
if (queryRunner && queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction()
}
await errorHandler(error)
}
throw error
}
}
return await doWork(this.transactionManager_)
} else {
const temp = this.manager_
const doWork = async (m: EntityManager): Promise<never | TResult> => {
this.manager_ = m
this.transactionManager_ = m
try {
const result = await work(m)
this.manager_ = temp
this.transactionManager_ = undefined
return result
} catch (error) {
this.manager_ = temp
this.transactionManager_ = undefined
throw error
}
}
if (isolation) {
let result
try {
result = await this.manager_.transaction(
isolation as IsolationLevel,
(m) => doWork(m)
)
return result
} catch (error) {
if (this.shouldRetryTransaction(error)) {
return this.manager_.transaction(
isolation as IsolationLevel,
(m): Promise<never | TResult> => doWork(m)
)
} else {
if (errorHandler) {
await errorHandler(error)
}
throw error
}
}
}
try {
return await this.manager_.transaction((m) => doWork(m))
} catch (error) {
if (errorHandler) {
const result = await errorHandler(error)
if (dontFail) {
return result as TResult
}
}
throw error
}
}
}
/**
* Dedicated method to set metadata.
* @param obj - the entity to apply metadata to.
@@ -334,4 +199,4 @@ export class BaseService<
...newData,
}
}
}
}

View File

@@ -0,0 +1,149 @@
import { EntityManager } from "typeorm"
import { IsolationLevel } from "typeorm/driver/types/IsolationLevel"
export abstract class TransactionBaseService<
TChild extends TransactionBaseService<TChild, TContainer>,
TContainer = unknown
> {
protected abstract manager_: EntityManager
protected abstract transactionManager_: EntityManager | undefined
protected constructor(
protected readonly container: TContainer,
protected readonly configModule?: Record<string, unknown>
) {}
withTransaction(transactionManager?: EntityManager): this | TChild {
if (!transactionManager) {
return this
}
const cloned = new (<any>this.constructor)(
{
...this.container,
manager: transactionManager,
},
this.configModule
)
cloned.transactionManager_ = transactionManager
return cloned as TChild
}
shouldRetryTransaction(
err: { code: string } | Record<string, unknown>
): boolean {
if (!(err as { code: string })?.code) {
return false
}
const code = (err as { code: string })?.code
return code === "40001" || code === "40P01"
}
/**
* Wraps some work within a transactional block. If the service already has
* a transaction manager attached this will be reused, otherwise a new
* transaction manager is created.
* @param work - the transactional work to be done
* @param isolationOrErrorHandler - the isolation level to be used for the work.
* @param maybeErrorHandlerOrDontFail Potential error handler
* @return the result of the transactional work
*/
async atomicPhase_<TResult, TError>(
work: (transactionManager: EntityManager) => Promise<TResult | never>,
isolationOrErrorHandler?:
| IsolationLevel
| ((error: TError) => Promise<never | TResult | void>),
maybeErrorHandlerOrDontFail?: (
error: TError
) => Promise<never | TResult | void>
): Promise<never | TResult> {
let errorHandler = maybeErrorHandlerOrDontFail
let isolation:
| IsolationLevel
| ((error: TError) => Promise<never | TResult | void>)
| undefined
| null = isolationOrErrorHandler
let dontFail = false
if (typeof isolationOrErrorHandler === "function") {
isolation = null
errorHandler = isolationOrErrorHandler
dontFail = !!maybeErrorHandlerOrDontFail
}
if (this.transactionManager_) {
const doWork = async (m: EntityManager): Promise<never | TResult> => {
this.manager_ = m
this.transactionManager_ = m
try {
return await work(m)
} catch (error) {
if (errorHandler) {
const queryRunner = this.transactionManager_.queryRunner
if (queryRunner && queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction()
}
await errorHandler(error)
}
throw error
}
}
return await doWork(this.transactionManager_)
} else {
const temp = this.manager_
const doWork = async (m: EntityManager): Promise<never | TResult> => {
this.manager_ = m
this.transactionManager_ = m
try {
const result = await work(m)
this.manager_ = temp
this.transactionManager_ = undefined
return result
} catch (error) {
this.manager_ = temp
this.transactionManager_ = undefined
throw error
}
}
if (isolation && this.manager_) {
let result
try {
result = await this.manager_.transaction(
isolation as IsolationLevel,
(m) => doWork(m)
)
return result
} catch (error) {
if (this.shouldRetryTransaction(error)) {
return this.manager_.transaction(
isolation as IsolationLevel,
(m): Promise<never | TResult> => doWork(m)
)
} else {
if (errorHandler) {
await errorHandler(error)
}
throw error
}
}
}
try {
return await this.manager_.transaction((m) => doWork(m))
} catch (error) {
if (errorHandler) {
const result = await errorHandler(error)
if (dontFail) {
return result as TResult
}
}
throw error
}
}
}
}