Skip to content

Commit e74044a

Browse files
chore(orchestration): improve transaction errors (#12951)
1 parent 919c53e commit e74044a

File tree

4 files changed

+171
-11
lines changed

4 files changed

+171
-11
lines changed

.changeset/angry-avocados-shave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@medusajs/orchestration": patch
3+
---
4+
5+
chore(orchestration): improve transaction error handling

packages/core/orchestration/src/__tests__/transaction/transaction-orchestrator.ts

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { TransactionStepState, TransactionStepStatus } from "@medusajs/utils"
1+
import {
2+
MedusaError,
3+
TransactionStepState,
4+
TransactionStepStatus,
5+
} from "@medusajs/utils"
26
import { setTimeout } from "timers/promises"
37
import {
48
DistributedTransaction,
@@ -690,6 +694,136 @@ describe("Transaction Orchestrator", () => {
690694
expect(transaction.getState()).toBe(TransactionState.FAILED)
691695
})
692696

697+
it("Should handle multiple types of errors", async () => {
698+
const errorTypes = [
699+
new Error("Regular error object"),
700+
new MedusaError(MedusaError.Types.NOT_FOUND, "Not found error"),
701+
{ message: "Custom error object" },
702+
"String error",
703+
123,
704+
{},
705+
null,
706+
[1, 2, "b"],
707+
new Date(),
708+
]
709+
async function handler(
710+
actionId: string,
711+
functionHandlerType: TransactionHandlerType,
712+
payload: TransactionPayload
713+
) {
714+
const command = {
715+
[actionId]: {
716+
[TransactionHandlerType.INVOKE]: () => {
717+
throw errorTypes[+actionId.slice(-1) - 1]
718+
},
719+
},
720+
}
721+
722+
return command[actionId][functionHandlerType](payload)
723+
}
724+
725+
const flow: TransactionStepsDefinition = {
726+
next: Array.from({ length: errorTypes.length }, (_, i) => ({
727+
action: `a${i + 1}`,
728+
maxRetries: 0,
729+
noCompensation: true,
730+
})),
731+
}
732+
733+
const strategy = new TransactionOrchestrator({
734+
id: "transaction-name",
735+
definition: flow,
736+
})
737+
738+
const transaction = await strategy.beginTransaction({
739+
transactionId: "transaction_id_123",
740+
handler,
741+
})
742+
743+
await strategy.resume(transaction)
744+
745+
expect(transaction.getErrors()).toEqual([
746+
{
747+
action: "a1",
748+
handlerType: "invoke",
749+
error: {
750+
message: "Regular error object",
751+
name: "Error",
752+
stack: expect.stringContaining("transaction-name -> a1 (invoke)"),
753+
},
754+
},
755+
{
756+
action: "a2",
757+
handlerType: "invoke",
758+
error: {
759+
message: "Not found error",
760+
name: "Error",
761+
stack: expect.stringContaining("transaction-name -> a2 (invoke)"),
762+
type: "not_found",
763+
__isMedusaError: true,
764+
code: undefined,
765+
date: expect.any(Date),
766+
},
767+
},
768+
{
769+
action: "a3",
770+
handlerType: "invoke",
771+
error: {
772+
message: "Custom error object",
773+
stack: expect.stringContaining("transaction-name -> a3 (invoke)"),
774+
},
775+
},
776+
{
777+
action: "a4",
778+
handlerType: "invoke",
779+
error: {
780+
message: '"String error"',
781+
stack: expect.stringContaining("transaction-name -> a4 (invoke)"),
782+
},
783+
},
784+
{
785+
action: "a5",
786+
handlerType: "invoke",
787+
error: {
788+
message: "123",
789+
stack: expect.stringContaining("transaction-name -> a5 (invoke)"),
790+
},
791+
},
792+
{
793+
action: "a6",
794+
handlerType: "invoke",
795+
error: {
796+
message: "{}",
797+
stack: expect.stringContaining("transaction-name -> a6 (invoke)"),
798+
},
799+
},
800+
{
801+
action: "a7",
802+
handlerType: "invoke",
803+
error: {
804+
message: "null",
805+
stack: expect.stringContaining("transaction-name -> a7 (invoke)"),
806+
},
807+
},
808+
{
809+
action: "a8",
810+
handlerType: "invoke",
811+
error: {
812+
message: '[1,2,"b"]',
813+
stack: expect.stringContaining("transaction-name -> a8 (invoke)"),
814+
},
815+
},
816+
{
817+
action: "a9",
818+
handlerType: "invoke",
819+
error: {
820+
message: expect.any(String),
821+
stack: expect.stringContaining("transaction-name -> a9 (invoke)"),
822+
},
823+
},
824+
])
825+
})
826+
693827
it("Should complete a transaction if a failing step has the flag 'continueOnPermanentFailure' set to true", async () => {
694828
const mocks = {
695829
one: jest.fn().mockImplementation((payload) => {

packages/core/orchestration/src/transaction/transaction-orchestrator.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,20 @@ export class TransactionOrchestrator extends EventEmitter {
686686

687687
if (isErrorLike(error)) {
688688
error = serializeError(error)
689+
} else {
690+
try {
691+
if (error?.message) {
692+
error = JSON.parse(JSON.stringify(error))
693+
} else {
694+
error = {
695+
message: JSON.stringify(error),
696+
}
697+
}
698+
} catch (e) {
699+
error = {
700+
message: "Unknown non-serializable error",
701+
}
702+
}
689703
}
690704

691705
if (
@@ -712,15 +726,15 @@ export class TransactionOrchestrator extends EventEmitter {
712726
? TransactionHandlerType.COMPENSATE
713727
: TransactionHandlerType.INVOKE
714728

715-
if (error?.stack) {
716-
const workflowId = transaction.modelId
717-
const stepAction = step.definition.action
718-
const sourcePath = transaction.getFlow().metadata?.sourcePath
719-
const sourceStack = sourcePath
720-
? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
721-
: `\n⮑ \sat [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
722-
error.stack += sourceStack
723-
}
729+
error.stack ??= ""
730+
731+
const workflowId = transaction.modelId
732+
const stepAction = step.definition.action
733+
const sourcePath = transaction.getFlow().metadata?.sourcePath
734+
const sourceStack = sourcePath
735+
? `\n⮑ \sat ${sourcePath}: [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
736+
: `\n⮑ \sat [${workflowId} -> ${stepAction} (${TransactionHandlerType.INVOKE})]`
737+
error.stack += sourceStack
724738

725739
transaction.addError(step.definition.action!, handlerType, error)
726740
}

packages/modules/workflow-engine-redis/integration-tests/__tests__/index.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,14 @@ moduleIntegrationTestRunner<IWorkflowEngineService>({
550550
return e
551551
})
552552

553-
expect(setStepError).toEqual({ uhuuuu: "yeaah!" })
553+
expect(setStepError).toEqual(
554+
expect.objectContaining({
555+
message: JSON.stringify({
556+
uhuuuu: "yeaah!",
557+
}),
558+
stack: expect.any(String),
559+
})
560+
)
554561
;({ data: executionsList } = await query.graph({
555562
entity: "workflow_executions",
556563
fields: ["id", "state", "context"],

0 commit comments

Comments
 (0)