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
@@ -114,7 +114,10 @@ export const addToCartWorkflowId = "add-to-cart"
* :::
*/
export const addToCartWorkflow = createWorkflow(
addToCartWorkflowId,
{
name: addToCartWorkflowId,
idempotent: true,
},
(input: WorkflowData<AddToCartWorkflowInputDTO & AdditionalData>) => {
const cartQuery = useQueryGraphStep({
entity: "cart",
@@ -79,7 +79,10 @@ export const createPaymentCollectionForCartWorkflowId =
* Create payment collection for cart.
*/
export const createPaymentCollectionForCartWorkflow = createWorkflow(
createPaymentCollectionForCartWorkflowId,
{
name: createPaymentCollectionForCartWorkflowId,
idempotent: true,
},
(
input: WorkflowData<CreatePaymentCollectionForCartWorkflowInputDTO>
): WorkflowData<void> => {
@@ -128,7 +128,10 @@ export const refreshCartItemsWorkflowId = "refresh-cart-items"
*
*/
export const refreshCartItemsWorkflow = createWorkflow(
refreshCartItemsWorkflowId,
{
name: refreshCartItemsWorkflowId,
idempotent: true,
},
(input: WorkflowData<RefreshCartItemsWorkflowInput & AdditionalData>) => {
const setPricingContext = createHook(
"setPricingContext",
@@ -50,7 +50,10 @@ export const refreshCartShippingMethodsWorkflowId =
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
*/
export const refreshCartShippingMethodsWorkflow = createWorkflow(
refreshCartShippingMethodsWorkflowId,
{
name: refreshCartShippingMethodsWorkflowId,
idempotent: true,
},
(input: WorkflowData<RefreshCartShippingMethodsWorkflowInput>) => {
const fetchCart = when("fetch-cart", { input }, ({ input }) => {
return !input.cart
@@ -54,7 +54,10 @@ export const refreshPaymentCollectionForCartWorkflowId =
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
*/
export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
refreshPaymentCollectionForCartWorkflowId,
{
name: refreshPaymentCollectionForCartWorkflowId,
idempotent: true,
},
(input: WorkflowData<RefreshPaymentCollectionForCartWorklowInput>) => {
const fetchCart = when("should-fetch-cart", { input }, ({ input }) => {
return !input.cart
@@ -62,7 +62,10 @@ export const refundPaymentAndRecreatePaymentSessionWorkflowId =
* Refund a payment and create a new payment session.
*/
export const refundPaymentAndRecreatePaymentSessionWorkflow = createWorkflow(
refundPaymentAndRecreatePaymentSessionWorkflowId,
{
name: refundPaymentAndRecreatePaymentSessionWorkflowId,
idempotent: true,
},
(
input: WorkflowData<refundPaymentAndRecreatePaymentSessionWorkflowInput>
): WorkflowResponse<PaymentSessionDTO> => {
@@ -1,3 +1,4 @@
import { CartWorkflowEvents } from "@medusajs/framework/utils"
import {
createHook,
createWorkflow,
@@ -9,7 +10,6 @@ import {
import { emitEventStep, useQueryGraphStep } from "../../common"
import { updateCartsStep } from "../steps"
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
import { CartWorkflowEvents } from "@medusajs/framework/utils"
/**
* The cart ownership transfer details.
@@ -49,7 +49,10 @@ export const transferCartCustomerWorkflowId = "transfer-cart-customer"
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
*/
export const transferCartCustomerWorkflow = createWorkflow(
transferCartCustomerWorkflowId,
{
name: transferCartCustomerWorkflowId,
idempotent: true,
},
(input: WorkflowData<TransferCartCustomerWorkflowInput>) => {
const cartQuery = useQueryGraphStep({
entity: "cart",
@@ -73,7 +73,10 @@ export const updateCartPromotionsWorkflowId = "update-cart-promotions"
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
*/
export const updateCartPromotionsWorkflow = createWorkflow(
updateCartPromotionsWorkflowId,
{
name: updateCartPromotionsWorkflowId,
idempotent: true,
},
(input: WorkflowData<UpdateCartPromotionsWorkflowInput>) => {
const fetchCart = when("should-fetch-cart", { input }, ({ input }) => {
return !input.cart
@@ -78,7 +78,10 @@ export const updateCartWorkflowId = "update-cart"
* @property hooks.cartUpdated - This hook is executed after a cart is update. You can consume this hook to perform custom actions on the updated cart.
*/
export const updateCartWorkflow = createWorkflow(
updateCartWorkflowId,
{
name: updateCartWorkflowId,
idempotent: true,
},
(input: WorkflowData<UpdateCartWorkflowInput>) => {
const { data: cartToUpdate } = useQueryGraphStep({
entity: "cart",
@@ -104,7 +104,10 @@ export const updateLineItemInCartWorkflowId = "update-line-item-in-cart"
* :::
*/
export const updateLineItemInCartWorkflow = createWorkflow(
updateLineItemInCartWorkflowId,
{
name: updateLineItemInCartWorkflowId,
idempotent: true,
},
(
input: WorkflowData<UpdateLineItemInCartWorkflowInputDTO & AdditionalData>
) => {
@@ -5,7 +5,7 @@ import { deleteLineItemsStep } from "../steps/delete-line-items"
/**
* The data to delete line items from a cart.
*/
export type DeleteLineItemsWorkflowInput = {
export type DeleteLineItemsWorkflowInput = {
/**
* The cart's ID.
*/
@@ -20,10 +20,10 @@ export const deleteLineItemsWorkflowId = "delete-line-items"
/**
* This workflow deletes line items from a cart. It's used by the
* [Delete Line Item Store API Route](https://docs.medusajs.com/api/store#carts_deletecartsidlineitemsline_id).
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to
* delete line items from a cart within your custom flows.
*
*
* @example
* const { result } = await deleteLineItemsWorkflow(container)
* .run({
@@ -32,13 +32,16 @@ export const deleteLineItemsWorkflowId = "delete-line-items"
* ids: ["li_123"]
* }
* })
*
*
* @summary
*
*
* Delete line items from a cart.
*/
export const deleteLineItemsWorkflow = createWorkflow(
deleteLineItemsWorkflowId,
{
name: deleteLineItemsWorkflowId,
idempotent: true,
},
(input: WorkflowData<DeleteLineItemsWorkflowInput>) => {
deleteLineItemsStep(input.ids)