Skip to content

Commit fa1793e

Browse files
chore(core-utils): avoid overfetching to refresh cart (#11602)
What: * Not all Cart operations need a full refresh updating items. This PR introduces a flag to force the refresh for special ocasions, like updating the Cart's region, or transfering the Cart to another customer. For all other flows it will update only promotions, taxes and payment collection if needed.
1 parent eeebb35 commit fa1793e

File tree

7 files changed

+130
-109
lines changed

7 files changed

+130
-109
lines changed

.changeset/twenty-oranges-agree.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@medusajs/core-flows": patch
3+
---
4+
5+
chore(core-flows): avoid overfetching to refresh cart

integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2068,7 +2068,7 @@ medusaIntegrationTestRunner({
20682068
unit_price: 4000,
20692069
is_custom_price: true,
20702070
quantity: 2,
2071-
title: "Test variant",
2071+
title: "Test item",
20722072
})
20732073
)
20742074
})

packages/core/core-flows/src/cart/utils/fields.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export const cartFieldsForRefreshSteps = [
3535
"customer.*",
3636
"customer.groups.*",
3737
"promotions.code",
38+
"payment_collection.id",
39+
"payment_collection.raw_amount",
40+
"payment_collection.amount",
41+
"payment_collection.currency_code",
42+
"payment_collection.payment_sessions.id",
3843
]
3944

