docs: added advanced workflows documentation (#7252)
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Access Workflow Errors`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how to access errors that occur during a workflow’s execution.
|
||||
|
||||
## How to Access Workflow Errors
|
||||
|
||||
By default, when an error occurs in a workflow, it throws that error, and the execution stops.
|
||||
|
||||
You can configure the workflow to return the errors instead so that you can access and handle them differently.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["11", "errors", "`errors` is an array of errors that occur during the workflow's execution."],
|
||||
["14", "throwOnError", "Specify that errors occuring during the workflow's execution should be returned, not thrown."],
|
||||
]
|
||||
|
||||
```ts title="src/api/store/workflows/route.ts" highlights={highlights}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../../../workflows/hello-world"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result, errors } = await myWorkflow(req.scope)
|
||||
.run({
|
||||
// ...
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (errors.length) {
|
||||
return res.send({
|
||||
errors: errors.map((error) => error.error),
|
||||
})
|
||||
}
|
||||
|
||||
res.send(result)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of the `run`'s output. The value of `errors` is an array of error objects.
|
||||
|
||||
Then, you can check the items in the `errors` array and handle them accordingly. Each error object has an `error` property, which holds the name or the text of the thrown error.
|
||||
@@ -8,13 +8,19 @@ In this chapter, you'll learn how to add a compensation function to a step.
|
||||
|
||||
## Compensation Function
|
||||
|
||||
Errors can occur in a workflow. To avoid data inconsistency, you can pass a compensation function as a third parameter to the `createStep` function.
|
||||
Errors can occur in a workflow. To avoid data inconsistency, define a function to run when an error occurs in a step. This function is called the compensation function.
|
||||
|
||||
The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve performed in a step.
|
||||
Each step can have a compensation function. The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve performed in a step.
|
||||
|
||||
For example, change step one to add a compensation function and step two to throw an error:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts" highlights={[["10"], ["11"], ["12"]]}
|
||||
```ts title="src/workflows/hello-world.ts" highlights={[["16"], ["17"], ["18"]]}
|
||||
// other imports...
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async () => {
|
||||
@@ -31,7 +37,7 @@ const step1 = createStep(
|
||||
|
||||
const step2 = createStep(
|
||||
"step-2",
|
||||
async ({ name }: WorkflowInput) => {
|
||||
async () => {
|
||||
throw new Error("Throwing an error...")
|
||||
}
|
||||
)
|
||||
@@ -54,12 +60,11 @@ type WorkflowOutput = {
|
||||
}
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
WorkflowInput,
|
||||
{},
|
||||
WorkflowOutput
|
||||
>("hello-world", function (input) {
|
||||
const str1 = step1()
|
||||
// to pass input
|
||||
const str2 = step2(input)
|
||||
step2()
|
||||
|
||||
return {
|
||||
message: str1,
|
||||
@@ -83,11 +88,7 @@ export async function GET(
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await myWorkflow(req.scope)
|
||||
.run({
|
||||
input: {
|
||||
name: req.query.name as string,
|
||||
},
|
||||
})
|
||||
.run()
|
||||
|
||||
res.send(result)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { TypeList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Long-Running Workflows`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn what a long-running workflow is and how to configure it.
|
||||
|
||||
## What is a Long-Running Workflow?
|
||||
|
||||
By default, when you execute a workflow, you wait until the workflow finishes execution. Once you receive the workflow’s output, the rest of the code is executed.
|
||||
|
||||
A long-running workflow is a workflow that continues its execution in the background. You don’t receive its output immediately. Instead, you subscribe to the workflow execution to listen to status changes and receive its result once the execution is finished.
|
||||
|
||||
---
|
||||
|
||||
## Configure Long-Running Workflows
|
||||
|
||||
A workflow is considered long-running if one or more of its steps have their `async` configuration set to `true`.
|
||||
|
||||
For example, consider the following workflow and steps:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts" highlights={[["13"]]}
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async () => {
|
||||
// ...
|
||||
})
|
||||
|
||||
const step2 = createStep(
|
||||
{
|
||||
name: "step-2",
|
||||
async: true,
|
||||
},
|
||||
async () => {
|
||||
// ...
|
||||
}
|
||||
)
|
||||
|
||||
const step3 = createStep("step-3", async () => {
|
||||
// ...
|
||||
})
|
||||
|
||||
type WorkflowOutput = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
{},
|
||||
WorkflowOutput
|
||||
>({
|
||||
name: "hello-world",
|
||||
}, function () {
|
||||
step1()
|
||||
step2()
|
||||
step3()
|
||||
})
|
||||
|
||||
export default myWorkflow
|
||||
|
||||
```
|
||||
|
||||
The second step has in its configuration object `async` set to true. This indicates that this step is an asynchronous step.
|
||||
|
||||
So, when you execute the `hello-world` workflow, it continues its execution in the background once it reaches the second step.
|
||||
|
||||
---
|
||||
|
||||
## Access Long-Running Workflow Status and Result
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [A workflow engine module installed](!resources!/architectural-modules/workflow-engine/in-memory).
|
||||
|
||||
</Note>
|
||||
|
||||
To access the status and result of a long-running workflow, use the workflow engine registered in the Medusa Container. The workflow engine provides methods to access and subscribe to workflow executions.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["18", "", "Resolve the workflow engine from the Medusa container."],
|
||||
["24", "subscribe", "Subscribe to status changes of the workflow execution."],
|
||||
]
|
||||
|
||||
```ts title="src/api/store/workflows/route.ts" highlights={highlights}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import myWorkflow from "../../../workflows/hello-world"
|
||||
import {
|
||||
IWorkflowEngineService,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { transaction, result } = await myWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
const workflowEngine = req.scope.resolve<
|
||||
IWorkflowEngineService
|
||||
>(
|
||||
ModuleRegistrationName.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
await workflowEngine.subscribe({
|
||||
workflowId: "hello-world",
|
||||
transactionId: transaction.transactionId,
|
||||
subscriber: (data) => {
|
||||
if (data.eventType === "onFinish") {
|
||||
console.log("Finished execution", data.result)
|
||||
} else if (data.eventType === "onStepFailure") {
|
||||
console.log("Workflow failed", data.step)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, you execute the long-running workflow `hello-world`. You then resolve the workflow engine from the Medusa container and use its `subscribe` method to listen to changes in the workflow execution’s status.
|
||||
|
||||
The `subscribe` method accepts an object having three properties:
|
||||
|
||||
<TypeList
|
||||
types={[
|
||||
{
|
||||
name: "workflowId",
|
||||
type: "`string`",
|
||||
description: "The name of the workflow."
|
||||
},
|
||||
{
|
||||
name: "transactionId",
|
||||
type: "`string`",
|
||||
description: "The ID of the workflow exection's transaction. The transaction's details are returned in the response of the workflow execution."
|
||||
},
|
||||
{
|
||||
name: "subscriber",
|
||||
type: "`string`",
|
||||
description: "The function executed when the workflow execution's status changes. The function receives a data object. It has an `eventType` property, which you use to check the status of the workflow execution."
|
||||
}
|
||||
]}
|
||||
sectionTitle="Access Long-Running Workflow Status and Result"
|
||||
/>
|
||||
|
||||
Once the workflow execution finishes, the subscriber function is executed with the `eventType` of the received parameter set to `onFinish`. The workflow’s output is set in the `result` property of the parameter.
|
||||
@@ -1,5 +1,5 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Running Workflow Steps in Parallel`,
|
||||
title: `${pageNumber} Run Workflow Steps in Parallel`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Retry Failed Steps`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to configure steps to allow retrial on failure.
|
||||
|
||||
## Configure a Step’s Retrial
|
||||
|
||||
By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
|
||||
|
||||
You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts" highlights={[["10"]]}
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
{
|
||||
name: "step-1",
|
||||
maxRetries: 2,
|
||||
},
|
||||
async () => {
|
||||
console.log("Executing step 1")
|
||||
|
||||
throw new Error("Oops! Something happened.")
|
||||
}
|
||||
)
|
||||
|
||||
type WorkflowOutput = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
{},
|
||||
WorkflowOutput
|
||||
>("hello-world", function () {
|
||||
const str1 = step1()
|
||||
|
||||
return {
|
||||
message: str1,
|
||||
}
|
||||
})
|
||||
|
||||
export default myWorkflow
|
||||
```
|
||||
|
||||
The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
|
||||
|
||||
When you execute the above workflow, you’ll see the following result in the terminal:
|
||||
|
||||
```bash
|
||||
Executing step 1
|
||||
Executing step 1
|
||||
Executing step 1
|
||||
error: Oops! Something happened.
|
||||
Error: Oops! Something happened.
|
||||
```
|
||||
|
||||
The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
|
||||
|
||||
---
|
||||
|
||||
## Step Retry Intervals
|
||||
|
||||
By default, a step is retried immediately after it fails.
|
||||
|
||||
To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
|
||||
const step1 = createStep(
|
||||
{
|
||||
name: "step-1",
|
||||
maxRetries: 2,
|
||||
retryInterval: 2, // 2 seconds
|
||||
},
|
||||
async () => {
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,86 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Workflow Timeout`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to set a timeout for workflows and steps.
|
||||
|
||||
## Configure Workflow Timeout
|
||||
|
||||
By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs.
|
||||
|
||||
You can configure a workflow’s timeout to indicate how long the workflow can run. Once the specified time is passed and the workflow is still running, the workflow is considered failed and an error is thrown.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/workflows/hello-world.ts" highlights={[["22"]]}
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async () => {
|
||||
// ...
|
||||
}
|
||||
)
|
||||
|
||||
type WorkflowOutput = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const myWorkflow = createWorkflow<
|
||||
{},
|
||||
WorkflowOutput
|
||||
>({
|
||||
name: "hello-world",
|
||||
timeout: 2, // 2 seconds
|
||||
}, function () {
|
||||
const str1 = step1()
|
||||
|
||||
return {
|
||||
message: str1,
|
||||
}
|
||||
})
|
||||
|
||||
export default myWorkflow
|
||||
|
||||
```
|
||||
|
||||
The `createWorkflow` function can accept a configuration object instead of the workflow’s name. In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
A workflow’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](../access-workflow-errors/page.mdx). The error’s name is `TransactionTimeoutError`.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Configure Step Timeout
|
||||
|
||||
Alternatively, you can configure timeout for a step rather than the entire workflow.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx
|
||||
const step1 = createStep(
|
||||
{
|
||||
name: "step-1",
|
||||
timeout: 2, // 2 seconds
|
||||
},
|
||||
async () => {
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
A step’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](../access-workflow-errors/page.mdx). The error’s name is `TransactionStepTimeoutError`.
|
||||
|
||||
</Note>
|
||||
Reference in New Issue
Block a user