Subchapter 2.12
checkpoints/checkpoint-workflow.mdMarkdown6 KBView on GitHub
This checkpoint verifies that you’ve successfully created the createBrandWorkflow with a step and compensation function.
Before proceeding, test your understanding:
Why can’t we use async or await in the workflow function?
Workflows are declarative blueprints that define the sequence of steps. They don’t execute steps directly - the workflow engine does. Using async/await would mean executing steps during definition, which breaks the orchestration model. Steps are called synchronously in the workflow function, and the engine handles the async execution.
What is a compensation function and why do we need it?
A compensation function is the “undo” logic for a step. If a later step in the workflow fails, Medusa automatically calls compensation functions for all completed steps in reverse order. This provides automatic rollback - for example, if brand creation succeeds but a later S3 upload fails, the compensation function deletes the brand to maintain data consistency.
What does StepResponse do?
StepResponse returns two things: (1) the data to pass to the next step, and (2) the data to pass to the compensation function if rollback is needed. This lets you return different data for success vs. rollback scenarios.
Why do we use transform() instead of directly returning workflow input?
transform() is a utility that extracts and shapes data from step results. It ensures type safety and makes it clear what data is being returned. While you could return step results directly, transform() provides better code readability and type inference.
Let me verify your implementation. Please share the following:
Show me your src/workflows/create-brand/steps/create-brand.ts file.
Key things to check:
createStep()container.resolve("brand") to get servicebrandService.createBrands() (note the plural - it’s a batch method)new StepResponse(brand, brand.id) (brand for next step, id for compensation)brandId parameterdeleteBrands([brandId])if (!brandId) returnShow me your src/workflows/create-brand/index.ts file.
Key things to check:
createWorkflow() with unique namefunction keyword)createBrandStep(input) without awaittransform() to extract brand from step resultnew WorkflowResponse(brand)Run this command and share any errors:
npm run buildExpected output: Build should succeed. TypeScript should not complain about the workflow.
Symptom: TypeScript error or runtime warning about async
Cause: Workflow function declared as async function
Fix:
Remove async keyword:
// ❌ WRONG
createWorkflow("create-brand", async function (input) {
// ...
})
// ✅ CORRECT
createWorkflow("create-brand", function (input) {
// ...
})Symptom: Error about await usage
Cause: Using await when calling steps
Fix:
Remove await - steps are called synchronously:
// ❌ WRONG
const result = await createBrandStep(input)
// ✅ CORRECT
const result = createBrandStep(input)Symptom: Runtime error when executing workflow
Cause: Service name doesn’t match module registration
Fix: Use exact service name with “ModuleService” suffix:
const brandService = container.resolve("brand")Verify each of these steps:
At this point, you should understand:
Example of why this matters:
Imagine this workflow:
If step 3 fails, the compensation functions for steps 2 and 1 run automatically:
This ensures your system never ends up in an inconsistent state - all or nothing.
Once this checkpoint passes:
The workflow provides the business logic orchestration. Now we’ll expose it via an HTTP API route that validates input and executes the workflow.
Ready to continue? Let me know when all checks pass, and we’ll move on to creating the API route.