feat(medusa): Export transaction related methods to the transactionBaseService
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
149
packages/medusa/src/interfaces/transaction-base-service.ts
Normal file
149
packages/medusa/src/interfaces/transaction-base-service.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user