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 Peretti
2025-09-30 18:19:06 +02:00
committed by GitHub
parent 5b135a41fe
commit b9d6f73320
117 changed files with 5741 additions and 530 deletions

View File

@@ -30,20 +30,38 @@ export async function ensurePublishableApiKeyMiddleware(
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
try {
const { data } = await query.graph({
entity: "api_key",
fields: ["id", "token", "sales_channels_link.sales_channel_id"],
filters: {
token: publishableApiKey,
type: ApiKeyType.PUBLISHABLE,
$or: [
{ revoked_at: { $eq: null } },
{ revoked_at: { $gt: new Date() } },
// Cache API key data and check revocation in memory
const { data } = await query.graph(
{
entity: "api_key",
fields: [
"id",
"token",
"revoked_at",
"sales_channels_link.sales_channel_id",
],
filters: {
token: publishableApiKey,
type: ApiKeyType.PUBLISHABLE,
},
},
})
{
cache: {
enable: true,
},
}
)
apiKey = data[0]
if (data.length) {
const now = new Date()
const cachedApiKey = data[0]
const isRevoked =
!!cachedApiKey.revoked_at && new Date(cachedApiKey.revoked_at) <= now
if (!isRevoked) {
apiKey = cachedApiKey
}
}
} catch (e) {
return next(e)
}

View File

@@ -1,49 +1,88 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
isString,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest } from "../types"
import type {
GraphResultSet,
MedusaContainer,
RemoteJoinerOptions,
RemoteQueryEntryPoints,
RemoteQueryFunctionReturnPagination,
} from "../../types"
import { ContainerRegistrationKeys, isString } from "../../utils"
import type { MedusaRequest } from "../types"
export const refetchEntities = async (
entryPoint: string,
idOrFilter: string | object,
scope: MedusaContainer,
fields: string[],
pagination?: MedusaRequest["queryConfig"]["pagination"],
export const refetchEntities = async <TEntry extends string>({
entity,
idOrFilter,
scope,
fields,
pagination,
withDeleted,
options,
}: {
entity: TEntry
idOrFilter?: string | object
scope: MedusaContainer
fields?: string[]
pagination?: MedusaRequest["queryConfig"]["pagination"]
withDeleted?: boolean
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const filters = isString(idOrFilter) ? { id: idOrFilter } : idOrFilter
let context: object = {}
options?: RemoteJoinerOptions
}): Promise<
Omit<GraphResultSet<TEntry>, "metadata"> & {
metadata: RemoteQueryFunctionReturnPagination
}
> => {
const query = scope.resolve(ContainerRegistrationKeys.QUERY)
let filters = isString(idOrFilter) ? { id: idOrFilter } : idOrFilter
let context!: Record<string, unknown>
if ("context" in filters) {
if (filters.context) {
context = filters.context!
if (filters && "context" in filters) {
const { context: context_, ...rest } = filters
if (context_) {
context = context_! as Record<string, unknown>
}
delete filters.context
filters = rest
}
const variables = { filters, ...context, ...pagination, withDeleted }
const graphOptions: Parameters<typeof query.graph>[0] = {
entity,
fields: fields ?? [],
filters,
pagination,
withDeleted,
context: context,
}
const queryObject = remoteQueryObjectFromString({
entryPoint,
variables,
const result = await query.graph(graphOptions, options)
return {
data: result.data as TEntry extends keyof RemoteQueryEntryPoints
? RemoteQueryEntryPoints[TEntry][]
: any[],
metadata: result.metadata ?? ({} as RemoteQueryFunctionReturnPagination),
}
}
export const refetchEntity = async <TEntry extends string>({
entity,
idOrFilter,
scope,
fields,
options,
}: {
entity: TEntry & string
idOrFilter: string | object
scope: MedusaContainer
fields: string[]
options?: RemoteJoinerOptions
}): Promise<
TEntry extends keyof RemoteQueryEntryPoints
? RemoteQueryEntryPoints[TEntry]
: any
> => {
const { data } = await refetchEntities<TEntry>({
entity,
idOrFilter,
scope,
fields,
options,
})
return await remoteQuery(queryObject)
}
export const refetchEntity = async (
entryPoint: string,
idOrFilter: string | object,
scope: MedusaContainer,
fields: string[]
) => {
const [entity] = await refetchEntities(entryPoint, idOrFilter, scope, fields)
return entity
return Array.isArray(data) ? data[0] : data
}

View File

@@ -5,6 +5,7 @@ import {
IApiKeyModuleService,
IAuthModuleService,
ICacheService,
ICachingModuleService,
ICartModuleService,
ICurrencyModuleService,
ICustomerModuleService,
@@ -76,6 +77,7 @@ declare module "@medusajs/types" {
[Modules.NOTIFICATION]: INotificationModuleService
[Modules.LOCKING]: ILockingModule
[Modules.SETTINGS]: ISettingsModuleService
[Modules.CACHING]: ICachingModuleService
}
}