chore: idempotent cart operations (#13236)

* chore(core-flows): idempotent cart operations

* changeset

* add tests

* revert

* revert route

* promo test

* skip bugs

* fix test

* tests

* avoid workflow name conflict

* prevent nested workflow from being deleted until the top level parent finishes

* remove unused setTimeout

* update changeset

* rm comments

---------

Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
This commit is contained in:
Carlos R. L. Rodrigues
2025-08-28 15:04:00 +02:00
committed by GitHub
co-authored by adrien2p
parent b111d01898
commit 9412669e65
38 changed files with 890 additions and 64 deletions
@@ -1,23 +1,25 @@
import { transferCartCustomerWorkflow } from "@medusajs/core-flows"
import { transferCartCustomerWorkflowId } from "@medusajs/core-flows"
import { HttpTypes } from "@medusajs/framework/types"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
import { refetchCart } from "../../helpers"
export const POST = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<HttpTypes.StoreCartResponse>
) => {
const workflow = transferCartCustomerWorkflow(req.scope)
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
await workflow.run({
await we.run(transferCartCustomerWorkflowId, {
input: {
id: req.params.id,
customer_id: req.auth_context?.actor_id,
},
transactionId: "cart-transfer-customer-" + req.params.id,
})
const cart = await refetchCart(
@@ -1,11 +1,11 @@
import {
deleteLineItemsWorkflow,
updateLineItemInCartWorkflow,
deleteLineItemsWorkflowId,
updateLineItemInCartWorkflowId,
} from "@medusajs/core-flows"
import { prepareListQuery } from "@medusajs/framework"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
import { MedusaError } from "@medusajs/framework/utils"
import { MedusaError, Modules } from "@medusajs/framework/utils"
import { refetchCart } from "../../../helpers"
import { StoreUpdateCartLineItemType } from "../../../validators"
@@ -40,12 +40,14 @@ export const POST = async (
)
}
await updateLineItemInCartWorkflow(req.scope).run({
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
await we.run(updateLineItemInCartWorkflowId, {
input: {
cart_id: req.params.id,
item_id: item.id,
update: req.validatedBody,
},
transactionId: "cart-update-item-" + req.params.id,
})
const updatedCart = await refetchCart(
@@ -63,8 +65,10 @@ export const DELETE = async (
) => {
const id = req.params.line_id
await deleteLineItemsWorkflow(req.scope).run({
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
await we.run(deleteLineItemsWorkflowId, {
input: { cart_id: req.params.id, ids: [id] },
transactionId: "cart-delete-item-" + req.params.id,
})
const cart = await refetchCart(
@@ -1,6 +1,7 @@
import { addToCartWorkflow } from "@medusajs/core-flows"
import { addToCartWorkflowId } from "@medusajs/core-flows"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
import { Modules } from "@medusajs/utils"
import { refetchCart } from "../../helpers"
import { StoreAddCartLineItemType } from "../../validators"
@@ -8,11 +9,13 @@ export const POST = async (
req: MedusaRequest<StoreAddCartLineItemType>,
res: MedusaResponse<HttpTypes.StoreCartResponse>
) => {
await addToCartWorkflow(req.scope).run({
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
await we.run(addToCartWorkflowId, {
input: {
cart_id: req.params.id,
items: [req.validatedBody],
},
transactionId: "cart-add-item-" + req.params.id,
})
const cart = await refetchCart(
@@ -1,22 +1,26 @@
import { updateCartPromotionsWorkflow } from "@medusajs/core-flows"
import { PromotionActions } from "@medusajs/framework/utils"
import { updateCartPromotionsWorkflowId } from "@medusajs/core-flows"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { refetchCart } from "../../helpers"
import { HttpTypes } from "@medusajs/framework/types"
import { Modules, PromotionActions } from "@medusajs/framework/utils"
import { refetchCart } from "../../helpers"
export const POST = async (
req: MedusaRequest<HttpTypes.StoreCartAddPromotion>,
res: MedusaResponse<HttpTypes.StoreCartResponse>
) => {
const workflow = updateCartPromotionsWorkflow(req.scope)
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
const payload = req.validatedBody
await workflow.run({
await we.run(updateCartPromotionsWorkflowId, {
input: {
promo_codes: payload.promo_codes,
cart_id: req.params.id,
action: PromotionActions.ADD,
action:
payload.promo_codes.length > 0
? PromotionActions.ADD
: PromotionActions.REPLACE,
},
transactionId: "cart-update-promotions-" + req.params.id,
})
const cart = await refetchCart(
@@ -34,15 +38,16 @@ export const DELETE = async (
cart: HttpTypes.StoreCart
}>
) => {
const workflow = updateCartPromotionsWorkflow(req.scope)
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
const payload = req.validatedBody
await workflow.run({
await we.run(updateCartPromotionsWorkflowId, {
input: {
promo_codes: payload.promo_codes,
cart_id: req.params.id,
action: PromotionActions.REMOVE,
},
transactionId: "cart-delete-promotions-" + req.params.id,
})
const cart = await refetchCart(
@@ -1,4 +1,4 @@
import { updateCartWorkflow } from "@medusajs/core-flows"
import { updateCartWorkflowId } from "@medusajs/core-flows"
import {
AdditionalData,
HttpTypes,
@@ -6,6 +6,7 @@ import {
} from "@medusajs/framework/types"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { Modules } from "@medusajs/framework/utils"
import { refetchCart } from "../helpers"
export const GET = async (
@@ -27,13 +28,14 @@ export const POST = async (
cart: HttpTypes.StoreCart
}>
) => {
const workflow = updateCartWorkflow(req.scope)
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
await workflow.run({
await we.run(updateCartWorkflowId, {
input: {
...req.validatedBody,
id: req.params.id,
},
transactionId: "cart-update-" + req.params.id,
})
const cart = await refetchCart(
@@ -1,13 +1,14 @@
import { createPaymentCollectionForCartWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/framework/utils"
import { createPaymentCollectionForCartWorkflowId } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/framework/utils"
import { Modules } from "@medusajs/utils"
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.StoreCreatePaymentCollection>,
@@ -27,8 +28,10 @@ export const POST = async (
let paymentCollection = cartCollectionRelation?.payment_collection
if (!paymentCollection) {
await createPaymentCollectionForCartWorkflow(req.scope).run({
const we = req.scope.resolve(Modules.WORKFLOW_ENGINE)
await we.run(createPaymentCollectionForCartWorkflowId, {
input: req.body,
transactionId: "create-payment-collection-for-cart-" + cart_id,
})
const [cartCollectionRelation] = await remoteQuery(