4.8.1. Workflow Constraints
This chapter lists constraints of defining a workflow or its steps.
Medusa creates an internal representation of the workflow definition you pass to createWorkflow to track and store its steps. At that point, variables in the workflow don't have any values. They only do when you execute the workflow.
This creates restrictions related to variable manipulations, using if-conditions, and other constraints. This chapter lists these constraints and provides their alternatives.
Workflow Constraints#
No Async Functions#
The function passed to createWorkflow can’t be an async function:
// Don't
const myWorkflow = createWorkflow(
"hello-world",
async function (input: WorkflowInput) {
// ...
})
// Do
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
// ...
})
No Direct Variable Manipulation#
You can’t directly manipulate variables within the workflow's constructor function.
Instead, use transform from the Workflows SDK:
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const str1 = step1(input)
const str2 = step2(input)
return new WorkflowResponse({
message: `${str1}${str2}`,
})
})
// Do
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const str1 = step1(input)
const str2 = step2(input)
const result = transform(
{
str1,
str2,
},
(input) => ({
message: `${input.str1}${input.str2}`,
})
)
return new WorkflowResponse(result)
})
Create Dates in transform
When you use new Date() in a workflow's constructor function, the date is evaluated when Medusa creates the internal representation of the workflow, not during execution.
Instead, create the date using transform.
For example:
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const today = new Date()
return new WorkflowResponse({
today,
})
})
// Do
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const today = transform({}, () => new Date())
return new WorkflowResponse({
today,
})
})
No If Conditions#
You can't use if-conditions in a workflow.
Instead, use when-then from the Workflows SDK:
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
if (input.is_active) {
// perform an action
}
})
// Do (explained in the next chapter)
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
when(input, (input) => {
return input.is_active
})
.then(() => {
// perform an action
})
})
You can also pair multiple when-then blocks to implement an if-else condition as explained in this chapter.
No Conditional Operators#
You can't use conditional operators in a workflow, such as ?? or ||.
Instead, use transform to store the desired value in a variable.
Logical Or (||) Alternative
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const message = input.message || "Hello"
})
// Do
// other imports...
import { transform } from "@medusajs/framework/workflows-sdk"
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const message = transform(
{
input,
},
(data) => data.input.message || "hello"
)
})
Nullish Coalescing (??) Alternative
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const message = input.message ?? "Hello"
})
// Do
// other imports...
import { transform } from "@medusajs/framework/workflows-sdk"
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const message = transform(
{
input,
},
(data) => data.input.message ?? "hello"
)
})
Double Not (!!) Alternative
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
step1({
isActive: !!input.is_active,
})
})
// Do
// other imports...
import { transform } from "@medusajs/framework/workflows-sdk"
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const isActive = transform(
{
input,
},
(data) => !!data.input.is_active
)
step1({
isActive,
})
})
Ternary Alternative
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
step1({
message: input.is_active ? "active" : "inactive",
})
})
// Do
// other imports...
import { transform } from "@medusajs/framework/workflows-sdk"
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const message = transform(
{
input,
},
(data) => {
return data.input.is_active ? "active" : "inactive"
}
)
step1({
message,
})
})
Optional Chaining (?.) Alternative
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
step1({
name: input.customer?.name,
})
})
// Do
// other imports...
import { transform } from "@medusajs/framework/workflows-sdk"
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const name = transform(
{
input,
},
(data) => data.input.customer?.name
)
step1({
name,
})
})
No Try-Catch#
In a workflow, don't use try-catch blocks to handle errors.
Instead, refer to the Error Handling chapter for alternative ways to handle errors.
No Object Property Spreading#
You can’t spread object properties using the spread operator (...) in a workflow's constructor function.
Instead, use transform to create new objects with the desired properties.
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const { prop1, ...rest } = input
// use rest
})
// Do
import { transform } from "@medusajs/framework/workflows-sdk"
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const rest = transform(
{
input,
},
(data) => {
const { prop1, ...rest } = data.input
return rest
}
)
// use rest
})
No Loops#
You can't use loops, such as for or while, in a workflow's constructor function. There are two alternatives based on your use case:
- To repeat all the actions of a workflow multiple times, wrap the workflow execution in a loop in the code that calls the workflow. This is common if the workflow receives an array as input and you want to execute the workflow for each item in the array.
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
for (const item of input.items) {
step1(item)
}
})
// Do
// API route that calls the workflow
import {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import myWorkflow from "../../workflows/my-workflow"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
) {
const { items } = req.body
for (const item of items) {
await myWorkflow(req.scope)
.run({ item })
}
res.status(200).send({ success: true })
}
// Workflow definition
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
step1(input.item)
})
- To iterate over an array within the workflow to prepare step inputs, use
transformto map over the array and create the desired inputs.
// Don't
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const stepInputs = []
for (const item of input.items) {
stepInputs.push({ id: item.id })
}
step1(stepInputs)
})
// Do
import { transform } from "@medusajs/framework/workflows-sdk"
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const stepInputs = transform(
{
input,
},
(data) => {
return data.input.items.map((item) => ({ id: item.id }))
}
)
step1(stepInputs)
})
Returned Value Constraints#
Data returned from workflows and steps are serialized, allowing Medusa to store them in the database. So, you must only return serializable values, such as primitive values or an object, from workflows and steps.
Values of other types, such as Maps, aren't allowed.
// Don't
import {
createStep,
StepResponse,
} from "@medusajs/framework/workflows-sdk"
const step1 = createStep(
"step-1",
(input, { container }) => {
const myMap = new Map()
// ...
return new StepResponse({
myMap,
})
}
)
// Do
import {
createStep,
StepResponse,
} from "@medusajs/framework/workflows-sdk"
const step1 = createStep(
"step-1",
(input, { container }) => {
const myObj: Record<string, unknown> = {}
// ...
return new StepResponse({
myObj,
})
}
)
Buffer Example#
In some cases, you may need to return a buffer. For example, when your workflow generates a file and you want to return it as a buffer.
In those cases, you can return an object containing the buffer as a property. Then, in customizations that execute the workflow, you can recreate the buffer from the serialized data.
For example, consider the following workflow that returns a buffer:
import {
createWorkflow,
createStep,
WorkflowResponse,
StepResponse,
} from "@medusajs/framework/workflows-sdk"
const step1 = createStep(
"step-1",
(_, { container }) => {
const buffer = Buffer.from("Hello, World!")
return new StepResponse({
buffer,
})
}
)
const myWorkflow = createWorkflow(
"hello-world",
function () {
const step1Response = step1()
return new WorkflowResponse({
buffer: step1Response.buffer,
})
}
)
Then, in an API route that executes this workflow, you can recreate the buffer from the serialized data using Buffer.from:
import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import myWorkflow from "../../workflows/hello-world"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
const { result } = await myWorkflow(req.scope)
.run()
const buffer = Buffer.from(result.buffer)
res.setHeader("Content-Type", "application/octet-stream")
res.setHeader("Content-Disposition", "attachment; filename=hello.txt")
res.send(buffer)
}