chore(): Improve caching rollout (#13702)

* chore(): Improve caching rollout

* Create bright-cobras-complain.md

* chore(): Improve caching rollout

* downgrade orm to 6.4.3

* chore(): Improve caching rollout

* chore(): Improve caching rollout

* chore(): Improve caching rollout

* chore(): Improve caching rollout

* chore(): Improve caching rollout

* fix

* update changeset

* update modules definition

* update engine tests

* update engine tests

* improve integration

* improve integration

* gracefully disconnect

* update test

* another attempt

* another attempt

* fix workflow storage

* fix remote joiner

* fix remote joiner

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2025-10-08 17:44:00 +02:00
committed by GitHub
co-authored by Oli Juhl
parent e9b7a8c1f3
commit 0cbd9f0bc3
13 changed files with 248 additions and 182 deletions
@@ -1,9 +1,8 @@
import { MedusaContainer } from "@medusajs/framework/types"
import {
IRegionModuleService,
IStoreModuleService,
MedusaContainer,
} from "@medusajs/framework/types"
import { MedusaError, Modules, useCache } from "@medusajs/framework/utils"
ContainerRegistrationKeys,
MedusaError,
} from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
/**
@@ -17,45 +16,54 @@ export type FindOneOrAnyRegionStepInput = {
}
async function fetchRegionById(regionId: string, container: MedusaContainer) {
const service = container.resolve<IRegionModuleService>(Modules.REGION)
const query = container.resolve(ContainerRegistrationKeys.QUERY)
const args = [
regionId,
const { data } = await query.graph(
{
relations: ["countries"],
entity: "region",
filters: { id: regionId },
fields: ["*", "countries.*"],
},
] as Parameters<IRegionModuleService["retrieveRegion"]>
{
cache: { enable: true },
}
)
return await useCache(async () => service.retrieveRegion(...args), {
container,
key: args,
})
return data?.[0]
}
async function fetchDefaultStore(container: MedusaContainer) {
const storeModule = container.resolve<IStoreModuleService>(Modules.STORE)
const query = container.resolve(ContainerRegistrationKeys.QUERY)
return await useCache(async () => storeModule.listStores(), {
container,
key: "find-one-or-any-region-default-store",
})
const { data } = await query.graph(
{
entity: "store",
fields: ["*"],
},
{
cache: { enable: true },
}
)
return data?.[0]
}
async function fetchDefaultRegion(
defaultRegionId: string,
container: MedusaContainer
) {
const service = container.resolve<IRegionModuleService>(Modules.REGION)
const query = container.resolve(ContainerRegistrationKeys.QUERY)
const args = [
{ id: defaultRegionId },
{ relations: ["countries"] },
] as Parameters<IRegionModuleService["listRegions"]>
const { data } = await query.graph(
{
entity: "region",
filters: { id: defaultRegionId },
fields: ["*", "countries.*"],
},
{ cache: { enable: true } }
)
return await useCache(async () => service.listRegions(...args), {
container,
key: args,
})
return data?.[0]
}
export const findOneOrAnyRegionStepId = "find-one-or-any-region"
@@ -74,16 +82,13 @@ export const findOneOrAnyRegionStep = createStep(
}
}
const [store] = await fetchDefaultStore(container)
const store = await fetchDefaultStore(container)
if (!store) {
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Store not found")
}
const [region] = await fetchDefaultRegion(
store.default_region_id!,
container
)
const region = await fetchDefaultRegion(store.default_region_id!, container)
if (!region) {
return new StepResponse(null)
@@ -24,7 +24,6 @@ import {
createRemoteLinkStep,
emitEventStep,
useQueryGraphStep,
useRemoteQueryStep,
} from "../../common"
import { acquireLockStep } from "../../locking/steps/acquire-lock"
import { releaseLockStep } from "../../locking/steps/release-lock"
@@ -118,17 +117,19 @@ export const completeCartWorkflow = createWorkflow(
return orderCart?.data?.order_id
})
const cart = useRemoteQueryStep({
entry_point: "cart",
const cartData = useQueryGraphStep({
entity: "cart",
fields: completeCartFields,
variables: { id: input.id },
list: false,
filters: { id: input.id },
options: {
isList: false,
},
}).config({
name: "cart-query",
})
// this needs to be before the validation step
const paymentSessions = validateCartPaymentsStep({ cart })
const paymentSessions = validateCartPaymentsStep({ cart: cartData.data })
// purpose of this step is to run compensation if cart completion fails
// and tries to refund the payment if captured
compensatePaymentIfNeededStep({
@@ -137,48 +138,58 @@ export const completeCartWorkflow = createWorkflow(
const validate = createHook("validate", {
input,
cart,
cart: cartData.data,
})
// If order ID does not exist, we are completing the cart for the first time
const order = when("create-order", { orderId }, ({ orderId }) => {
return !orderId
}).then(() => {
const cartOptionIds = transform({ cart }, ({ cart }) => {
const cartOptionIds = transform({ cart: cartData.data }, ({ cart }) => {
return cart.shipping_methods?.map((sm) => sm.shipping_option_id)
})
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_option",
const shippingOptionsData = useQueryGraphStep({
entity: "shipping_option",
fields: ["id", "shipping_profile_id"],
variables: { id: cartOptionIds },
list: true,
filters: { id: cartOptionIds },
options: {
cache: {
enable: true,
},
},
}).config({
name: "shipping-options-query",
})
validateShippingStep({ cart, shippingOptions })
const { variants, sales_channel_id } = transform({ cart }, (data) => {
const variantsMap: Record<string, any> = {}
const allItems = data.cart?.items?.map((item) => {
variantsMap[item.variant_id] = item.variant
return {
id: item.id,
variant_id: item.variant_id,
quantity: item.quantity,
}
})
return {
variants: Object.values(variantsMap),
items: allItems,
sales_channel_id: data.cart.sales_channel_id,
}
validateShippingStep({
cart: cartData.data,
shippingOptions: shippingOptionsData.data,
})
const cartToOrder = transform({ cart }, ({ cart }) => {
const { variants, sales_channel_id } = transform(
{ cart: cartData.data },
(data) => {
const variantsMap: Record<string, any> = {}
const allItems = data.cart?.items?.map((item) => {
variantsMap[item.variant_id] = item.variant
return {
id: item.id,
variant_id: item.variant_id,
quantity: item.quantity,
}
})
return {
variants: Object.values(variantsMap),
items: allItems,
sales_channel_id: data.cart.sales_channel_id,
}
}
)
const cartToOrder = transform({ cart: cartData.data }, ({ cart }) => {
const allItems = (cart.items ?? []).map((item) => {
const input: PrepareLineItemDataInput = {
item,
@@ -290,15 +301,18 @@ export const completeCartWorkflow = createWorkflow(
prepareConfirmInventoryInput
)
const updateCompletedAt = transform({ cart }, ({ cart }) => {
return {
id: cart.id,
completed_at: new Date(),
const updateCompletedAt = transform(
{ cart: cartData.data },
({ cart }) => {
return {
id: cart.id,
completed_at: new Date(),
}
}
})
)
const promotionUsage = transform(
{ cart },
{ cart: cartData.data },
({ cart }: { cart: CartWorkflowDTO }) => {
const promotionUsage: UsageComputedActions[] = []
@@ -329,7 +343,7 @@ export const completeCartWorkflow = createWorkflow(
)
const linksToCreate = transform(
{ cart, createdOrder },
{ cart: cartData.data, createdOrder },
({ cart, createdOrder }) => {
const links: LinkDefinition[] = [
{
@@ -414,7 +428,7 @@ export const completeCartWorkflow = createWorkflow(
*/
createHook("orderCreated", {
order_id: createdOrder.id,
cart_id: cart.id,
cart_id: cartData.data.id,
})
return createdOrder
@@ -1,4 +1,8 @@
import { isDefined, ShippingOptionPriceType } from "@medusajs/framework/utils"
import {
isDefined,
QueryContext,
ShippingOptionPriceType,
} from "@medusajs/framework/utils"
import {
createHook,
createWorkflow,
@@ -11,6 +15,7 @@ import {
AdditionalData,
CalculateShippingOptionPriceDTO,
ListShippingOptionsForCartWithPricingWorkflowInput,
QueryContextType,
} from "@medusajs/framework/types"
import { useQueryGraphStep, validatePresenceOfStep } from "../../common"
@@ -206,14 +211,21 @@ export const listShippingOptionsForCartWithPricingWorkflow = createWorkflow(
setShippingOptionsContext.getResult()
const commonOptions = transform(
{ input, cart, fulfillmentSetIds, setShippingOptionsContextResult },
{
input,
cart,
fulfillmentSetIds,
optionIds,
setShippingOptionsContextResult,
},
({
input,
cart,
fulfillmentSetIds,
optionIds,
setShippingOptionsContextResult,
}) => ({
context: {
context: QueryContext({
...(setShippingOptionsContextResult
? setShippingOptionsContextResult
: {}),
@@ -223,9 +235,10 @@ export const listShippingOptionsForCartWithPricingWorkflow = createWorkflow(
: input.enabled_in_store
? "true"
: "false",
},
}),
filters: {
id: optionIds.length ? optionIds : undefined,
fulfillment_set_id: fulfillmentSetIds,
address: {
@@ -238,21 +251,19 @@ export const listShippingOptionsForCartWithPricingWorkflow = createWorkflow(
})
)
const typeQueryFilters = transform(
{ optionIds, commonOptions },
({ optionIds, commonOptions }) => ({
id: optionIds.length ? optionIds : undefined,
...commonOptions,
})
)
/**
* We need to prefetch exact same SO as in the final result but only to determine pricing calculations first.
*/
const initialOptions = useRemoteQueryStep({
entry_point: "shipping_options",
variables: typeQueryFilters,
const { data: initialOptions } = useQueryGraphStep({
entity: "shipping_options",
fields: ["id", "price_type"],
filters: commonOptions.filters,
context: commonOptions.context as QueryContextType,
options: {
cache: {
enable: true,
},
},
}).config({ name: "shipping-options-price-type-query" })
/**
@@ -261,7 +272,7 @@ export const listShippingOptionsForCartWithPricingWorkflow = createWorkflow(
const { flatRateOptionsQuery, calculatedShippingOptionsQuery } = transform(
{
cart,
initialOptions,
initialOptions: initialOptions,
commonOptions,
},
({ cart, initialOptions, commonOptions }) => {
@@ -83,12 +83,16 @@ export const transferCartCustomerWorkflow = createWorkflow(
entity: "customer",
filters: { id: input.customer_id },
fields: ["id", "email"],
options: { throwIfKeyNotFound: true },
options: {
throwIfKeyNotFound: true,
isList: false,
cache: { enable: true },
},
}).config({ name: "get-customer" })
const customer = transform(
{ customerQuery },
({ customerQuery }) => customerQuery.data[0]
({ customerQuery }) => customerQuery.data
)
// If its the same customer, we don't want the email to be overridden, so we skip the
@@ -72,11 +72,17 @@ export const listShippingOptionsForOrderWorkflow = createWorkflow(
entity: "sales_channels",
filters: { id: order.sales_channel_id },
fields: [
"id",
"stock_locations.fulfillment_sets.id",
"stock_locations.id",
"stock_locations.name",
"stock_locations.address.*",
],
options: {
cache: {
enable: true,
},
},
}).config({ name: "sales_channels-fulfillment-query" })
const scFulfillmentSets = transform(
@@ -70,10 +70,10 @@ export const maybeRefreshShippingMethodsWorkflowId =
/**
* This workflows refreshes shipping method prices of an order and its changes. It's used in Return Merchandise Authorization (RMA) flows. It's used
* by other workflows, such as {@link refreshExchangeShippingWorkflow}.
*
*
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around
* refreshing shipping methods in your custom flows.
*
*
* @example
* const { result } = await maybeRefreshShippingMethodsWorkflow(container)
* .run({
@@ -92,9 +92,9 @@ export const maybeRefreshShippingMethodsWorkflowId =
* }
* }
* })
*
*
* @summary
*
*
* Refreshes the shipping method prices of an order and its changes.
*/
export const maybeRefreshShippingMethodsWorkflow = createWorkflow(
@@ -116,6 +116,11 @@ export const maybeRefreshShippingMethodsWorkflow = createWorkflow(
entity: "shipping_option",
fields: [...COMMON_OPTIONS_FIELDS],
filters: { id: shippingMethod.shipping_option_id },
options: {
cache: {
enable: true,
},
},
}).config({ name: "calculated-option" })
const shippingOption = transform(shippingOptionQuery, ({ data }) => data[0])