Feat(): distributed caching (#13435)

RESOLVES CORE-1153

**What**
- This pr mainly lay the foundation the caching layer. It comes with a modules (built in memory cache) and a redis provider.
- Apply caching to few touch point to test

Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
Adrien de PerettiandCarlos R. L. Rodrigues authored and GitHub committed 2025-09-30 16:19:06 +00:00
1 parent 5b135a41fe
commit b9d6f73320
117 files changed
+5741 -530

No files matched your search

+2
View File
@@ -80,6 +80,8 @@
"@medusajs/auth-google": "2.10.3",
"@medusajs/cache-inmemory": "2.10.3",
"@medusajs/cache-redis": "2.10.3",
"@medusajs/caching": "2.10.3",
"@medusajs/caching-redis": "2.10.3",
"@medusajs/cart": "2.10.3",
"@medusajs/core-flows": "2.10.3",
"@medusajs/currency": "2.10.3",
@@ -67,10 +67,12 @@ export const DELETE = async (
) => {
const { id, action_id } = req.params
const claim = await refetchEntity("order_claim", id, req.scope, [
"id",
"return_id",
])
const claim = await refetchEntity({
entity: "order_claim",
idOrFilter: id,
scope: req.scope,
fields: ["id", "return_id"],
})
const { result: orderPreview } = await removeItemReturnActionWorkflow(
req.scope
@@ -81,15 +83,15 @@ export const DELETE = async (
},
})
const orderReturn = await refetchEntity(
"return",
{
const orderReturn = await refetchEntity({
entity: "return",
idOrFilter: {
...req.filterableFields,
id,
},
req.scope,
defaultAdminDetailsReturnFields
)
scope: req.scope,
fields: defaultAdminDetailsReturnFields,
})
res.json({
order_preview: orderPreview as unknown as HttpTypes.AdminOrderPreview,
@@ -10,12 +10,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<AdminClaimResponse>
) => {
const claim = await refetchEntity(
"order_claim",
req.params.id,
req.scope,
req.queryConfig.fields
)
const claim = await refetchEntity({
entity: "order_claim",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
if (!claim) {
throw new MedusaError(
@@ -69,9 +69,12 @@ export const DELETE = async (
const { id, action_id } = req.params
const exchange = await refetchEntity("order_exchange", id, req.scope, [
"return_id",
])
const exchange = await refetchEntity({
entity: "order_exchange",
idOrFilter: id,
scope: req.scope,
fields: ["return_id"],
})
const { result: orderPreview } = await removeItemReturnActionWorkflow(
req.scope
@@ -10,12 +10,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<AdminExchangeResponse>
) => {
const exchange = await refetchEntity(
"order_exchange",
req.params.id,
req.scope,
req.queryConfig.fields
)
const exchange = await refetchEntity({
entity: "order_exchange",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
if (!exchange) {
throw new MedusaError(
@@ -10,11 +10,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetNotificationParamsType>,
res: MedusaResponse<HttpTypes.AdminNotificationResponse>
) => {
const notification = await refetchEntity(
"notification",
req.params.id,
req.scope,
req.queryConfig.fields
)
const notification = await refetchEntity({
entity: "notification",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ notification })
}
@@ -9,13 +9,13 @@ export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminNotificationListParams>,
res: MedusaResponse<HttpTypes.AdminNotificationListResponse>
) => {
const { rows: notifications, metadata } = await refetchEntities(
"notification",
req.filterableFields,
req.scope,
req.queryConfig.fields,
req.queryConfig.pagination
)
const { data: notifications, metadata } = await refetchEntities({
entity: "notification",
idOrFilter: req.filterableFields,
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.json({
notifications,
@@ -16,12 +16,12 @@ export const POST = async (
input: { orderId, fulfillmentId },
})
const order = await refetchEntity(
"order",
orderId,
req.scope,
req.queryConfig.fields
)
const order = await refetchEntity({
entity: "order",
idOrFilter: orderId,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ order })
}
@@ -21,12 +21,12 @@ export const POST = async (
},
})
const paymentCollection = await refetchEntity(
"payment_collection",
id,
req.scope,
req.queryConfig.fields
)
const paymentCollection = await refetchEntity({
entity: "payment_collection",
idOrFilter: id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ payment_collection: paymentCollection })
}
@@ -15,12 +15,12 @@ export const POST = async (
input: req.body,
})
const paymentCollection = await refetchEntity(
"payment_collection",
result[0].id,
req.scope,
req.queryConfig.fields
)
const paymentCollection = await refetchEntity({
entity: "payment_collection",
idOrFilter: result[0].id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ payment_collection: paymentCollection })
}
@@ -14,12 +14,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<HttpTypes.AdminPricePreferenceResponse>
) => {
const price_preference = await refetchEntity(
"price_preference",
req.params.id,
req.scope,
req.queryConfig.fields
)
const price_preference = await refetchEntity({
entity: "price_preference",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ price_preference })
}
@@ -35,12 +35,12 @@ export const POST = async (
input: { selector: { id: [id] }, update: req.body },
})
const price_preference = await refetchEntity(
"price_preference",
id,
req.scope,
req.queryConfig.fields
)
const price_preference = await refetchEntity({
entity: "price_preference",
idOrFilter: id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ price_preference })
}
@@ -11,13 +11,14 @@ export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminPricePreferenceListParams>,
res: MedusaResponse<HttpTypes.AdminPricePreferenceListResponse>
) => {
const { rows: price_preferences, metadata } = await refetchEntities(
"price_preference",
req.filterableFields,
req.scope,
req.queryConfig.fields,
req.queryConfig.pagination
)
const { data: price_preferences, metadata } = await refetchEntities({
entity: "price_preference",
idOrFilter: req.filterableFields,
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.json({
price_preferences: price_preferences,
count: metadata.count,
@@ -35,12 +36,12 @@ export const POST = async (
input: [req.validatedBody],
})
const price_preference = await refetchEntity(
"price_preference",
result[0].id,
req.scope,
req.queryConfig.fields
)
const price_preference = await refetchEntity({
entity: "price_preference",
idOrFilter: result[0].id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ price_preference })
}
@@ -19,12 +19,12 @@ export const POST = async (
input: { id, ...req.validatedBody },
})
const category = await refetchEntity(
"product_category",
id,
req.scope,
req.queryConfig.fields
)
const category = await refetchEntity({
entity: "product_category",
idOrFilter: id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ product_category: category })
}
@@ -21,12 +21,15 @@ export const GET = async (
req: AuthenticatedMedusaRequest<AdminProductCategoryParamsType>,
res: MedusaResponse<AdminProductCategoryResponse>
) => {
const [category] = await refetchEntities(
"product_category",
{ id: req.params.id, ...req.filterableFields },
req.scope,
req.queryConfig.fields
)
const {
data: [category],
} = await refetchEntities({
entity: "product_category",
idOrFilter: { id: req.params.id, ...req.filterableFields },
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
if (!category) {
throw new MedusaError(
@@ -48,12 +51,15 @@ export const POST = async (
input: { selector: { id }, update: req.validatedBody },
})
const [category] = await refetchEntities(
"product_category",
{ id, ...req.filterableFields },
req.scope,
req.queryConfig.fields
)
const {
data: [category],
} = await refetchEntities({
entity: "product_category",
idOrFilter: { id, ...req.filterableFields },
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.status(200).json({ product_category: category })
}
@@ -10,13 +10,13 @@ export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminProductCategoryListParams>,
res: MedusaResponse<HttpTypes.AdminProductCategoryListResponse>
) => {
const { rows: product_categories, metadata } = await refetchEntities(
"product_category",
req.filterableFields,
req.scope,
req.queryConfig.fields,
req.queryConfig.pagination
)
const { data: product_categories, metadata } = await refetchEntities({
entity: "product_category",
idOrFilter: req.filterableFields,
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.json({
product_categories,
@@ -34,12 +34,15 @@ export const POST = async (
input: { product_categories: [req.validatedBody] },
})
const [category] = await refetchEntities(
"product_category",
{ id: result[0].id, ...req.filterableFields },
req.scope,
req.queryConfig.fields
)
const {
data: [category],
} = await refetchEntities({
entity: "product_category",
idOrFilter: { id: result[0].id, ...req.filterableFields },
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.status(200).json({ product_category: category })
}
@@ -19,12 +19,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetProductTagParamsType>,
res: MedusaResponse<HttpTypes.AdminProductTagResponse>
) => {
const productTag = await refetchEntity(
"product_tag",
req.params.id,
req.scope,
req.queryConfig.fields
)
const productTag = await refetchEntity({
entity: "product_tag",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ product_tag: productTag })
}
@@ -33,12 +33,12 @@ export const POST = async (
req: AuthenticatedMedusaRequest<AdminUpdateProductTagType>,
res: MedusaResponse<HttpTypes.AdminProductTagResponse>
) => {
const existingProductTag = await refetchEntity(
"product_tag",
req.params.id,
req.scope,
["id"]
)
const existingProductTag = await refetchEntity({
entity: "product_tag",
idOrFilter: req.params.id,
scope: req.scope,
fields: ["id"],
})
if (!existingProductTag) {
throw new MedusaError(
@@ -54,12 +54,12 @@ export const POST = async (
},
})
const productTag = await refetchEntity(
"product_tag",
result[0].id,
req.scope,
req.queryConfig.fields
)
const productTag = await refetchEntity({
entity: "product_tag",
idOrFilter: result[0].id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ product_tag: productTag })
}
@@ -12,13 +12,13 @@ export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminProductTagListParams>,
res: MedusaResponse<HttpTypes.AdminProductTagListResponse>
) => {
const { rows: product_tags, metadata } = await refetchEntities(
"product_tag",
req.filterableFields,
req.scope,
req.queryConfig.fields,
req.queryConfig.pagination
)
const { data: product_tags, metadata } = await refetchEntities({
entity: "product_tag",
idOrFilter: req.filterableFields,
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.json({
product_tags: product_tags,
@@ -38,12 +38,12 @@ export const POST = async (
input: { product_tags: input },
})
const productTag = await refetchEntity(
"product_tag",
result[0].id,
req.scope,
req.queryConfig.fields
)
const productTag = await refetchEntity({
entity: "product_tag",
idOrFilter: result[0].id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ product_tag: productTag })
}
@@ -21,13 +21,13 @@ export const GET = async (
)
}
const { rows: variants, metadata } = await refetchEntities(
"variant",
{ ...req.filterableFields },
req.scope,
remapKeysForVariant(req.queryConfig.fields ?? []),
req.queryConfig.pagination
)
const { data: variants, metadata } = await refetchEntities({
entity: "variant",
idOrFilter: { ...req.filterableFields },
scope: req.scope,
fields: remapKeysForVariant(req.queryConfig.fields ?? []),
pagination: req.queryConfig.pagination,
})
if (withInventoryQuantity) {
await wrapVariantsWithTotalInventoryQuantity(req, variants || [])
@@ -17,12 +17,12 @@ export const GET = async (
) => {
const productId = req.params.id
const optionId = req.params.option_id
const productOption = await refetchEntity(
"product_option",
{ id: optionId, product_id: productId },
req.scope,
req.queryConfig.fields
)
const productOption = await refetchEntity({
entity: "product_option",
idOrFilter: { id: optionId, product_id: productId },
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ product_option: productOption })
}
@@ -45,12 +45,12 @@ export const POST = async (
},
})
const product = await refetchEntity(
"product",
productId,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: productId,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -67,12 +67,12 @@ export const DELETE = async (
input: { ids: [optionId] /* product_id: productId */ },
})
const product = await refetchEntity(
"product",
productId,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: productId,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({
id: optionId,
@@ -14,13 +14,13 @@ export const GET = async (
res: MedusaResponse<HttpTypes.AdminProductOptionListResponse>
) => {
const productId = req.params.id
const { rows: product_options, metadata } = await refetchEntities(
"product_option",
{ ...req.filterableFields, product_id: productId },
req.scope,
req.queryConfig.fields,
req.queryConfig.pagination
)
const { data: product_options, metadata } = await refetchEntities({
entity: "product_option",
idOrFilter: { ...req.filterableFields, product_id: productId },
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.json({
product_options,
@@ -51,11 +51,11 @@ export const POST = async (
},
})
const product = await refetchEntity(
"product",
productId,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: productId,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -16,12 +16,12 @@ export const GET = async (
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const selectFields = remapKeysForProduct(req.queryConfig.fields ?? [])
const product = await refetchEntity(
"product",
req.params.id,
req.scope,
selectFields
)
const product = await refetchEntity({
entity: "product",
idOrFilter: req.params.id,
scope: req.scope,
fields: selectFields,
})
if (!product) {
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Product not found")
@@ -38,12 +38,12 @@ export const POST = async (
) => {
const { additional_data, ...update } = req.validatedBody
const existingProduct = await refetchEntity(
"product",
req.params.id,
req.scope,
["id"]
)
const existingProduct = await refetchEntity({
entity: "product",
idOrFilter: req.params.id,
scope: req.scope,
fields: ["id"],
})
/**
* Check if the product exists with the id or not before calling the workflow.
*/
@@ -62,12 +62,12 @@ export const POST = async (
},
})
const product = await refetchEntity(
"product",
result[0].id,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: result[0].id,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -24,12 +24,12 @@ export const GET = async (
const variantId = req.params.variant_id
const variables = { id: variantId, product_id: productId }
const variant = await refetchEntity(
"variant",
variables,
req.scope,
remapKeysForVariant(req.queryConfig.fields ?? [])
)
const variant = await refetchEntity({
entity: "variant",
idOrFilter: variables,
scope: req.scope,
fields: remapKeysForVariant(req.queryConfig.fields ?? []),
})
res.status(200).json({ variant: remapVariantResponse(variant) })
}
@@ -52,12 +52,12 @@ export const POST = async (
},
})
const product = await refetchEntity(
"product",
productId,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: productId,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -74,12 +74,12 @@ export const DELETE = async (
input: { ids: [variantId] /* product_id: productId */ },
})
const product = await refetchEntity(
"product",
productId,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: productId,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({
id: variantId,
@@ -29,13 +29,13 @@ export const GET = async (
)
}
const { rows: variants, metadata } = await refetchEntities(
"variant",
{ ...req.filterableFields, product_id: productId },
req.scope,
remapKeysForVariant(req.queryConfig.fields ?? []),
req.queryConfig.pagination
)
const { data: variants, metadata } = await refetchEntities({
entity: "variant",
idOrFilter: { ...req.filterableFields, product_id: productId },
scope: req.scope,
fields: remapKeysForVariant(req.queryConfig.fields ?? []),
pagination: req.queryConfig.pagination,
})
if (withInventoryQuantity) {
await wrapVariantsWithTotalInventoryQuantity(req, variants || [])
@@ -69,12 +69,12 @@ export const POST = async (
input: { product_variants: input, additional_data },
})
const product = await refetchEntity(
"product",
productId,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: productId,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({ product: remapProductResponse(product) })
}
+14 -14
View File
@@ -41,14 +41,14 @@ async function getProducts(
) {
const selectFields = remapKeysForProduct(req.queryConfig.fields ?? [])
const { rows: products, metadata } = await refetchEntities(
"product",
req.filterableFields,
req.scope,
selectFields,
req.queryConfig.pagination,
req.queryConfig.withDeleted
)
const { data: products, metadata } = await refetchEntities({
entity: "product",
idOrFilter: req.filterableFields,
scope: req.scope,
fields: selectFields,
pagination: req.queryConfig.pagination,
withDeleted: req.queryConfig.withDeleted,
})
res.json({
products: products.map(remapProductResponse),
@@ -103,12 +103,12 @@ export const POST = async (
input: { products: [products], additional_data },
})
const product = await refetchEntity(
"product",
result[0].id,
req.scope,
remapKeysForProduct(req.queryConfig.fields ?? [])
)
const product = await refetchEntity({
entity: "product",
idOrFilter: result[0].id,
scope: req.scope,
fields: remapKeysForProduct(req.queryConfig.fields ?? []),
})
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -14,12 +14,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<RefundReasonResponse>
) => {
const refund_reason = await refetchEntity(
"refund_reason",
req.params.id,
req.scope,
req.queryConfig.fields
)
const refund_reason = await refetchEntity({
entity: "refund_reason",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.json({ refund_reason })
}
@@ -39,12 +39,12 @@ export const POST = async (
],
})
const refund_reason = await refetchEntity(
"refund_reason",
req.params.id,
req.scope,
req.queryConfig.fields
)
const refund_reason = await refetchEntity({
entity: "refund_reason",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.json({ refund_reason })
}
@@ -17,13 +17,13 @@ export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.RefundReasonFilters>,
res: MedusaResponse<PaginatedResponse<RefundReasonsResponse>>
) => {
const { rows: refund_reasons, metadata } = await refetchEntities(
"refund_reasons",
req.filterableFields,
req.scope,
req.queryConfig.fields,
req.queryConfig.pagination
)
const { data: refund_reasons, metadata } = await refetchEntities({
entity: "refund_reasons",
idOrFilter: req.filterableFields,
scope: req.scope,
fields: req.queryConfig.fields,
pagination: req.queryConfig.pagination,
})
res.json({
refund_reasons,
@@ -43,12 +43,12 @@ export const POST = async (
input: { data: [req.validatedBody] },
})
const refund_reason = await refetchEntity(
"refund_reason",
refundReason.id,
req.scope,
req.queryConfig.fields
)
const refund_reason = await refetchEntity({
entity: "refund_reason",
idOrFilter: refundReason.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
res.status(200).json({ refund_reason })
}
@@ -18,12 +18,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<AdminReturnReasonResponse>
) => {
const return_reason = await refetchEntity(
"return_reason",
req.params.id,
req.scope,
req.queryConfig.fields
)
const return_reason = await refetchEntity({
entity: "return_reason",
idOrFilter: req.params.id,
scope: req.scope,
fields: req.queryConfig.fields,
})
if (!return_reason) {
throw new MedusaError(
@@ -6,5 +6,5 @@ export const refetchOrder = async (
scope: MedusaContainer,
fields: string[]
) => {
return await refetchEntity("order", idOrFilter, scope, fields)
return await refetchEntity({ entity: "order", idOrFilter, scope, fields })
}
@@ -9,5 +9,10 @@ export const refetchPaymentCollection = async (
scope: MedusaContainer,
fields: string[]
): Promise<PaymentCollectionDTO> => {
return refetchEntity("payment_collection", id, scope, fields)
return refetchEntity({
entity: "payment_collection",
idOrFilter: id,
scope,
fields,
})
}
@@ -11,12 +11,12 @@ export const GET = async (
req: AuthenticatedMedusaRequest<StoreProductCategoryParamsType>,
res: MedusaResponse<StoreProductCategoryResponse>
) => {
const category = await refetchEntity(
"product_category",
{ id: req.params.id, ...req.filterableFields },
req.scope,
req.queryConfig.fields
)
const category = await refetchEntity({
entity: "product_category",
idOrFilter: { id: req.params.id, ...req.filterableFields },
scope: req.scope,
fields: req.queryConfig.fields,
})
if (!category) {
throw new MedusaError(
@@ -1,5 +1,6 @@
import { isPresent, MedusaError } from "@medusajs/framework/utils"
import { MedusaResponse } from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
import { isPresent, MedusaError, QueryContext } from "@medusajs/framework/utils"
import { wrapVariantsWithInventoryQuantityForSalesChannel } from "../../../utils/middlewares"
import {
filterOutInternalProductCategories,
@@ -7,7 +8,6 @@ import {
RequestWithContext,
wrapProductsWithTaxPrices,
} from "../helpers"
import { HttpTypes } from "@medusajs/framework/types"
export const GET = async (
req: RequestWithContext<HttpTypes.StoreProductParams>,
@@ -29,9 +29,11 @@ export const GET = async (
}
if (isPresent(req.pricingContext)) {
filters["context"] = {
"variants.calculated_price": { context: req.pricingContext },
}
filters["context"] ??= {}
filters["context"]["variants"] ??= {}
filters["context"]["variants"]["calculated_price"] ??= QueryContext(
req.pricingContext!
)
}
const includesCategoriesField = req.queryConfig.fields.some((field) =>
@@ -25,7 +25,7 @@ export const refetchProduct = async (
scope: MedusaContainer,
fields: string[]
) => {
return await refetchEntity("product", idOrFilter, scope, fields)
return await refetchEntity({ entity: "product", idOrFilter, scope, fields })
}
export const filterOutInternalProductCategories = (
+37 -25
View File
@@ -5,7 +5,6 @@ import {
FeatureFlag,
isPresent,
QueryContext,
remoteQueryObjectFromString,
} from "@medusajs/framework/utils"
import IndexEngineFeatureFlag from "../../../feature-flags/index-engine"
import { wrapVariantsWithInventoryQuantityForSalesChannel } from "../../utils/middlewares"
@@ -49,7 +48,9 @@ async function getProductsWithIndexEngine(
if (isPresent(req.pricingContext)) {
context["variants"] ??= {}
context["variants"]["calculated_price"] = QueryContext(req.pricingContext!)
context["variants"]["calculated_price"] ??= QueryContext(
req.pricingContext!
)
}
const filters: Record<string, any> = req.filterableFields
@@ -62,13 +63,20 @@ async function getProductsWithIndexEngine(
delete filters.sales_channel_id
}
const { data: products = [], metadata } = await query.index({
entity: "product",
fields: req.queryConfig.fields,
filters,
pagination: req.queryConfig.pagination,
context,
})
const { data: products = [], metadata } = await query.index(
{
entity: "product",
fields: req.queryConfig.fields,
filters,
pagination: req.queryConfig.pagination,
context,
},
{
cache: {
enable: true,
},
}
)
if (withInventoryQuantity) {
await wrapVariantsWithInventoryQuantityForSalesChannel(
@@ -91,7 +99,7 @@ async function getProducts(
req: RequestWithContext<HttpTypes.StoreProductListParams>,
res: MedusaResponse<HttpTypes.StoreProductListResponse>
) {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
const context: object = {}
const withInventoryQuantity = req.queryConfig.fields.some((field) =>
field.includes("variants.inventory_quantity")
@@ -104,22 +112,26 @@ async function getProducts(
}
if (isPresent(req.pricingContext)) {
context["variants.calculated_price"] = {
context: req.pricingContext,
}
context["variants"] ??= {}
context["variants"]["calculated_price"] ??= QueryContext(
req.pricingContext!
)
}
const queryObject = remoteQueryObjectFromString({
entryPoint: "product",
variables: {
const { data: products = [], metadata } = await query.graph(
{
entity: "product",
fields: req.queryConfig.fields,
filters: req.filterableFields,
...req.queryConfig.pagination,
...context,
pagination: req.queryConfig.pagination,
context,
},
fields: req.queryConfig.fields,
})
const { rows: products, metadata } = await remoteQuery(queryObject)
{
cache: {
enable: true,
},
}
)
if (withInventoryQuantity) {
await wrapVariantsWithInventoryQuantityForSalesChannel(
@@ -131,8 +143,8 @@ async function getProducts(
await wrapProductsWithTaxPrices(req, products)
res.json({
products,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
count: metadata!.count,
offset: metadata!.skip,
limit: metadata!.take,
})
}
@@ -39,12 +39,12 @@ export function normalizeDataForContext() {
// If the cart is passed, get the information from it
if (req.filterableFields.cart_id) {
const cart = await refetchEntity(
"cart",
req.filterableFields.cart_id,
req.scope,
["region_id", "shipping_address.*"]
)
const cart = await refetchEntity({
entity: "cart",
idOrFilter: req.filterableFields.cart_id,
scope: req.scope,
fields: ["region_id", "shipping_address.*"],
})
if (cart?.region_id) {
regionId = cart.region_id
@@ -58,9 +58,16 @@ export function normalizeDataForContext() {
// Finally, try to get it from the store defaults if not available
if (!regionId) {
const stores = await refetchEntities("store", {}, req.scope, [
"default_region_id",
])
const stores = await refetchEntities({
entity: "store",
scope: req.scope,
fields: ["id", "default_region_id"],
options: {
cache: {
enable: true,
},
},
})
regionId = stores[0]?.default_region_id
}
@@ -17,12 +17,17 @@ export function setPricingContext() {
}
// We validate the region ID in the previous middleware
const region = await refetchEntity(
"region",
req.filterableFields.region_id!,
req.scope,
["id", "currency_code"]
)
const region = await refetchEntity({
entity: "region",
idOrFilter: req.filterableFields.region_id!,
scope: req.scope,
fields: ["id", "currency_code"],
options: {
cache: {
enable: true,
},
},
})
if (!region) {
try {
@@ -42,12 +47,12 @@ export function setPricingContext() {
// Find all the customer groups the customer is a part of and set
if (req.auth_context?.actor_id) {
const customerGroups = await refetchEntities(
"customer_group",
{ customers: { id: req.auth_context.actor_id } },
req.scope,
["id"]
)
const { data: customerGroups } = await refetchEntities({
entity: "customer_group",
idOrFilter: { customers: { id: req.auth_context.actor_id } },
scope: req.scope,
fields: ["id"],
})
pricingContext.customer = { groups: [] }
customerGroups.map((cg) =>
@@ -38,12 +38,12 @@ export function setTaxContext() {
}
const getTaxInclusivityInfo = async (req: MedusaRequest) => {
const region = await refetchEntity(
"region",
req.filterableFields.region_id as string,
req.scope,
["automatic_taxes"]
)
const region = await refetchEntity({
entity: "region",
idOrFilter: req.filterableFields.region_id as string,
scope: req.scope,
fields: ["automatic_taxes"],
})
if (!region) {
throw new MedusaError(
@@ -0,0 +1,10 @@
import { FlagSettings } from "@medusajs/framework/feature-flags"
const CachingFeatureFlag: FlagSettings = {
key: "caching",
default_val: false,
env_key: "MEDUSA_FF_CACHING",
description: "[WIP] Enable core caching where applicable",
}
export default CachingFeatureFlag
+116 -4
View File
@@ -1,4 +1,3 @@
import { snakeCase } from "lodash"
import {
MedusaNextFunction,
MedusaRequest,
@@ -6,10 +5,15 @@ import {
Query,
} from "@medusajs/framework"
import { ApiLoader } from "@medusajs/framework/http"
import { Tracer } from "@medusajs/framework/telemetry"
import type { SpanExporter } from "@opentelemetry/sdk-trace-node"
import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node"
import { TransactionOrchestrator } from "@medusajs/framework/orchestration"
import { Tracer } from "@medusajs/framework/telemetry"
import { FeatureFlag } from "@medusajs/framework/utils"
import { SpanStatusCode } from "@opentelemetry/api"
import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node"
import type { SpanExporter } from "@opentelemetry/sdk-trace-node"
import { snakeCase } from "lodash"
import CacheModule from "../modules/caching"
import { ICachingModuleService } from "@medusajs/framework/types"
const EXCLUDED_RESOURCES = [".vite", "virtual:"]
@@ -261,6 +265,110 @@ export function instrumentWorkflows() {
}
}
export function instrumentCache() {
if (!FeatureFlag.isFeatureEnabled("caching")) {
return
}
const CacheTracer = new Tracer("@medusajs/caching", "2.0.0")
const cacheModule_ = CacheModule as unknown as {
service: ICachingModuleService & {
traceGet: (
cacheGetFn: () => Promise<any>,
key: string,
tags: string[]
) => Promise<any>
traceSet: (
cacheSetFn: () => Promise<any>,
key: string,
tags: string[],
options: { autoInvalidate?: boolean }
) => Promise<any>
traceClear: (
cacheClearFn: () => Promise<any>,
key: string,
tags: string[],
options: { autoInvalidate?: boolean }
) => Promise<any>
}
}
cacheModule_.service.traceGet = async function (cacheGetFn, key, tags) {
return await CacheTracer.trace(`cache.get`, async (span) => {
span.setAttributes({
"cache.key": key,
"cache.tags": tags,
})
try {
return await cacheGetFn()
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
})
throw error
} finally {
span.end()
}
})
}
cacheModule_.service.traceSet = async function (
cacheSetFn,
key,
tags,
options = {}
) {
return await CacheTracer.trace(`cache.set`, async (span) => {
span.setAttributes({
"cache.key": key,
"cache.tags": tags,
"cache.options": JSON.stringify(options),
})
try {
return await cacheSetFn()
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
})
throw error
} finally {
span.end()
}
})
}
cacheModule_.service.traceClear = async function (
cacheClearFn,
key,
tags,
options = {}
) {
return await CacheTracer.trace(`cache.clear`, async (span) => {
span.setAttributes({
"cache.key": key,
"cache.tags": tags,
"cache.options": JSON.stringify(options),
})
try {
return await cacheClearFn()
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
})
throw error
} finally {
span.end()
}
})
}
}
/**
* A helper function to configure the OpenTelemetry SDK with some defaults.
* For better/more control, please configure the SDK manually.
@@ -283,6 +391,7 @@ export function registerOtel(
query: boolean
workflows: boolean
db: boolean
cache: boolean
}>
}
) {
@@ -318,6 +427,9 @@ export function registerOtel(
if (instrument.workflows) {
instrumentWorkflows()
}
if (instrument.cache) {
instrumentCache()
}
const sdk = new NodeSDK({
serviceName,
@@ -0,0 +1,6 @@
import RedisCachingProvider from "@medusajs/caching-redis"
export * from "@medusajs/caching-redis"
export default RedisCachingProvider
export const discoveryPath = require.resolve("@medusajs/caching-redis")
+6
View File
@@ -0,0 +1,6 @@
import CacheModule from "@medusajs/caching"
export * from "@medusajs/caching"
export default CacheModule
export const discoveryPath = require.resolve("@medusajs/caching")