chore(orchestration): improve transaction errors (#12951)
This commit is contained in:
committed by
GitHub
parent
919c53e44e
commit
e74044af4d
@@ -1,4 +1,8 @@
|
||||
import { TransactionStepState, TransactionStepStatus } from "@medusajs/utils"
|
||||
import {
|
||||
MedusaError,
|
||||
TransactionStepState,
|
||||
TransactionStepStatus,
|
||||
} from "@medusajs/utils"
|
||||
import { setTimeout } from "timers/promises"
|
||||
import {
|
||||
DistributedTransaction,
|
||||
@@ -690,6 +694,136 @@ describe("Transaction Orchestrator", () => {
|
||||
expect(transaction.getState()).toBe(TransactionState.FAILED)
|
||||
})
|
||||
|
||||
it("Should handle multiple types of errors", async () => {
|
||||
const errorTypes = [
|
||||
new Error("Regular error object"),
|
||||
new MedusaError(MedusaError.Types.NOT_FOUND, "Not found error"),
|
||||
{ message: "Custom error object" },
|
||||
"String error",
|
||||
123,
|
||||
{},
|
||||
null,
|
||||
[1, 2, "b"],
|
||||
new Date(),
|
||||
]
|
||||
async function handler(
|
||||
actionId: string,
|
||||
functionHandlerType: TransactionHandlerType,
|
||||
payload: TransactionPayload
|
||||
) {
|
||||
const command = {
|
||||
[actionId]: {
|
||||
[TransactionHandlerType.INVOKE]: () => {
|
||||
throw errorTypes[+actionId.slice(-1) - 1]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return command[actionId][functionHandlerType](payload)
|
||||
}
|
||||
|
||||
const flow: TransactionStepsDefinition = {
|
||||
next: Array.from({ length: errorTypes.length }, (_, i) => ({
|
||||
action: `a${i + 1}`,
|
||||
maxRetries: 0,
|
||||
noCompensation: true,
|
||||
})),
|
||||
}
|
||||
|
||||
const strategy = new TransactionOrchestrator({
|
||||
id: "transaction-name",
|
||||
definition: flow,
|
||||
})
|
||||
|
||||
const transaction = await strategy.beginTransaction({
|
||||
transactionId: "transaction_id_123",
|
||||
handler,
|
||||
})
|
||||
|
||||
await strategy.resume(transaction)
|
||||
|
||||
expect(transaction.getErrors()).toEqual([
|
||||
{
|
||||
action: "a1",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: "Regular error object",
|
||||
name: "Error",
|
||||
stack: expect.stringContaining("transaction-name -> a1 (invoke)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a2",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: "Not found error",
|
||||
name: "Error",
|
||||
stack: expect.stringContaining("transaction-name -> a2 (invoke)"),
|
||||
type: "not_found",
|
||||
__isMedusaError: true,
|
||||
code: undefined,
|
||||
date: expect.any(Date),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a3",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: "Custom error object",
|
||||
stack: expect.stringContaining("transaction-name -> a3 (invoke)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a4",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: '"String error"',
|
||||
stack: expect.stringContaining("transaction-name -> a4 (invoke)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a5",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: "123",
|
||||
stack: expect.stringContaining("transaction-name -> a5 (invoke)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a6",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: "{}",
|
||||
stack: expect.stringContaining("transaction-name -> a6 (invoke)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a7",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: "null",
|
||||
stack: expect.stringContaining("transaction-name -> a7 (invoke)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a8",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: '[1,2,"b"]',
|
||||
stack: expect.stringContaining("transaction-name -> a8 (invoke)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "a9",
|
||||
handlerType: "invoke",
|
||||
error: {
|
||||
message: expect.any(String),
|
||||
stack: expect.stringContaining("transaction-name -> a9 (invoke)"),
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("Should complete a transaction if a failing step has the flag 'continueOnPermanentFailure' set to true", async () => {
|
||||
const mocks = {
|
||||
one: jest.fn().mockImplementation((payload) => {
|
||||
|
||||
@@ -686,6 +686,20 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
|
||||
if (isErrorLike(error)) {
|
||||
error = serializeError(error)
|
||||
} else {
|
||||
try {
|
||||
if (error?.message) {
|
||||
error = JSON.parse(JSON.stringify(error))
|
||||
} else {
|
||||
error = {
|
||||
message: JSON.stringify(error),
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
error = {
|
||||
message: "Unknown non-serializable error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -712,15 +726,15 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
? TransactionHandlerType.COMPENSATE
|
||||
: TransactionHandlerType.INVOKE
|
||||
|
||||
if (error?.stack) {
|
||||
const workflowId = transaction.modelId
|
||||
const stepAction = step.definition.action
|
||||
const sourcePath = transaction.getFlow().metadata?.sourcePath
|
||||
const sourceStack = sourcePath
|
||||
? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
|
||||
: `\n⮑ \sat [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
|
||||
error.stack += sourceStack
|
||||
}
|
||||
error.stack ??= ""
|
||||
|
||||
const workflowId = transaction.modelId
|
||||
const stepAction = step.definition.action
|
||||
const sourcePath = transaction.getFlow().metadata?.sourcePath
|
||||
const sourceStack = sourcePath
|
||||
? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
|
||||
: `\n⮑ \sat [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
|
||||
error.stack += sourceStack
|
||||
|
||||
transaction.addError(step.definition.action!, handlerType, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user