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 { MedusaError } from "medusa-core-utils"
|
||||||
import { EntityManager, FindOperator, In, Raw } from "typeorm"
|
import { EntityManager, FindOperator, In, Raw } from "typeorm"
|
||||||
import { IsolationLevel } from "typeorm/driver/types/IsolationLevel"
|
|
||||||
import { FindConfig, Writable } from "../types/common"
|
import { FindConfig, Writable } from "../types/common"
|
||||||
|
|
||||||
type Selector<TEntity> = { [key in keyof TEntity]?: unknown }
|
type Selector<TEntity> = { [key in keyof TEntity]?: unknown }
|
||||||
@@ -24,24 +23,6 @@ export class BaseService<
|
|||||||
this.container_ = container
|
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.
|
* Used to build TypeORM queries.
|
||||||
* @param selector The selector
|
* @param selector The selector
|
||||||
@@ -191,122 +172,6 @@ export class BaseService<
|
|||||||
return rawId
|
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.
|
* Dedicated method to set metadata.
|
||||||
* @param obj - the entity to apply metadata to.
|
* @param obj - the entity to apply metadata to.
|
||||||
@@ -334,4 +199,4 @@ export class BaseService<
|
|||||||
...newData,
|
...newData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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