fix(orchestration): fix set step failure (#10031)
What: - copy data before saving checkpoint - removed unused data format function - properly handle registerStepFailure to not throw - emit onFinish event even when execution failed
This commit is contained in:
1 parent
7794faf49e
commit
1eef324af3
10 files changed
+214
-209
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/workflow-engine-inmemory": patch
|
||||||
|
"@medusajs/workflow-engine-redis": patch
|
||||||
|
"@medusajs/orchestration": patch
|
||||||
|
"@medusajs/workflows-sdk": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix set step failure for async steps
|
||||||
@@ -215,9 +215,11 @@ class DistributedTransaction extends EventEmitter {
|
|||||||
this.modelId,
|
this.modelId,
|
||||||
this.transactionId
|
this.transactionId
|
||||||
)
|
)
|
||||||
await DistributedTransaction.keyValueStore.save(key, data, ttl, options)
|
|
||||||
|
|
||||||
return data
|
const rawData = JSON.parse(JSON.stringify(data))
|
||||||
|
await DistributedTransaction.keyValueStore.save(key, rawData, ttl, options)
|
||||||
|
|
||||||
|
return rawData
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async loadTransaction(
|
public static async loadTransaction(
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
import { createMedusaContainer } from "@medusajs/utils"
|
|
||||||
import { MedusaWorkflow } from "../../medusa-workflow"
|
|
||||||
import { exportWorkflow } from "../workflow-export"
|
|
||||||
|
|
||||||
jest.mock("@medusajs/orchestration", () => {
|
|
||||||
return {
|
|
||||||
TransactionHandlerType: {
|
|
||||||
INVOKE: "invoke",
|
|
||||||
COMPENSATE: "compensate",
|
|
||||||
},
|
|
||||||
TransactionState: {
|
|
||||||
FAILED: "failed",
|
|
||||||
REVERTED: "reverted",
|
|
||||||
},
|
|
||||||
LocalWorkflow: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
run: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
getErrors: jest.fn(),
|
|
||||||
getState: jest.fn(() => "done"),
|
|
||||||
getContext: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
invoke: { result_step: "invoke_test" },
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
registerStepSuccess: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
getErrors: jest.fn(),
|
|
||||||
getState: jest.fn(() => "done"),
|
|
||||||
getContext: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
invoke: { result_step: "invoke_test" },
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
registerStepFailure: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
getErrors: jest.fn(),
|
|
||||||
getState: jest.fn(() => "done"),
|
|
||||||
getContext: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
invoke: { result_step: "invoke_test" },
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
cancel: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
getErrors: jest.fn(),
|
|
||||||
getState: jest.fn(() => "reverted"),
|
|
||||||
getContext: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
invoke: { result_step: "invoke_test" },
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("Export Workflow", function () {
|
|
||||||
afterEach(() => {
|
|
||||||
MedusaWorkflow.workflows = {}
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should prepare the input data before initializing the transaction", async function () {
|
|
||||||
let transformedInput
|
|
||||||
const prepare = jest.fn().mockImplementation(async (data) => {
|
|
||||||
data.__transformed = true
|
|
||||||
transformedInput = data
|
|
||||||
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
|
|
||||||
const work = exportWorkflow("id" as any, "result_step", prepare)
|
|
||||||
|
|
||||||
const container = createMedusaContainer()
|
|
||||||
const wfHandler = work(container)
|
|
||||||
|
|
||||||
const input = {
|
|
||||||
test: "payload",
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result } = await wfHandler.run({
|
|
||||||
input,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(input).toEqual({
|
|
||||||
test: "payload",
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(transformedInput).toEqual({
|
|
||||||
test: "payload",
|
|
||||||
__transformed: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result).toEqual("invoke_test")
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("Using the exported workflow run", function () {
|
|
||||||
afterEach(() => {
|
|
||||||
MedusaWorkflow.workflows = {}
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should prepare the input data before initializing the transaction", async function () {
|
|
||||||
let transformedInput
|
|
||||||
const prepare = jest.fn().mockImplementation(async (data) => {
|
|
||||||
data.__transformed = true
|
|
||||||
transformedInput = data
|
|
||||||
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
|
|
||||||
const work = exportWorkflow("id" as any, "result_step", prepare)
|
|
||||||
|
|
||||||
const input = {
|
|
||||||
test: "payload",
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = createMedusaContainer()
|
|
||||||
|
|
||||||
const { result } = await work.run({
|
|
||||||
input,
|
|
||||||
container,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(input).toEqual({
|
|
||||||
test: "payload",
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(transformedInput).toEqual({
|
|
||||||
test: "payload",
|
|
||||||
__transformed: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result).toEqual("invoke_test")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
DistributedTransactionEvents,
|
DistributedTransactionEvents,
|
||||||
DistributedTransactionType,
|
DistributedTransactionType,
|
||||||
LocalWorkflow,
|
LocalWorkflow,
|
||||||
TransactionHandlerType,
|
|
||||||
TransactionState,
|
TransactionState,
|
||||||
} from "@medusajs/orchestration"
|
} from "@medusajs/orchestration"
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +17,7 @@ import {
|
|||||||
isPresent,
|
isPresent,
|
||||||
MedusaContextType,
|
MedusaContextType,
|
||||||
Modules,
|
Modules,
|
||||||
|
TransactionHandlerType,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { ulid } from "ulid"
|
import { ulid } from "ulid"
|
||||||
@@ -41,13 +41,11 @@ function createContextualWorkflowRunner<
|
|||||||
>({
|
>({
|
||||||
workflowId,
|
workflowId,
|
||||||
defaultResult,
|
defaultResult,
|
||||||
dataPreparation,
|
|
||||||
options,
|
options,
|
||||||
container,
|
container,
|
||||||
}: {
|
}: {
|
||||||
workflowId: string
|
workflowId: string
|
||||||
defaultResult?: string | Symbol
|
defaultResult?: string | Symbol
|
||||||
dataPreparation?: (data: TData) => Promise<unknown>
|
|
||||||
options?: {
|
options?: {
|
||||||
wrappedInput?: boolean
|
wrappedInput?: boolean
|
||||||
sourcePath?: string
|
sourcePath?: string
|
||||||
@@ -110,7 +108,10 @@ function createContextualWorkflowRunner<
|
|||||||
events,
|
events,
|
||||||
flowMetadata,
|
flowMetadata,
|
||||||
]
|
]
|
||||||
const transaction = await method.apply(method, args) as DistributedTransactionType
|
const transaction = (await method.apply(
|
||||||
|
method,
|
||||||
|
args
|
||||||
|
)) as DistributedTransactionType
|
||||||
|
|
||||||
let errors = transaction.getErrors(TransactionHandlerType.INVOKE)
|
let errors = transaction.getErrors(TransactionHandlerType.INVOKE)
|
||||||
|
|
||||||
@@ -118,23 +119,33 @@ function createContextualWorkflowRunner<
|
|||||||
const isCancelled =
|
const isCancelled =
|
||||||
isCancel && transaction.getState() === TransactionState.REVERTED
|
isCancel && transaction.getState() === TransactionState.REVERTED
|
||||||
|
|
||||||
|
const isRegisterStepFailure =
|
||||||
|
method === originalRegisterStepFailure &&
|
||||||
|
transaction.getState() === TransactionState.REVERTED
|
||||||
|
|
||||||
|
let thrownError = null
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isCancelled &&
|
|
||||||
failedStatus.includes(transaction.getState()) &&
|
failedStatus.includes(transaction.getState()) &&
|
||||||
throwOnError
|
!isCancelled &&
|
||||||
|
!isRegisterStepFailure
|
||||||
) {
|
) {
|
||||||
/*const errorMessage = errors
|
|
||||||
?.map((err) => `${err.error?.message}${EOL}${err.error?.stack}`)
|
|
||||||
?.join(`${EOL}`)*/
|
|
||||||
const firstError = errors?.[0]?.error ?? new Error("Unknown error")
|
const firstError = errors?.[0]?.error ?? new Error("Unknown error")
|
||||||
|
|
||||||
|
thrownError = firstError
|
||||||
|
|
||||||
|
if (throwOnError) {
|
||||||
throw firstError
|
throw firstError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let result
|
let result
|
||||||
if (options?.wrappedInput) {
|
if (options?.wrappedInput) {
|
||||||
result = resolveValue(resultFrom, transaction.getContext())
|
result = resolveValue(resultFrom, transaction.getContext())
|
||||||
if (result instanceof Promise) {
|
if (result instanceof Promise) {
|
||||||
result = await result.catch((e) => {
|
result = await result.catch((e) => {
|
||||||
|
thrownError = e
|
||||||
|
|
||||||
if (throwOnError) {
|
if (throwOnError) {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
@@ -151,6 +162,7 @@ function createContextualWorkflowRunner<
|
|||||||
errors,
|
errors,
|
||||||
transaction,
|
transaction,
|
||||||
result,
|
result,
|
||||||
|
thrownError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,22 +187,6 @@ function createContextualWorkflowRunner<
|
|||||||
context.transactionId ??= ulid()
|
context.transactionId ??= ulid()
|
||||||
context.eventGroupId ??= ulid()
|
context.eventGroupId ??= ulid()
|
||||||
|
|
||||||
if (typeof dataPreparation === "function") {
|
|
||||||
try {
|
|
||||||
const copyInput = input ? JSON.parse(JSON.stringify(input)) : input
|
|
||||||
input = await dataPreparation(copyInput as TData)
|
|
||||||
} catch (err) {
|
|
||||||
if (throwOnError) {
|
|
||||||
throw new Error(
|
|
||||||
`Data preparation failed: ${err.message}${EOL}${err.stack}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
errors: [err],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await originalExecution(
|
return await originalExecution(
|
||||||
originalRun,
|
originalRun,
|
||||||
{
|
{
|
||||||
@@ -339,7 +335,6 @@ function createContextualWorkflowRunner<
|
|||||||
export const exportWorkflow = <TData = unknown, TResult = unknown>(
|
export const exportWorkflow = <TData = unknown, TResult = unknown>(
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
defaultResult?: string | Symbol,
|
defaultResult?: string | Symbol,
|
||||||
dataPreparation?: (data: TData) => Promise<unknown>,
|
|
||||||
options?: {
|
options?: {
|
||||||
wrappedInput?: boolean
|
wrappedInput?: boolean
|
||||||
sourcePath?: string
|
sourcePath?: string
|
||||||
@@ -364,7 +359,6 @@ export const exportWorkflow = <TData = unknown, TResult = unknown>(
|
|||||||
>({
|
>({
|
||||||
workflowId,
|
workflowId,
|
||||||
defaultResult,
|
defaultResult,
|
||||||
dataPreparation,
|
|
||||||
options,
|
options,
|
||||||
container,
|
container,
|
||||||
})
|
})
|
||||||
@@ -390,7 +384,6 @@ export const exportWorkflow = <TData = unknown, TResult = unknown>(
|
|||||||
>({
|
>({
|
||||||
workflowId,
|
workflowId,
|
||||||
defaultResult,
|
defaultResult,
|
||||||
dataPreparation,
|
|
||||||
options,
|
options,
|
||||||
container,
|
container,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -152,15 +152,10 @@ export function createWorkflow<TData, TResult, THooks extends any[]>(
|
|||||||
WorkflowManager.register(name, context.flow, handlers, options)
|
WorkflowManager.register(name, context.flow, handlers, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflow = exportWorkflow<TData, TResult>(
|
const workflow = exportWorkflow<TData, TResult>(name, returnedStep, {
|
||||||
name,
|
|
||||||
returnedStep,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
wrappedInput: true,
|
wrappedInput: true,
|
||||||
sourcePath: fileSourcePath,
|
sourcePath: fileSourcePath,
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const mainFlow = <TDataOverride = undefined, TResultOverride = undefined>(
|
const mainFlow = <TDataOverride = undefined, TResultOverride = undefined>(
|
||||||
container?: LoadedModule[] | MedusaContainer
|
container?: LoadedModule[] | MedusaContainer
|
||||||
|
|||||||
+5
-4
@@ -3,11 +3,14 @@ import {
|
|||||||
createWorkflow,
|
createWorkflow,
|
||||||
StepResponse,
|
StepResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { setTimeout } from "timers/promises"
|
||||||
|
|
||||||
const step_1 = createStep(
|
const step_1 = createStep(
|
||||||
"step_1",
|
"step_1",
|
||||||
jest.fn((input) => {
|
jest.fn(async (input) => {
|
||||||
input.test = "test"
|
input.test = "test"
|
||||||
|
await setTimeout(200)
|
||||||
|
|
||||||
return new StepResponse(input, { compensate: 123 })
|
return new StepResponse(input, { compensate: 123 })
|
||||||
}),
|
}),
|
||||||
jest.fn((compensateInput) => {
|
jest.fn((compensateInput) => {
|
||||||
@@ -27,9 +30,7 @@ createWorkflow(
|
|||||||
timeout: 0.1, // 0.1 second
|
timeout: 0.1, // 0.1 second
|
||||||
},
|
},
|
||||||
function (input) {
|
function (input) {
|
||||||
const resp = step_1(input).config({
|
const resp = step_1(input)
|
||||||
async: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,17 +131,20 @@ export class WorkflowOrchestratorService {
|
|||||||
options?: WorkflowOrchestratorRunOptions<T>,
|
options?: WorkflowOrchestratorRunOptions<T>,
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
let {
|
const {
|
||||||
input,
|
input,
|
||||||
context,
|
|
||||||
transactionId,
|
transactionId,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
throwOnError,
|
|
||||||
logOnError,
|
logOnError,
|
||||||
events: eventHandlers,
|
events: eventHandlers,
|
||||||
container,
|
container,
|
||||||
} = options ?? {}
|
} = options ?? {}
|
||||||
|
|
||||||
|
let { throwOnError, context } = options ?? {}
|
||||||
|
throwOnError ??= true
|
||||||
|
context ??= {}
|
||||||
|
context.transactionId ??= transactionId ?? ulid()
|
||||||
|
|
||||||
const workflowId = isString(workflowIdOrWorkflow)
|
const workflowId = isString(workflowIdOrWorkflow)
|
||||||
? workflowIdOrWorkflow
|
? workflowIdOrWorkflow
|
||||||
: workflowIdOrWorkflow.getName()
|
: workflowIdOrWorkflow.getName()
|
||||||
@@ -153,9 +156,6 @@ export class WorkflowOrchestratorService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
context ??= {}
|
|
||||||
context.transactionId ??= transactionId ?? ulid()
|
|
||||||
|
|
||||||
const events: FlowRunOptions["events"] = this.buildWorkflowEvents({
|
const events: FlowRunOptions["events"] = this.buildWorkflowEvents({
|
||||||
customEventHandlers: eventHandlers,
|
customEventHandlers: eventHandlers,
|
||||||
workflowId,
|
workflowId,
|
||||||
@@ -172,7 +172,7 @@ export class WorkflowOrchestratorService {
|
|||||||
|
|
||||||
const ret = await exportedWorkflow.run({
|
const ret = await exportedWorkflow.run({
|
||||||
input,
|
input,
|
||||||
throwOnError,
|
throwOnError: false,
|
||||||
logOnError,
|
logOnError,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
context,
|
context,
|
||||||
@@ -210,6 +210,10 @@ export class WorkflowOrchestratorService {
|
|||||||
await this.triggerParentStep(ret.transaction, result)
|
await this.triggerParentStep(ret.transaction, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwOnError && ret.thrownError) {
|
||||||
|
throw ret.thrownError
|
||||||
|
}
|
||||||
|
|
||||||
return { acknowledgement, ...ret }
|
return { acknowledgement, ...ret }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,13 +266,15 @@ export class WorkflowOrchestratorService {
|
|||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
context,
|
context,
|
||||||
throwOnError,
|
|
||||||
logOnError,
|
logOnError,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
container,
|
container,
|
||||||
events: eventHandlers,
|
events: eventHandlers,
|
||||||
} = options ?? {}
|
} = options ?? {}
|
||||||
|
|
||||||
|
let { throwOnError } = options ?? {}
|
||||||
|
throwOnError ??= true
|
||||||
|
|
||||||
const [idempotencyKey_, { workflowId, transactionId }] =
|
const [idempotencyKey_, { workflowId, transactionId }] =
|
||||||
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
||||||
|
|
||||||
@@ -287,7 +293,7 @@ export class WorkflowOrchestratorService {
|
|||||||
idempotencyKey: idempotencyKey_,
|
idempotencyKey: idempotencyKey_,
|
||||||
context,
|
context,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
throwOnError,
|
throwOnError: false,
|
||||||
logOnError,
|
logOnError,
|
||||||
events,
|
events,
|
||||||
response: stepResponse,
|
response: stepResponse,
|
||||||
@@ -308,6 +314,10 @@ export class WorkflowOrchestratorService {
|
|||||||
await this.triggerParentStep(ret.transaction, result)
|
await this.triggerParentStep(ret.transaction, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwOnError && ret.thrownError) {
|
||||||
|
throw ret.thrownError
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,13 +336,15 @@ export class WorkflowOrchestratorService {
|
|||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
context,
|
context,
|
||||||
throwOnError,
|
|
||||||
logOnError,
|
logOnError,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
container,
|
container,
|
||||||
events: eventHandlers,
|
events: eventHandlers,
|
||||||
} = options ?? {}
|
} = options ?? {}
|
||||||
|
|
||||||
|
let { throwOnError } = options ?? {}
|
||||||
|
throwOnError ??= true
|
||||||
|
|
||||||
const [idempotencyKey_, { workflowId, transactionId }] =
|
const [idempotencyKey_, { workflowId, transactionId }] =
|
||||||
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
||||||
|
|
||||||
@@ -351,7 +363,7 @@ export class WorkflowOrchestratorService {
|
|||||||
idempotencyKey: idempotencyKey_,
|
idempotencyKey: idempotencyKey_,
|
||||||
context,
|
context,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
throwOnError,
|
throwOnError: false,
|
||||||
logOnError,
|
logOnError,
|
||||||
events,
|
events,
|
||||||
response: stepResponse,
|
response: stepResponse,
|
||||||
@@ -372,6 +384,10 @@ export class WorkflowOrchestratorService {
|
|||||||
await this.triggerParentStep(ret.transaction, result)
|
await this.triggerParentStep(ret.transaction, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwOnError && ret.thrownError) {
|
||||||
|
throw ret.thrownError
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
createStep,
|
createStep,
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
StepResponse,
|
StepResponse,
|
||||||
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
|
||||||
const step_1 = createStep(
|
const step_1 = createStep(
|
||||||
@@ -49,6 +50,14 @@ const step_3 = createStep(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const broken_step_2 = createStep(
|
||||||
|
"broken_step_2",
|
||||||
|
jest.fn(() => {}),
|
||||||
|
jest.fn((_, context) => {
|
||||||
|
throw new Error("Broken compensation step")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
createWorkflow(
|
createWorkflow(
|
||||||
{
|
{
|
||||||
name: "workflow_2",
|
name: "workflow_2",
|
||||||
@@ -67,3 +76,19 @@ createWorkflow(
|
|||||||
return step_3(ret2)
|
return step_3(ret2)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
createWorkflow(
|
||||||
|
{
|
||||||
|
name: "workflow_2_revert_fail",
|
||||||
|
retentionTime: 1000,
|
||||||
|
},
|
||||||
|
function (input) {
|
||||||
|
step_1(input)
|
||||||
|
|
||||||
|
broken_step_2().config({
|
||||||
|
async: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return new WorkflowResponse("done")
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -15,13 +15,13 @@ import {
|
|||||||
TransactionHandlerType,
|
TransactionHandlerType,
|
||||||
TransactionStepState,
|
TransactionStepState,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { asValue } from "awilix"
|
|
||||||
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
import { asValue } from "awilix"
|
||||||
import { setTimeout } from "timers/promises"
|
import { setTimeout } from "timers/promises"
|
||||||
|
import { WorkflowsModuleService } from "../../src/services"
|
||||||
import "../__fixtures__"
|
import "../__fixtures__"
|
||||||
import { createScheduled } from "../__fixtures__/workflow_scheduled"
|
import { createScheduled } from "../__fixtures__/workflow_scheduled"
|
||||||
import { TestDatabase } from "../utils"
|
import { TestDatabase } from "../utils"
|
||||||
import { WorkflowsModuleService } from "../../src/services"
|
|
||||||
|
|
||||||
jest.setTimeout(999900000)
|
jest.setTimeout(999900000)
|
||||||
|
|
||||||
@@ -167,6 +167,98 @@ moduleIntegrationTestRunner<IWorkflowEngineService>({
|
|||||||
expect(executionsList).toHaveLength(1)
|
expect(executionsList).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should return a list of failed workflow executions and keep it saved when there is a retentionTime set", async () => {
|
||||||
|
await workflowOrcModule.run("workflow_2", {
|
||||||
|
input: {
|
||||||
|
value: "123",
|
||||||
|
},
|
||||||
|
transactionId: "transaction_1",
|
||||||
|
})
|
||||||
|
|
||||||
|
let executionsList = await query({
|
||||||
|
workflow_executions: {
|
||||||
|
fields: ["id"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(executionsList).toHaveLength(1)
|
||||||
|
|
||||||
|
await workflowOrcModule.setStepFailure({
|
||||||
|
idempotencyKey: {
|
||||||
|
action: TransactionHandlerType.INVOKE,
|
||||||
|
stepId: "new_step_name",
|
||||||
|
workflowId: "workflow_2",
|
||||||
|
transactionId: "transaction_1",
|
||||||
|
},
|
||||||
|
stepResponse: { uhuuuu: "yeaah!" },
|
||||||
|
})
|
||||||
|
|
||||||
|
executionsList = await query({
|
||||||
|
workflow_executions: {
|
||||||
|
fields: ["id", "state"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(executionsList).toHaveLength(1)
|
||||||
|
expect(executionsList[0].state).toEqual("reverted")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw if setStepFailure fails", async () => {
|
||||||
|
const { acknowledgement } = await workflowOrcModule.run(
|
||||||
|
"workflow_2_revert_fail",
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
value: "123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
void workflowOrcModule.subscribe({
|
||||||
|
workflowId: "workflow_2_revert_fail",
|
||||||
|
transactionId: acknowledgement.transactionId,
|
||||||
|
subscriber: (event) => {
|
||||||
|
if (event.eventType === "onFinish") {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let executionsList = await query({
|
||||||
|
workflow_executions: {
|
||||||
|
fields: ["id"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(executionsList).toHaveLength(1)
|
||||||
|
|
||||||
|
const setStepError = await workflowOrcModule
|
||||||
|
.setStepFailure({
|
||||||
|
idempotencyKey: {
|
||||||
|
action: TransactionHandlerType.INVOKE,
|
||||||
|
stepId: "broken_step_2",
|
||||||
|
workflowId: "workflow_2_revert_fail",
|
||||||
|
transactionId: acknowledgement.transactionId,
|
||||||
|
},
|
||||||
|
stepResponse: { uhuuuu: "yeaah!" },
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(setStepError).toEqual({ uhuuuu: "yeaah!" })
|
||||||
|
|
||||||
|
executionsList = await query({
|
||||||
|
workflow_executions: {
|
||||||
|
fields: ["id", "state", "context"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(executionsList).toHaveLength(1)
|
||||||
|
expect(executionsList[0].state).toEqual("failed")
|
||||||
|
expect(done).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it("should revert the entire transaction when a step timeout expires", async () => {
|
it("should revert the entire transaction when a step timeout expires", async () => {
|
||||||
const { transaction, result, errors } = await workflowOrcModule.run(
|
const { transaction, result, errors } = await workflowOrcModule.run(
|
||||||
"workflow_step_timeout",
|
"workflow_step_timeout",
|
||||||
|
|||||||
@@ -175,17 +175,21 @@ export class WorkflowOrchestratorService {
|
|||||||
options?: WorkflowOrchestratorRunOptions<T>,
|
options?: WorkflowOrchestratorRunOptions<T>,
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
let {
|
const {
|
||||||
input,
|
input,
|
||||||
context,
|
|
||||||
transactionId,
|
transactionId,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
throwOnError,
|
|
||||||
logOnError,
|
logOnError,
|
||||||
events: eventHandlers,
|
events: eventHandlers,
|
||||||
container,
|
container,
|
||||||
} = options ?? {}
|
} = options ?? {}
|
||||||
|
|
||||||
|
let { throwOnError, context } = options ?? {}
|
||||||
|
|
||||||
|
throwOnError ??= true
|
||||||
|
context ??= {}
|
||||||
|
context.transactionId ??= transactionId ?? ulid()
|
||||||
|
|
||||||
const workflowId = isString(workflowIdOrWorkflow)
|
const workflowId = isString(workflowIdOrWorkflow)
|
||||||
? workflowIdOrWorkflow
|
? workflowIdOrWorkflow
|
||||||
: workflowIdOrWorkflow.getName()
|
: workflowIdOrWorkflow.getName()
|
||||||
@@ -194,9 +198,6 @@ export class WorkflowOrchestratorService {
|
|||||||
throw new Error("Workflow ID is required")
|
throw new Error("Workflow ID is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
context ??= {}
|
|
||||||
context.transactionId ??= transactionId ?? ulid()
|
|
||||||
|
|
||||||
const events: FlowRunOptions["events"] = this.buildWorkflowEvents({
|
const events: FlowRunOptions["events"] = this.buildWorkflowEvents({
|
||||||
customEventHandlers: eventHandlers,
|
customEventHandlers: eventHandlers,
|
||||||
workflowId,
|
workflowId,
|
||||||
@@ -210,7 +211,7 @@ export class WorkflowOrchestratorService {
|
|||||||
|
|
||||||
const ret = await exportedWorkflow.run({
|
const ret = await exportedWorkflow.run({
|
||||||
input,
|
input,
|
||||||
throwOnError,
|
throwOnError: false,
|
||||||
logOnError,
|
logOnError,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
context,
|
context,
|
||||||
@@ -248,6 +249,10 @@ export class WorkflowOrchestratorService {
|
|||||||
await this.triggerParentStep(ret.transaction, result)
|
await this.triggerParentStep(ret.transaction, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwOnError && ret.thrownError) {
|
||||||
|
throw ret.thrownError
|
||||||
|
}
|
||||||
|
|
||||||
return { acknowledgement, ...ret }
|
return { acknowledgement, ...ret }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,13 +304,15 @@ export class WorkflowOrchestratorService {
|
|||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
context,
|
context,
|
||||||
throwOnError,
|
|
||||||
logOnError,
|
logOnError,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
container,
|
container,
|
||||||
events: eventHandlers,
|
events: eventHandlers,
|
||||||
} = options ?? {}
|
} = options ?? {}
|
||||||
|
|
||||||
|
let { throwOnError } = options ?? {}
|
||||||
|
throwOnError ??= true
|
||||||
|
|
||||||
const [idempotencyKey_, { workflowId, transactionId }] =
|
const [idempotencyKey_, { workflowId, transactionId }] =
|
||||||
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
||||||
|
|
||||||
@@ -324,7 +331,7 @@ export class WorkflowOrchestratorService {
|
|||||||
idempotencyKey: idempotencyKey_,
|
idempotencyKey: idempotencyKey_,
|
||||||
context,
|
context,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
throwOnError,
|
throwOnError: false,
|
||||||
logOnError,
|
logOnError,
|
||||||
events,
|
events,
|
||||||
response: stepResponse,
|
response: stepResponse,
|
||||||
@@ -345,6 +352,10 @@ export class WorkflowOrchestratorService {
|
|||||||
await this.triggerParentStep(ret.transaction, result)
|
await this.triggerParentStep(ret.transaction, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwOnError && ret.thrownError) {
|
||||||
|
throw ret.thrownError
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,13 +374,15 @@ export class WorkflowOrchestratorService {
|
|||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
context,
|
context,
|
||||||
throwOnError,
|
|
||||||
logOnError,
|
logOnError,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
container,
|
container,
|
||||||
events: eventHandlers,
|
events: eventHandlers,
|
||||||
} = options ?? {}
|
} = options ?? {}
|
||||||
|
|
||||||
|
let { throwOnError } = options ?? {}
|
||||||
|
throwOnError ??= true
|
||||||
|
|
||||||
const [idempotencyKey_, { workflowId, transactionId }] =
|
const [idempotencyKey_, { workflowId, transactionId }] =
|
||||||
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
this.buildIdempotencyKeyAndParts(idempotencyKey)
|
||||||
|
|
||||||
@@ -388,7 +401,7 @@ export class WorkflowOrchestratorService {
|
|||||||
idempotencyKey: idempotencyKey_,
|
idempotencyKey: idempotencyKey_,
|
||||||
context,
|
context,
|
||||||
resultFrom,
|
resultFrom,
|
||||||
throwOnError,
|
throwOnError: false,
|
||||||
logOnError,
|
logOnError,
|
||||||
events,
|
events,
|
||||||
response: stepResponse,
|
response: stepResponse,
|
||||||
@@ -409,6 +422,10 @@ export class WorkflowOrchestratorService {
|
|||||||
await this.triggerParentStep(ret.transaction, result)
|
await this.triggerParentStep(ret.transaction, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwOnError && ret.thrownError) {
|
||||||
|
throw ret.thrownError
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user