Skip to content

Commit c1ede88

Browse files
authored
docs: document locks for nested workflows (medusajs#14130)
* docs: document locks for nested workflows * smal text change * fix build error
1 parent 76660fb commit c1ede88

File tree

53 files changed

+714
-210
lines changed
  • www
    • apps
      • book
      • resources
        • app/infrastructure-modules/locking
        • generated
        • references/core_flows
          • Cart/Workflows_Cart/functions
            • core_flows.Cart.Workflows_Cart.addShippingMethodToCartWorkflow
            • core_flows.Cart.Workflows_Cart.addToCartWorkflow
            • core_flows.Cart.Workflows_Cart.completeCartWorkflow
            • core_flows.Cart.Workflows_Cart.createCartWorkflow
            • core_flows.Cart.Workflows_Cart.createPaymentCollectionForCartWorkflow
            • core_flows.Cart.Workflows_Cart.refreshCartItemsWorkflow
            • core_flows.Cart.Workflows_Cart.refreshCartShippingMethodsWorkflow
            • core_flows.Cart.Workflows_Cart.refreshPaymentCollectionForCartWorkflow
            • core_flows.Cart.Workflows_Cart.transferCartCustomerWorkflow
            • core_flows.Cart.Workflows_Cart.updateCartPromotionsWorkflow
            • core_flows.Cart.Workflows_Cart.updateCartWorkflow
            • core_flows.Cart.Workflows_Cart.updateLineItemInCartWorkflow
            • core_flows.Cart.Workflows_Cart.updateTaxLinesWorkflow
          • Draft_Order/Workflows_Draft_Order/functions
            • core_flows.Draft_Order.Workflows_Draft_Order.addDraftOrderItemsWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.addDraftOrderPromotionWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.addDraftOrderShippingMethodsWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.beginDraftOrderEditWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.cancelDraftOrderEditWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.confirmDraftOrderEditWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.convertDraftOrderWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.removeDraftOrderActionItemWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.removeDraftOrderActionShippingMethodWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.removeDraftOrderPromotionsWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.removeDraftOrderShippingMethodWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.requestDraftOrderEditWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.updateDraftOrderActionItemWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.updateDraftOrderActionShippingMethodWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.updateDraftOrderItemWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.updateDraftOrderShippingMethodWorkflow
            • core_flows.Draft_Order.Workflows_Draft_Order.updateDraftOrderWorkflow
          • Line_Item/Workflows_Line_Item/functions/core_flows.Line_Item.Workflows_Line_Item.deleteLineItemsWorkflow
          • Order/Workflows_Order/functions
            • core_flows.Order.Workflows_Order.cancelOrderFulfillmentWorkflow
            • core_flows.Order.Workflows_Order.confirmClaimRequestWorkflow
            • core_flows.Order.Workflows_Order.confirmExchangeRequestWorkflow
            • core_flows.Order.Workflows_Order.confirmOrderEditRequestWorkflow
            • core_flows.Order.Workflows_Order.confirmReturnReceiveWorkflow
            • core_flows.Order.Workflows_Order.createOrderFulfillmentWorkflow
          • Reservation/Workflows_Reservation/functions/core_flows.Reservation.Workflows_Reservation.createReservationsWorkflow
    • utils/packages/typedoc-plugin-markdown-medusa/src

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+714
-210
lines changed

www/apps/book/app/learn/fundamentals/workflows/locks/page.mdx

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ The [Locking Module](!resources!/infrastructure-modules/locking) handles the loc
2020

2121
Medusa provides two steps that you can use to create locks in your workflows:
2222

23-
- `acquireLockStep`: Attempt to acquire a lock. If the lock is already held by another process, this step will wait until the lock is released. It will fail if a timeout occurs before acquiring the lock.
24-
- `releaseLockStep`: Release a previously acquired lock.
23+
- [acquireLockStep](!resources!/references/medusa-workflows/steps/acquireLockStep): Attempt to acquire a lock. If the lock is already held by another process, this step will wait until the lock is released. It will fail if a timeout occurs before acquiring the lock.
24+
- [releaseLockStep](!resources!/references/medusa-workflows/steps/releaseLockStep): Release a previously acquired lock.
2525

2626
You can use these steps in your workflows to ensure that only one instance of a workflow can modify a resource at a time.
2727

@@ -128,4 +128,61 @@ The step will wait until the lock is acquired based on the configured Locking Mo
128128

129129
### Locking Module Service API
130130

131-
Refer to the [Locking Module reference](!resources!/references/locking-service) for more information on the Locking Module's service methods.
131+
Refer to the [Locking Module reference](!resources!/references/locking-service) for more information on the Locking Module's service methods.
132+
133+
---
134+
135+
## Locks in Nested Workflows
136+
137+
[Nested workflows](../execute-another-workflow/page.mdx) can't acquire locks. So, if you're using a workflow that acquires locks within another workflow, the parent workflow must handle the locking mechanism.
138+
139+
For example, consider a custom workflow that executes Medusa's [completeCartWorkflow](!resources!/references/medusa-workflows/completeCartWorkflow). The `completeCartWorkflow` acquires locks on the cart to prevent race conditions.
140+
141+
Therefore, the custom workflow must acquire and release the locks around the `completeCartWorkflow` execution. For example:
142+
143+
export const nestedWorkflowLockHighlights = [
144+
["16", "acquireLockStep", "Acquire a lock on the cart before executing the nested workflow."],
145+
["23", "completeCartWorkflow", "Execute the nested workflow that requires the lock."],
146+
["30", "releaseLockStep", "Release the lock after the nested workflow is complete."]
147+
]
148+
149+
```ts title="src/workflows/custom-complete-cart.ts" highlights={nestedWorkflowLockHighlights}
150+
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
151+
import {
152+
acquireLockStep,
153+
releaseLockStep,
154+
completeCartWorkflow
155+
} from "@medusajs/medusa/core-flows"
156+
157+
type WorkflowInput = {
158+
cart_id: string;
159+
}
160+
161+
export const customCompleteCartWorkflow = createWorkflow(
162+
"custom-complete-cart",
163+
(input: WorkflowInput) => {
164+
// acquire the same lock as the nested workflow
165+
acquireLockStep({
166+
key: input.cart_id,
167+
timeout: 30,
168+
ttl: 60 * 2,
169+
})
170+
171+
// Execute the nested workflow
172+
completeCartWorkflow.runAsStep({
173+
input: {
174+
id: input.cart_id,
175+
}
176+
})
177+
178+
// release the lock after the nested workflow is complete
179+
releaseLockStep({
180+
key: input.cart_id,
181+
})
182+
}
183+
)
184+
```
185+
186+
In the example above, the `customCompleteCartWorkflow` acquires a lock on the cart before executing the `completeCartWorkflow` and releases it afterward.
187+
188+
Make sure you're acquiring the same lock key as the nested workflow.

www/apps/book/generated/edit-dates.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export const generatedEditDates = {
132132
"app/learn/fundamentals/scheduled-jobs/interval/page.mdx": "2025-09-02T08:36:12.714Z",
133133
"app/learn/debugging-and-testing/feature-flags/create/page.mdx": "2025-09-02T08:36:12.714Z",
134134
"app/learn/debugging-and-testing/feature-flags/page.mdx": "2025-09-02T08:36:12.714Z",
135-
"app/learn/fundamentals/workflows/locks/page.mdx": "2025-09-15T09:37:00.808Z",
135+
"app/learn/fundamentals/workflows/locks/page.mdx": "2025-11-26T11:40:04.279Z",
136136
"app/learn/codemods/page.mdx": "2025-09-29T15:40:03.620Z",
137137
"app/learn/codemods/replace-imports/page.mdx": "2025-10-09T11:37:44.754Z",
138138
"app/learn/fundamentals/admin/translations/page.mdx": "2025-10-30T11:55:32.221Z",

www/apps/book/generated/sidebar.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,9 +840,9 @@ export const generatedSidebars = [
840840
"isPathHref": true,
841841
"type": "link",
842842
"path": "/learn/fundamentals/workflows/execute-another-workflow",
843-
"title": "Execute Nested Workflows",
843+
"title": "Nested Workflows",
844844
"children": [],
845-
"chapterTitle": "3.7.13. Execute Nested Workflows",
845+
"chapterTitle": "3.7.13. Nested Workflows",
846846
"number": "3.7.13."
847847
},
848848
{

www/apps/book/public/llms-full.txt

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22299,8 +22299,8 @@ The [Locking Module](https://docs.medusajs.com/resources/infrastructure-modules/
2229922299

2230022300
Medusa provides two steps that you can use to create locks in your workflows:
2230122301

22302-
- `acquireLockStep`: Attempt to acquire a lock. If the lock is already held by another process, this step will wait until the lock is released. It will fail if a timeout occurs before acquiring the lock.
22303-
- `releaseLockStep`: Release a previously acquired lock.
22302+
- [acquireLockStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/acquireLockStep/index.html.md): Attempt to acquire a lock. If the lock is already held by another process, this step will wait until the lock is released. It will fail if a timeout occurs before acquiring the lock.
22303+
- [releaseLockStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/releaseLockStep/index.html.md): Release a previously acquired lock.
2230422304

2230522305
You can use these steps in your workflows to ensure that only one instance of a workflow can modify a resource at a time.
2230622306

@@ -22391,6 +22391,57 @@ The step will wait until the lock is acquired based on the configured Locking Mo
2239122391

2239222392
Refer to the [Locking Module reference](https://docs.medusajs.com/resources/references/locking-service/index.html.md) for more information on the Locking Module's service methods.
2239322393

22394+
***
22395+
22396+
## Locks in Nested Workflows
22397+
22398+
[Nested workflows](https://docs.medusajs.com/learn/fundamentals/workflows/execute-another-workflow/index.html.md) can't acquire locks. So, if you're using a workflow that acquires locks within another workflow, the parent workflow must handle the locking mechanism.
22399+
22400+
For example, consider a custom workflow that executes Medusa's [completeCartWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/completeCartWorkflow/index.html.md). The `completeCartWorkflow` acquires locks on the cart to prevent race conditions.
22401+
22402+
Therefore, the custom workflow must acquire and release the locks around the `completeCartWorkflow` execution. For example:
22403+
22404+
```ts title="src/workflows/custom-complete-cart.ts" highlights={nestedWorkflowLockHighlights}
22405+
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
22406+
import {
22407+
acquireLockStep,
22408+
releaseLockStep,
22409+
completeCartWorkflow
22410+
} from "@medusajs/medusa/core-flows"
22411+
22412+
type WorkflowInput = {
22413+
cart_id: string;
22414+
}
22415+
22416+
export const customCompleteCartWorkflow = createWorkflow(
22417+
"custom-complete-cart",
22418+
(input: WorkflowInput) => {
22419+
// acquire the same lock as the nested workflow
22420+
acquireLockStep({
22421+
key: input.cart_id,
22422+
timeout: 30,
22423+
ttl: 60 * 2,
22424+
})
22425+
22426+
// Execute the nested workflow
22427+
completeCartWorkflow.runAsStep({
22428+
input: {
22429+
id: input.cart_id,
22430+
}
22431+
})
22432+
22433+
// release the lock after the nested workflow is complete
22434+
releaseLockStep({
22435+
key: input.cart_id,
22436+
})
22437+
}
22438+
)
22439+
```
22440+
22441+
In the example above, the `customCompleteCartWorkflow` acquires a lock on the cart before executing the `completeCartWorkflow` and releases it afterward.
22442+
22443+
Make sure you're acquiring the same lock key as the nested workflow.
22444+
2239422445

2239522446
# Long-Running Workflows
2239622447

@@ -45263,44 +45314,50 @@ For example, Medusa uses the Locking Module in inventory management to ensure th
4526345314

4526445315
You can use the Locking Module as part of the [workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
4526545316

45266-
In a step of your workflow, you can resolve the Locking Module's service and use its methods to execute an asynchronous job, acquire a lock, or release locks.
45317+
In a workflow, you can either use:
45318+
45319+
- Medusa's [acquireLockStep](https://docs.medusajs.com/references/medusa-workflows/steps/acquireLockStep/index.html.md) and [releaseLockStep](https://docs.medusajs.com/references/medusa-workflows/steps/releaseLockStep/index.html.md) to create locks around critical steps in your workflow.
45320+
- The Locking Module's service directly in your steps to have more control over the locking mechanism
4526745321

4526845322
For example:
4526945323

45270-
```ts
45271-
import { Modules } from "@medusajs/framework/utils"
45272-
import {
45273-
createStep,
45274-
createWorkflow,
45275-
} from "@medusajs/framework/workflows-sdk"
45324+
### Workflow
4527645325

45277-
const step1 = createStep(
45278-
"step-1",
45279-
async ({}, { container }) => {
45280-
const lockingModuleService = container.resolve(
45281-
Modules.LOCKING
45282-
)
45283-
const productModuleService = container.resolve(
45284-
Modules.PRODUCT
45285-
)
45326+
```ts title="src/workflows/charge-customer.ts" highlights={workflowLockHighlights}
45327+
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
45328+
import { acquireLockStep, releaseLockStep } from "@medusajs/medusa/core-flows"
45329+
import { chargeCustomerStep } from "./steps/charge-customer-step"
45330+
45331+
type WorkflowInput = {
45332+
customer_id: string;
45333+
order_id: string;
45334+
}
4528645335

45287-
await lockingModuleService.execute("prod_123", async () => {
45288-
await productModuleService.deleteProduct("prod_123")
45336+
export const chargeCustomerWorkflow = createWorkflow(
45337+
"charge-customer",
45338+
(input: WorkflowInput) => {
45339+
acquireLockStep({
45340+
key: input.order_id,
45341+
// Attempt to acquire the lock for two seconds before timing out
45342+
timeout: 2,
45343+
// Lock is only held for a maximum of ten seconds
45344+
ttl: 10,
4528945345
})
45290-
}
45291-
)
4529245346

45293-
export const workflow = createWorkflow(
45294-
"workflow-1",
45295-
() => {
45296-
step1()
45347+
chargeCustomerStep(input)
45348+
45349+
releaseLockStep({
45350+
key: input.order_id,
45351+
})
4529745352
}
4529845353
)
4529945354
```
4530045355

45301-
In the example above, you create a workflow that has a step. In the step, you resolve the services of the Locking and Product modules from the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md).
45356+
In the example above, you create a workflow that acquires a lock on an order using the `acquireLockStep` before charging a customer. The lock is then released using the `releaseLockStep` after the operation is complete.
4530245357

45303-
Then, you use the `execute` method of the Locking Module to acquire a lock for the product with the ID `prod_123` and execute an asynchronous function, which deletes the product.
45358+
This ensures that only one instance of the workflow can modify the order at a time, preventing issues like double charging the customer.
45359+
45360+
### Step
4530445361

4530545362
***
4530645363

www/apps/book/sidebar.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ export const sidebars = [
441441
{
442442
type: "link",
443443
path: "/learn/fundamentals/workflows/execute-another-workflow",
444-
title: "Execute Nested Workflows",
444+
title: "Nested Workflows",
445445
},
446446
{
447447
type: "link",

www/apps/resources/app/infrastructure-modules/locking/page.mdx

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Table, CardList } from "docs-ui"
1+
import { Table, CardList, Tabs, TabsList, TabsTrigger, TabsContent, TabsContentWrapper } from "docs-ui"
22

33
export const metadata = {
44
title: `Locking Module`,
@@ -22,44 +22,96 @@ For example, Medusa uses the Locking Module in inventory management to ensure th
2222

2323
You can use the Locking Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
2424

25-
In a step of your workflow, you can resolve the Locking Module's service and use its methods to execute an asynchronous job, acquire a lock, or release locks.
25+
In a workflow, you can either use:
26+
27+
- Medusa's [acquireLockStep](/references/medusa-workflows/steps/acquireLockStep) and [releaseLockStep](/references/medusa-workflows/steps/releaseLockStep) to create locks around critical steps in your workflow.
28+
- The Locking Module's service directly in your steps to have more control over the locking mechanism
2629

2730
For example:
2831

29-
```ts
30-
import { Modules } from "@medusajs/framework/utils"
31-
import {
32-
createStep,
33-
createWorkflow,
34-
} from "@medusajs/framework/workflows-sdk"
35-
36-
const step1 = createStep(
37-
"step-1",
38-
async ({}, { container }) => {
39-
const lockingModuleService = container.resolve(
40-
Modules.LOCKING
32+
<Tabs defaultValue="workflow">
33+
<TabsList>
34+
<TabsTrigger value="workflow">Workflow</TabsTrigger>
35+
<TabsTrigger value="step">Step</TabsTrigger>
36+
</TabsList>
37+
<TabsContentWrapper>
38+
<TabsContent value="workflow">
39+
40+
```ts title="src/workflows/charge-customer.ts"
41+
import { createWorkflow } from "@medusajs/framework/workflows-sdk"
42+
import { acquireLockStep, releaseLockStep } from "@medusajs/medusa/core-flows"
43+
import { chargeCustomerStep } from "./steps/charge-customer-step"
44+
45+
type WorkflowInput = {
46+
customer_id: string;
47+
order_id: string;
48+
}
49+
50+
export const chargeCustomerWorkflow = createWorkflow(
51+
"charge-customer",
52+
(input: WorkflowInput) => {
53+
acquireLockStep({
54+
key: input.order_id,
55+
// Attempt to acquire the lock for two seconds before timing out
56+
timeout: 2,
57+
// Lock is only held for a maximum of ten seconds
58+
ttl: 10,
59+
})
60+
61+
chargeCustomerStep(input)
62+
63+
releaseLockStep({
64+
key: input.order_id,
65+
})
66+
}
4167
)
42-
const productModuleService = container.resolve(
43-
Modules.PRODUCT
68+
```
69+
70+
In the example above, you create a workflow that acquires a lock on an order using the `acquireLockStep` before charging a customer. The lock is then released using the `releaseLockStep` after the operation is complete.
71+
72+
This ensures that only one instance of the workflow can modify the order at a time, preventing issues like double charging the customer.
73+
74+
</TabsContent>
75+
<TabsContent value="step">
76+
77+
```ts
78+
import { Modules } from "@medusajs/framework/utils"
79+
import {
80+
createStep,
81+
createWorkflow,
82+
} from "@medusajs/framework/workflows-sdk"
83+
84+
const step1 = createStep(
85+
"step-1",
86+
async ({}, { container }) => {
87+
const lockingModuleService = container.resolve(
88+
Modules.LOCKING
89+
)
90+
const productModuleService = container.resolve(
91+
Modules.PRODUCT
92+
)
93+
94+
await lockingModuleService.execute("prod_123", async () => {
95+
await productModuleService.deleteProduct("prod_123")
96+
})
97+
}
4498
)
4599

46-
await lockingModuleService.execute("prod_123", async () => {
47-
await productModuleService.deleteProduct("prod_123")
48-
})
49-
}
50-
)
51-
52-
export const workflow = createWorkflow(
53-
"workflow-1",
54-
() => {
55-
step1()
56-
}
57-
)
58-
```
100+
export const workflow = createWorkflow(
101+
"workflow-1",
102+
() => {
103+
step1()
104+
}
105+
)
106+
```
107+
108+
In the example above, you create a workflow that has a step. In the step, you resolve the services of the Locking and Product modules from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
59109

60-
In the example above, you create a workflow that has a step. In the step, you resolve the services of the Locking and Product modules from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
110+
Then, you use the `execute` method of the Locking Module to acquire a lock for the product with the ID `prod_123` and execute an asynchronous function, which deletes the product.
61111

62-
Then, you use the `execute` method of the Locking Module to acquire a lock for the product with the ID `prod_123` and execute an asynchronous function, which deletes the product.
112+
</TabsContent>
113+
</TabsContentWrapper>
114+
</Tabs>
63115

64116
---
65117

0 commit comments

Comments
 (0)