feat(workflows-sdk): Configurable retries upon step creation (#5728)

**What**
- Allow to create step that can be configured to have a max retry
- Step end retry mechanism on permanent failure

Also added an API to override a step configuration from within the createWorkflow
```ts
const step = createStep({ name: "step", maxRetries: 3 }, async (_, context) => {
  return new StepResponse({ output: "output" })
})

const workflow = createWorkflow("workflow", function () {
  const res = step().config({ maxRetries: 5 }) // This will override the original maxRetries of 3
})
```

**NOTE**
We can maybe find another name than config on the step workflow data to override the step config.
This commit is contained in:
Adrien de Peretti
2023-12-19 10:38:27 +01:00
committed by GitHub
parent 1a2f513d53
commit 9cc787cac4
21 changed files with 255 additions and 92 deletions

View File

@@ -1,12 +1,4 @@
import {
resolveValue,
StepResponse,
SymbolMedusaWorkflowComposerContext,
SymbolWorkflowStep,
SymbolWorkflowStepBind,
SymbolWorkflowStepResponse,
SymbolWorkflowWorkflowData,
} from "./helpers"
import { resolveValue, StepResponse } from "./helpers"
import {
CreateWorkflowComposerContext,
StepExecutionContext,
@@ -15,6 +7,8 @@ import {
WorkflowData,
} from "./type"
import { proxify } from "./helpers/proxy"
import { TransactionStepsDefinition } from "@medusajs/orchestration"
import { isString, OrchestrationUtils } from "@medusajs/utils"
/**
* The type of invocation function passed to a step.
@@ -75,6 +69,7 @@ interface ApplyStepOptions<
TInvokeResultCompensateInput
> {
stepName: string
stepConfig?: TransactionStepsDefinition
input: TStepInputs
invokeFn: InvokeFn<
TInvokeInput,
@@ -91,6 +86,7 @@ interface ApplyStepOptions<
* This is where the inputs and context are passed to the underlying invoke and compensate function.
*
* @param stepName
* @param stepConfig
* @param input
* @param invokeFn
* @param compensateFn
@@ -104,6 +100,7 @@ function applyStep<
TInvokeResultCompensateInput
>({
stepName,
stepConfig = {},
input,
invokeFn,
compensateFn,
@@ -135,12 +132,12 @@ function applyStep<
)
const stepResponseJSON =
stepResponse?.__type === SymbolWorkflowStepResponse
stepResponse?.__type === OrchestrationUtils.SymbolWorkflowStepResponse
? stepResponse.toJSON()
: stepResponse
return {
__type: SymbolWorkflowWorkflowData,
__type: OrchestrationUtils.SymbolWorkflowWorkflowData,
output: stepResponseJSON,
}
},
@@ -154,7 +151,8 @@ function applyStep<
const stepOutput = transactionContext.invoke[stepName]?.output
const invokeResult =
stepOutput?.__type === SymbolWorkflowStepResponse
stepOutput?.__type ===
OrchestrationUtils.SymbolWorkflowStepResponse
? stepOutput.compensateInput &&
JSON.parse(JSON.stringify(stepOutput.compensateInput))
: stepOutput && JSON.parse(JSON.stringify(stepOutput))
@@ -168,14 +166,21 @@ function applyStep<
: undefined,
}
this.flow.addAction(stepName, {
noCompensation: !compensateFn,
})
stepConfig!.noCompensation = !compensateFn
this.flow.addAction(stepName, stepConfig)
this.handlers.set(stepName, handler)
const ret = {
__type: SymbolWorkflowStep,
__type: OrchestrationUtils.SymbolWorkflowStep,
__step__: stepName,
config: (config: Pick<TransactionStepsDefinition, "maxRetries">) => {
this.flow.replaceAction(stepName, stepName, {
...stepConfig,
...config,
})
return proxify(ret)
},
}
return proxify(ret)
@@ -236,9 +241,11 @@ export function createStep<
TInvokeResultCompensateInput
>(
/**
* The name of the step.
* The name of the step or its configuration (currently support maxRetries).
*/
name: string,
nameOrConfig:
| string
| ({ name: string } & Pick<TransactionStepsDefinition, "maxRetries">),
/**
* An invocation function that will be executed when the workflow is executed. The function must return an instance of {@link StepResponse}. The constructor of {@link StepResponse}
* accepts the output of the step as a first argument, and optionally as a second argument the data to be passed to the compensation function as a parameter.
@@ -256,12 +263,14 @@ export function createStep<
*/
compensateFn?: CompensateFn<TInvokeResultCompensateInput>
): StepFunction<TInvokeInput, TInvokeResultOutput> {
const stepName = name ?? invokeFn.name
const stepName =
(isString(nameOrConfig) ? nameOrConfig : nameOrConfig.name) ?? invokeFn.name
const config = isString(nameOrConfig) ? {} : nameOrConfig
const returnFn = function (input: {
[K in keyof TInvokeInput]: WorkflowData<TInvokeInput[K]>
}): WorkflowData<TInvokeResultOutput> {
if (!global[SymbolMedusaWorkflowComposerContext]) {
if (!global[OrchestrationUtils.SymbolMedusaWorkflowComposerContext]) {
throw new Error(
"createStep must be used inside a createWorkflow definition"
)
@@ -269,7 +278,7 @@ export function createStep<
const stepBinder = (
global[
SymbolMedusaWorkflowComposerContext
OrchestrationUtils.SymbolMedusaWorkflowComposerContext
] as CreateWorkflowComposerContext
).stepBinder
@@ -281,6 +290,7 @@ export function createStep<
TInvokeResultCompensateInput
>({
stepName,
stepConfig: config,
input,
invokeFn,
compensateFn,
@@ -288,7 +298,7 @@ export function createStep<
)
}
returnFn.__type = SymbolWorkflowStepBind
returnFn.__type = OrchestrationUtils.SymbolWorkflowStepBind
returnFn.__step__ = stepName
return returnFn as unknown as StepFunction<TInvokeInput, TInvokeResultOutput>