4045
export const completeCartFields = [

packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts

Lines changed: 68 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
PromotionActions,
55
} from "@medusajs/framework/utils"
66
import {
7-
createHook,
87
createWorkflow,
98
transform,
109
when,
@@ -41,57 +40,58 @@ export type RefreshCartItemsWorkflowInput = {
4140
* These promotion codes will replace previously applied codes.
4241
*/
4342
promo_codes?: string[]
43+
/**
44+
* Force refresh the cart items
45+
*/
46+
force_refresh?: boolean
4447
}
4548

4649
export const refreshCartItemsWorkflowId = "refresh-cart-items"
4750
/**
4851
* This workflow refreshes a cart to ensure its prices, promotion codes, taxes, and other details are applied correctly. It's useful
4952
* after making a chnge to a cart, such as after adding an item to the cart or adding a promotion code.
50-
*
53+
*
5154
* This workflow is used by other cart-related workflows, such as the {@link addToCartWorkflow} after an item
5255
* is added to the cart.
53-
*
56+
*
5457
* You can use this workflow within your own customizations or custom workflows, allowing you to refresh the cart after making updates to it in your
5558
* custom flows.
56-
*
59+
*
5760
* @example
5861
* const { result } = await refreshCartItemsWorkflow(container)
5962
* .run({
6063
* input: {
6164
* cart_id: "cart_123",
6265
* }
6366
* })
64-
*
67+
*
6568
* @summary
66-
*
69+
*
6770
* Refresh a cart's details after an update.
68-
*
69-
* @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.
71+
*
7072
*/
7173
export const refreshCartItemsWorkflow = createWorkflow(
7274
refreshCartItemsWorkflowId,
73-
(
74-
input: WorkflowData<RefreshCartItemsWorkflowInput>
75-
) => {
76-
const cart = useRemoteQueryStep({
77-
entry_point: "cart",
78-
fields: cartFieldsForRefreshSteps,
79-
variables: { id: input.cart_id },
80-
list: false,
81-
})
75+
(input: WorkflowData<RefreshCartItemsWorkflowInput>) => {
76+
when({ input }, ({ input }) => {
77+
return !!input.force_refresh
78+
}).then(() => {
79+
const cart = useRemoteQueryStep({
80+
entry_point: "cart",
81+
fields: cartFieldsForRefreshSteps,
82+
variables: { id: input.cart_id },
83+
list: false,
84+
})
8285

83-
const variantIds = transform({ cart }, (data) => {
84-
return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean)
85-
})
86+
const variantIds = transform({ cart }, (data) => {
87+
return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean)
88+
})
8689

87-
const cartPricingContext = transform({ cart }, ({ cart }) => {
88-
return filterObjectByKeys(cart, cartFieldsForPricingContext)
89-
})
90+
const cartPricingContext = transform({ cart }, ({ cart }) => {
91+
return filterObjectByKeys(cart, cartFieldsForPricingContext)
92+
})
9093

91-
const variants = when({ variantIds }, ({ variantIds }) => {
92-
return !!variantIds.length
93-
}).then(() => {
94-
return useRemoteQueryStep({
94+
const variants = useRemoteQueryStep({
9595
entry_point: "variants",
9696
fields: productVariantsFields,
9797
variables: {
@@ -101,62 +101,59 @@ export const refreshCartItemsWorkflow = createWorkflow(
101101
},
102102
},
103103
}).config({ name: "fetch-variants" })
104-
})
105104

106-
validateVariantPricesStep({ variants })
107-
108-
const validate = createHook("validate", {
109-
input,
110-
cart,
111-
})
112-
113-
const lineItems = transform({ cart, variants }, ({ cart, variants }) => {
114-
const items = cart.items.map((item) => {
115-
const variant = (variants ?? []).find((v) => v.id === item.variant_id)!
116-
117-
const input: PrepareLineItemDataInput = {
118-
item,
119-
variant: variant,
120-
cartId: cart.id,
121-
unitPrice: item.unit_price,
122-
isTaxInclusive: item.is_tax_inclusive,
123-
}
124-
125-
if (variant && !item.is_custom_price) {
126-
input.unitPrice = variant.calculated_price?.calculated_amount
127-
input.isTaxInclusive =
128-
variant.calculated_price?.is_calculated_price_tax_inclusive
129-
}
130-
131-
const preparedItem = prepareLineItemData(input)
132-
133-
return {
134-
selector: { id: item.id },
135-
data: preparedItem,
136-
}
105+
validateVariantPricesStep({ variants })
106+
107+
const lineItems = transform({ cart, variants }, ({ cart, variants }) => {
108+
const items = cart.items.map((item) => {
109+
const variant = (variants ?? []).find(
110+
(v) => v.id === item.variant_id
111+
)!
112+
113+
const input: PrepareLineItemDataInput = {
114+
item,
115+
variant: variant,
116+
cartId: cart.id,
117+
unitPrice: item.unit_price,
118+
isTaxInclusive: item.is_tax_inclusive,
119+
}
120+
121+
if (variant && !item.is_custom_price) {
122+
input.unitPrice = variant.calculated_price?.calculated_amount
123+
input.isTaxInclusive =
124+
variant.calculated_price?.is_calculated_price_tax_inclusive
125+
}
126+
127+
const preparedItem = prepareLineItemData(input)
128+
129+
return {
130+
selector: { id: item.id },
131+
data: preparedItem,
132+
}
133+
})
134+
135+
return items
137136
})
138137

139-
return items
140-
})
141-
142-
updateLineItemsStep({
143-
id: cart.id,
144-
items: lineItems,
138+
updateLineItemsStep({
139+
id: cart.id,
140+
items: lineItems,
141+
})
145142
})
146143

147144
const refetchedCart = useRemoteQueryStep({
148145
entry_point: "cart",
149146
fields: cartFieldsForRefreshSteps,
150-
variables: { id: cart.id },
147+
variables: { id: input.cart_id },
151148
list: false,
152149
}).config({ name: "refetch–cart" })
153150

154151
refreshCartShippingMethodsWorkflow.runAsStep({
155-
input: { cart_id: cart.id },
152+
input: { cart_id: input.cart_id },
156153
})
157154

158155
updateTaxLinesWorkflow.runAsStep({
159-
input: { cart_id: cart.id },
156+
input: { cart_id: input.cart_id },
160157
})
161158

162159
const cartPromoCodes = transform(
@@ -172,18 +169,16 @@ export const refreshCartItemsWorkflow = createWorkflow(
172169

173170
updateCartPromotionsWorkflow.runAsStep({
174171
input: {
175-
cart_id: cart.id,
172+
cart_id: input.cart_id,
176173
promo_codes: cartPromoCodes,
177174
action: PromotionActions.REPLACE,
178175
},
179176
})
180177

181178
refreshPaymentCollectionForCartWorkflow.runAsStep({
182-
input: { cart_id: cart.id },
179+
input: { cart: refetchedCart },
183180
})
184181

185-
return new WorkflowResponse(refetchedCart, {
186-
hooks: [validate],
187-
})
182+
return new WorkflowResponse(refetchedCart)
188183
}
189184
)

packages/core/core-flows/src/cart/workflows/refresh-payment-collection.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,56 +19,68 @@ export type RefreshPaymentCollectionForCartWorklowInput = {
1919
/**
2020
* The cart's ID.
2121
*/
22-
cart_id: string
22+
cart_id?: string
23+
/**
24+
* The Cart reference.
25+
*/
26+
cart?: any
2327
}
2428

2529
export const refreshPaymentCollectionForCartWorkflowId =
2630
"refresh-payment-collection-for-cart"
2731
/**
2832
* This workflow refreshes a cart's payment collection, which is useful once the cart is created or when its details
29-
* are updated. If the cart's total changes to the amount in its payment collection, the payment collection's payment sessions are
33+
* are updated. If the cart's total changes to the amount in its payment collection, the payment collection's payment sessions are
3034
* deleted. It also syncs the payment collection's amount, currency code, and other details with the details in the cart.
31-
*
35+
*
3236
* This workflow is used by other cart-related workflows, such as the {@link refreshCartItemsWorkflow} to refresh the cart's
3337
* payment collection after an update.
34-
*
38+
*
3539
* You can use this workflow within your own customizations or custom workflows, allowing you to refresh the cart's payment collection after making updates to it in your
3640
* custom flows.
37-
*
41+
*
3842
* @example
3943
* const { result } = await refreshPaymentCollectionForCartWorkflow(container)
4044
* .run({
4145
* input: {
4246
* cart_id: "cart_123",
4347
* }
4448
* })
45-
*
49+
*
4650
* @summary
47-
*
51+
*
4852
* Refresh a cart's payment collection details.
49-
*
53+
*
5054
* @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.
5155
*/
5256
export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
5357
refreshPaymentCollectionForCartWorkflowId,
5458
(input: WorkflowData<RefreshPaymentCollectionForCartWorklowInput>) => {
55-
const cart = useRemoteQueryStep({
56-
entry_point: "cart",
57-
fields: [
58-
"id",
59-
"region_id",
60-
"currency_code",
61-
"total",
62-
"raw_total",
63-
"payment_collection.id",
64-
"payment_collection.raw_amount",
65-
"payment_collection.amount",
66-
"payment_collection.currency_code",
67-
"payment_collection.payment_sessions.id",
68-
],
69-
variables: { id: input.cart_id },
70-
throw_if_key_not_found: true,
71-
list: false,
59+
const fetchCart = when({ input }, ({ input }) => {
60+
return !input.cart
61+
}).then(() => {
62+
return useRemoteQueryStep({
63+
entry_point: "cart",
64+
fields: [
65+
"id",
66+
"region_id",
67+
"currency_code",
68+
"total",
69+
"raw_total",
70+
"payment_collection.id",
71+
"payment_collection.raw_amount",
72+
"payment_collection.amount",
73+
"payment_collection.currency_code",
74+
"payment_collection.payment_sessions.id",
75+
],
76+
variables: { id: input.cart_id },
77+
throw_if_key_not_found: true,
78+
list: false,
79+
})
80+
})
81+
82+
const cart = transform({ fetchCart, input }, ({ fetchCart, input }) => {
83+
return input.cart ?? fetchCart
7284
})
7385

7486
const validate = createHook("validate", {

packages/core/core-flows/src/cart/workflows/transfer-cart-customer.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import { refreshCartItemsWorkflow } from "./refresh-cart-items"
1313
/**
1414
* The cart ownership transfer details.
1515
*/
16-
export type TransferCartCustomerWorkflowInput = {
16+
export type TransferCartCustomerWorkflowInput = {
1717
/**
1818
* The cart's ID.
1919
*/
20-
id: string;
20+
id: string
2121
/**
2222
* The ID of the customer to transfer the cart to.
2323
*/
@@ -29,9 +29,9 @@ export const transferCartCustomerWorkflowId = "transfer-cart-customer"
2929
* This workflow transfers a cart's customer ownership to another customer. It's useful if a customer logs in after
3030
* adding the items to their cart, allowing you to transfer the cart's ownership to the logged-in customer. This workflow is used
3131
* by the [Set Cart's Customer Store API Route](https://docs.medusajs.com/api/store#carts_postcartsidcustomer).
32-
*
32+
*
3333
* You can use this workflow within your own customizations or custom workflows, allowing you to set the cart's customer within your custom flows.
34-
*
34+
*
3535
* @example
3636
* const { result } = await transferCartCustomerWorkflow(container)
3737
* .run({
@@ -40,11 +40,11 @@ export const transferCartCustomerWorkflowId = "transfer-cart-customer"
4040
* customer_id: "cus_123"
4141
* }
4242
* })
43-
*
43+
*
4444
* @summary
45-
*
45+
*
4646
* Refresh a cart's payment collection details.
47-
*
47+
*
4848
* @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.
4949
*/
5050
export const transferCartCustomerWorkflow = createWorkflow(
@@ -108,7 +108,7 @@ export const transferCartCustomerWorkflow = createWorkflow(
108108
updateCartsStep(cartInput)
109109

110110
refreshCartItemsWorkflow.runAsStep({
111-
input: { cart_id: input.id },
111+
input: { cart_id: input.id, force_refresh: true },
112112
})
113113
}
114114
)

0 commit comments

Comments
 (0)