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.
This commit is contained in:
Carlos R. L. Rodrigues
2025-02-26 10:00:04 +00:00
committed by GitHub
parent eeebb35758
commit fa1793e8e9
7 changed files with 126 additions and 105 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/core-flows": patch
---
chore(core-flows): avoid overfetching to refresh cart
@@ -2068,7 +2068,7 @@ medusaIntegrationTestRunner({
unit_price: 4000, unit_price: 4000,
is_custom_price: true, is_custom_price: true,
quantity: 2, quantity: 2,
title: "Test variant", title: "Test item",
}) })
) )
}) })
@@ -35,6 +35,11 @@ export const cartFieldsForRefreshSteps = [
"customer.*", "customer.*",
"customer.groups.*", "customer.groups.*",
"promotions.code", "promotions.code",
"payment_collection.id",
"payment_collection.raw_amount",
"payment_collection.amount",
"payment_collection.currency_code",
"payment_collection.payment_sessions.id",
] ]
export const completeCartFields = [ export const completeCartFields = [
@@ -4,7 +4,6 @@ import {
PromotionActions, PromotionActions,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { import {
createHook,
createWorkflow, createWorkflow,
transform, transform,
when, when,
@@ -41,6 +40,10 @@ export type RefreshCartItemsWorkflowInput = {
* These promotion codes will replace previously applied codes. * These promotion codes will replace previously applied codes.
*/ */
promo_codes?: string[] promo_codes?: string[]
/**
* Force refresh the cart items
*/
force_refresh?: boolean
} }
export const refreshCartItemsWorkflowId = "refresh-cart-items" export const refreshCartItemsWorkflowId = "refresh-cart-items"
@@ -66,32 +69,29 @@ export const refreshCartItemsWorkflowId = "refresh-cart-items"
* *
* Refresh a cart's details after an update. * Refresh a cart's details after an update.
* *
* @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 refreshCartItemsWorkflow = createWorkflow( export const refreshCartItemsWorkflow = createWorkflow(
refreshCartItemsWorkflowId, refreshCartItemsWorkflowId,
( (input: WorkflowData<RefreshCartItemsWorkflowInput>) => {
input: WorkflowData<RefreshCartItemsWorkflowInput> when({ input }, ({ input }) => {
) => { return !!input.force_refresh
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart_id },
list: false,
})
const variantIds = transform({ cart }, (data) => {
return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean)
})
const cartPricingContext = transform({ cart }, ({ cart }) => {
return filterObjectByKeys(cart, cartFieldsForPricingContext)
})
const variants = when({ variantIds }, ({ variantIds }) => {
return !!variantIds.length
}).then(() => { }).then(() => {
return useRemoteQueryStep({ const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart_id },
list: false,
})
const variantIds = transform({ cart }, (data) => {
return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean)
})
const cartPricingContext = transform({ cart }, ({ cart }) => {
return filterObjectByKeys(cart, cartFieldsForPricingContext)
})
const variants = useRemoteQueryStep({
entry_point: "variants", entry_point: "variants",
fields: productVariantsFields, fields: productVariantsFields,
variables: { variables: {
@@ -101,62 +101,59 @@ export const refreshCartItemsWorkflow = createWorkflow(
}, },
}, },
}).config({ name: "fetch-variants" }) }).config({ name: "fetch-variants" })
})
validateVariantPricesStep({ variants }) validateVariantPricesStep({ variants })
const validate = createHook("validate", { const lineItems = transform({ cart, variants }, ({ cart, variants }) => {
input, const items = cart.items.map((item) => {
cart, const variant = (variants ?? []).find(
}) (v) => v.id === item.variant_id
)!
const lineItems = transform({ cart, variants }, ({ cart, variants }) => { const input: PrepareLineItemDataInput = {
const items = cart.items.map((item) => { item,
const variant = (variants ?? []).find((v) => v.id === item.variant_id)! variant: variant,
cartId: cart.id,
unitPrice: item.unit_price,
isTaxInclusive: item.is_tax_inclusive,
}
const input: PrepareLineItemDataInput = { if (variant && !item.is_custom_price) {
item, input.unitPrice = variant.calculated_price?.calculated_amount
variant: variant, input.isTaxInclusive =
cartId: cart.id, variant.calculated_price?.is_calculated_price_tax_inclusive
unitPrice: item.unit_price, }
isTaxInclusive: item.is_tax_inclusive,
}
if (variant && !item.is_custom_price) { const preparedItem = prepareLineItemData(input)
input.unitPrice = variant.calculated_price?.calculated_amount
input.isTaxInclusive =
variant.calculated_price?.is_calculated_price_tax_inclusive
}
const preparedItem = prepareLineItemData(input) return {
selector: { id: item.id },
data: preparedItem,
}
})
return { return items
selector: { id: item.id },
data: preparedItem,
}
}) })
return items updateLineItemsStep({
}) id: cart.id,
items: lineItems,
updateLineItemsStep({ })
id: cart.id,
items: lineItems,
}) })
const refetchedCart = useRemoteQueryStep({ const refetchedCart = useRemoteQueryStep({
entry_point: "cart", entry_point: "cart",
fields: cartFieldsForRefreshSteps, fields: cartFieldsForRefreshSteps,
variables: { id: cart.id }, variables: { id: input.cart_id },
list: false, list: false,
}).config({ name: "refetchcart" }) }).config({ name: "refetchcart" })
refreshCartShippingMethodsWorkflow.runAsStep({ refreshCartShippingMethodsWorkflow.runAsStep({
input: { cart_id: cart.id }, input: { cart_id: input.cart_id },
}) })
updateTaxLinesWorkflow.runAsStep({ updateTaxLinesWorkflow.runAsStep({
input: { cart_id: cart.id }, input: { cart_id: input.cart_id },
}) })
const cartPromoCodes = transform( const cartPromoCodes = transform(
@@ -172,18 +169,16 @@ export const refreshCartItemsWorkflow = createWorkflow(
updateCartPromotionsWorkflow.runAsStep({ updateCartPromotionsWorkflow.runAsStep({
input: { input: {
cart_id: cart.id, cart_id: input.cart_id,
promo_codes: cartPromoCodes, promo_codes: cartPromoCodes,
action: PromotionActions.REPLACE, action: PromotionActions.REPLACE,
}, },
}) })
refreshPaymentCollectionForCartWorkflow.runAsStep({ refreshPaymentCollectionForCartWorkflow.runAsStep({
input: { cart_id: cart.id }, input: { cart: refetchedCart },
}) })
return new WorkflowResponse(refetchedCart, { return new WorkflowResponse(refetchedCart)
hooks: [validate],
})
} }
) )
@@ -19,7 +19,11 @@ export type RefreshPaymentCollectionForCartWorklowInput = {
/** /**
* The cart's ID. * The cart's ID.
*/ */
cart_id: string cart_id?: string
/**
* The Cart reference.
*/
cart?: any
} }
export const refreshPaymentCollectionForCartWorkflowId = export const refreshPaymentCollectionForCartWorkflowId =
@@ -52,23 +56,31 @@ export const refreshPaymentCollectionForCartWorkflowId =
export const refreshPaymentCollectionForCartWorkflow = createWorkflow( export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
refreshPaymentCollectionForCartWorkflowId, refreshPaymentCollectionForCartWorkflowId,
(input: WorkflowData<RefreshPaymentCollectionForCartWorklowInput>) => { (input: WorkflowData<RefreshPaymentCollectionForCartWorklowInput>) => {
const cart = useRemoteQueryStep({ const fetchCart = when({ input }, ({ input }) => {
entry_point: "cart", return !input.cart
fields: [ }).then(() => {
"id", return useRemoteQueryStep({
"region_id", entry_point: "cart",
"currency_code", fields: [
"total", "id",
"raw_total", "region_id",
"payment_collection.id", "currency_code",
"payment_collection.raw_amount", "total",
"payment_collection.amount", "raw_total",
"payment_collection.currency_code", "payment_collection.id",
"payment_collection.payment_sessions.id", "payment_collection.raw_amount",
], "payment_collection.amount",
variables: { id: input.cart_id }, "payment_collection.currency_code",
throw_if_key_not_found: true, "payment_collection.payment_sessions.id",
list: false, ],
variables: { id: input.cart_id },
throw_if_key_not_found: true,
list: false,
})
})
const cart = transform({ fetchCart, input }, ({ fetchCart, input }) => {
return input.cart ?? fetchCart
}) })
const validate = createHook("validate", { const validate = createHook("validate", {
@@ -17,7 +17,7 @@ export type TransferCartCustomerWorkflowInput = {
/** /**
* The cart's ID. * The cart's ID.
*/ */
id: string; id: string
/** /**
* The ID of the customer to transfer the cart to. * The ID of the customer to transfer the cart to.
*/ */
@@ -108,7 +108,7 @@ export const transferCartCustomerWorkflow = createWorkflow(
updateCartsStep(cartInput) updateCartsStep(cartInput)
refreshCartItemsWorkflow.runAsStep({ refreshCartItemsWorkflow.runAsStep({
input: { cart_id: input.id }, input: { cart_id: input.id, force_refresh: true },
}) })
} }
) )
@@ -27,8 +27,8 @@ import {
findSalesChannelStep, findSalesChannelStep,
updateCartsStep, updateCartsStep,
} from "../steps" } from "../steps"
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
import { validateSalesChannelStep } from "../steps/validate-sales-channel" import { validateSalesChannelStep } from "../steps/validate-sales-channel"
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
/** /**
* The data to update the cart, along with custom data that's passed to the workflow's hooks. * The data to update the cart, along with custom data that's passed to the workflow's hooks.
@@ -278,7 +278,11 @@ export const updateCartWorkflow = createWorkflow(
}) })
const cart = refreshCartItemsWorkflow.runAsStep({ const cart = refreshCartItemsWorkflow.runAsStep({
input: { cart_id: cartInput.id, promo_codes: input.promo_codes }, input: {
cart_id: cartInput.id,
promo_codes: input.promo_codes,
force_refresh: !!newRegion,
},
}) })
const cartUpdated = createHook("cartUpdated", { const cartUpdated = createHook("cartUpdated", {