diff --git a/www/apps/book/app/learn/configurations/medusa-config/page.mdx b/www/apps/book/app/learn/configurations/medusa-config/page.mdx index dcad539735..5798b9b2fc 100644 --- a/www/apps/book/app/learn/configurations/medusa-config/page.mdx +++ b/www/apps/book/app/learn/configurations/medusa-config/page.mdx @@ -701,7 +701,7 @@ The value of this configuration is prepended to `sess:`. For example, if you set -This configuration is not used for modules that also connect to Redis, such as the [Redis Cache Module](!resources!/infrastructure-modules/cache/redis). +This configuration is not used for modules that also connect to Redis, such as the [Redis Caching Module Provider](!resources!/infrastructure-modules/caching/providers/redis). @@ -719,9 +719,9 @@ module.exports = defineConfig({ ### redisUrl -The `projectConfig.redisUrl` configuration specifies the connection URL to Redis to store the Medusa server session. When specified, the Medusa server uses Redis to store the session data. Otherwie, the session data is stored in-memory. +The `projectConfig.redisUrl` configuration specifies the connection URL to Redis to store the Medusa server session. When specified, the Medusa server uses Redis to store the session data. Otherwise, the session data is stored in-memory. -This configuration is not used for modules that also connect to Redis, such as the [Redis Cache Module](!resources!/infrastructure-modules/cache/redis). You'll have to configure the Redis connection for those modules separately. +This configuration is not used for modules that also connect to Redis, such as the [Redis Caching Module Provider](!resources!/infrastructure-modules/caching/providers/redis). You'll have to configure the Redis connection for those modules separately. @@ -764,7 +764,7 @@ The `projectConfig.sessionOptions` configuration defines additional options to p -This configuration is not used for modules that also connect to Redis, such as the [Redis Cache Module](!resources!/infrastructure-modules/cache/redis). +This configuration is not used for modules that also connect to Redis, such as the [Redis Caching Module Provider](!resources!/infrastructure-modules/caching/providers/redis). diff --git a/www/apps/book/app/learn/deployment/general/page.mdx b/www/apps/book/app/learn/deployment/general/page.mdx index b6aff15dbb..6c43dd8d04 100644 --- a/www/apps/book/app/learn/deployment/general/page.mdx +++ b/www/apps/book/app/learn/deployment/general/page.mdx @@ -120,11 +120,11 @@ So, add the following script in `package.json`: ## 3. Install Production Modules and Providers -By default, your Medusa application uses modules and providers useful for development, such as the In-Memory Cache Module or the Local File Module Provider. +By default, your Medusa application uses modules and providers useful for development, such as the Local File Module Provider. It’s highly recommended to instead use modules and providers suitable for production, including: -- [Redis Cache Module](!resources!/infrastructure-modules/cache/redis) +- [Redis Caching Module](!resources!/infrastructure-modules/caching/providers/redis) - [Redis Event Bus Module](!resources!/infrastructure-modules/event/redis) - [Workflow Engine Redis Module](!resources!/infrastructure-modules/workflow-engine/redis) - [Redis Locking Module Provider](!resources!/infrastructure-modules/locking/redis) @@ -132,6 +132,12 @@ It’s highly recommended to instead use modules and providers suitable for prod - [S3 File Module Provider](!resources!/infrastructure-modules/file/s3) (or other file module providers that are production-ready). - [SendGrid Notification Module Provider](!resources!/infrastructure-modules/notification/sendgrid) (or other notification module providers that are production-ready). + + +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. + + + Then, add these modules in `medusa-config.ts`: ```ts title="medusa-config.ts" @@ -141,9 +147,18 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/medusa/cache-redis", + resolve: "@medusajs/medusa/caching", options: { - redisUrl: process.env.REDIS_URL, + providers: [ + { + resolve: "@medusajs/cache-redis", + id: "caching-redis", + is_default: true, + options: { + redisUrl: process.env.CACHE_REDIS_URL, + }, + }, + ], }, }, { diff --git a/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx index bffcfcaa27..5e1b47e5b3 100644 --- a/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx +++ b/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx @@ -1,3 +1,5 @@ +import { Prerequisites, TypeList } from "docs-ui" + export const metadata = { title: `${pageNumber} Index Module`, } @@ -341,6 +343,338 @@ For example, this is the response returned by the above API route: --- +## Cache Index Module Results + + + + + +Caching options are available from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + + + +You can cache Index Module results to improve performance and reduce database load. To do that, you can pass a `cache` property in the second parameter of the `query.index` method. + +For example, to enable caching for a query: + +```ts highlights={[["6", "enable", "Enable caching for this query."]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true + } +}) +``` + +In this example, you enable caching of the query's results. The next time the same query is executed, the results are returned from the cache instead of querying the database. + + + +Refer to the [Caching Module documentation](!resources!/infrastructure-modules/caching/concepts#caching-best-practices) for best practices on caching. + + + +### Cache Properties + +`cache` is an object that accepts the following properties: + + boolean \| undefined)`", + name: "enable", + description: "Whether to enable caching of query results. If a function is passed, it receives as a parameter the `query.index` parameters, and returns a boolean indicating whether caching is enabled.", + defaultValue: "false" + }, + { + type: "`string` \| `((args: any[], cachingModule: ICachingModuleService) => string \| Promise)`", + name: "key", + description: "The key to cache the query results with. If no key is provided, the Caching Module will generate the key from the `query.index` parameters.\n\nIf a function is passed, it receives the following properties:\n\n1. The parameters passed to `query.index`.\n\n2. The [Caching Module's service](!resources!/references/caching-service), which you can use to perform caching operations.\n\nThe function must return a string indicating the cache key.", + }, + { + type: "`string[]` \| `((args: any[]) => string[] \| undefined)`", + name: "tags", + description: "The tags to associate with the cached results. Tags are useful to group related items. If no tag is provided, the Caching Module will generate relevant tags based on the entity and its retrieved relations.\n\nIf a function is passed, it receives as a parameter the `query.index` parameters, and returns an array of strings indicating the cache tags." + }, + { + type: "`number` \| `((args: any[]) => number \| undefined)`", + name: "ttl", + description: "The time-to-live (TTL) for the cached results, in seconds. If no TTL is provided, the Caching Module Provider will receive the [configured TTL of the Caching Module](!resources!/infrastructure-modules/caching#caching-module-options), or it will use its own default value.\n\nIf a function is passed, it receives as a parameter the `query.index` parameters, and returns a number indicating the TTL.", + }, + { + type: "`boolean` \| `((args: any[]) => boolean \| undefined)`", + name: "autoInvalidate", + description: "Whether to automatically invalidate the cached data when it expires.\n\nIf a function is passed, it receives as a parameter the `query.index` parameters, and returns a boolean indicating whether to automatically invalidate the cache.", + defaultValue: "`true`" + }, + { + type: "`string[]` \| `((args: any[]) => string[] \| undefined)`", + name: "providers", + description: "The IDs of the providers to use for caching. If not provided, the [default Caching Module Provider](!resources!/infrastructure-modules/caching/providers#default-caching-module-provider) is used. If multiple providers are passed, the cache is stored and retrieved in those providers in order.\n\nIf a function is passed, it receives as a parameter the `query.index` parameters, and return an array of strings indicating the providers to use." + } + ]} + sectionTitle="Cache Properties" +/> + +### Set Cache Key + +By default, the Caching Module generates a cache key for a query based on the arguments passed to `query.index`. The cache key is a unique key that the cached result is stored with. + +Alternatively, you can set a custom cache key for a query. This is useful if you want to manage invalidating the cache manually. + +To set the cache key of a query, pass the `cache.key` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: "products-123456", + // to disable auto invalidation: + // autoInvalidate: false, + } +}) +``` + +In the example above, you cache the query results with the `products-123456` key. + + + +You should generate cache keys with the Caching Module service's [computeKey method](!resources!/references/caching-service#computeKey) to ensure that the key is unique and follows best practices. + + + +You can also pass a function as the value of `cache.key`: + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: async (args, cachingModuleService) => { + return await cachingModuleService.computeKey({ + ...args, + prefix: "products" + }) + } + } +}) +``` + +In the example above, you pass a function to `key`. It accepts two parameters: + +1. The arguments of `query.index` passed as an array. +2. The [Caching Module's service](!resources!/references/caching-service). + +You generate the key using the [computeKey method of the Caching Module's service](!resources!/references/caching-service#computeKey). The query results will be cached with that key. + +### Set Cache Tags + +By default, the Caching Module generates relevant tags for a query based on the entity and its retrieved relations. Cache tags are useful to group related items together, allowing you to [retrieve](!resources!/references/caching-service#get) or [invalidate](!resources!/references/caching-service#clear) items by common tags. + +Alternatively, you can set the cache tags of a query manually. This is useful if you want to manage invalidating the cache manually, or you want to group related cached items with custom tags. + +To set the cache tags of a query, pass the `cache.tags` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: ["Product:list:*"], + } +}) +``` + +In the example above, you cache the query results with the `Product:list:*` tag. + + + +The cache tag must follow the [Caching Tags Convention](!resources!/infrastructure-modules/caching/concepts#caching-tags-convention) to be automatically invalidated. + + + +You can also pass a function as the value of `cache.tags`: + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: (args) => { + const collectionId = args[0].filter?.collection_id + return [ + ...args, + collectionId ? `ProductCollection:${collectionId}` : undefined, + ] + }, + } +}) +``` + +In the example above, you use a function to determine the cache tags. The function accepts the arguments passed to `query.index` as an array. + +Then, you add the `ProductCollection:id` tag if `collection_id` is passed in the query filters. + +### Set TTL + +By default, the Caching Module will pass the [configured time-to-live (TTL)](!resources!/infrastructure-modules/caching#caching-module-options) to the Caching Module Provider when caching data. The Caching Module Provider may also have its own default TTL. The cache isn't invalidated until the configured TTL passes. + +Alternatively, you can set a custom TTL for a query. This is useful if you want the cached data to be invalidated sooner or later than the default TTL. + +To set the TTL of the cached query results to a custom value, use the `cache.ttl` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + ttl: 100, // 100 seconds + } +}) +``` + +In the example above, you set the TTL of the cached query result to `100` seconds. It will be invalidated after that time. + +You can also pass a function as the value of `cache.ttl`: + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + ttl: (args) => { + return args[0].filters.id === "test" ? 10 : 100 + } + } +}) +``` + +In the example above, you use a function to determine the TTL. The function accepts the arguments passed to `query.index` as an array. + +Then, you set the TTL based on the ID of the product passed in the filters. + +### Set Auto Invalidation + +By default, the Caching Module automatically invalidates cached query results when the data changes. + +Alternatively, you can disable auto invalidation of cached query results. This is useful if you want to manage invalidating the cache manually. + +To configure invalidation behavior, use the `cache.autoInvalidate` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: false, + } +}) +``` + +In this example, you disable auto invalidation of the query result. You must [invalidate](!resources!/references/caching-service#clear) the cached data manually. + +You can also pass a function as the value of `cache.autoInvalidate`: + +```ts highlights={[["7"], ["8"], ["9"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: (args) => { + return !args[0].fields.includes("custom_field") + } + } +}) +``` + +In the example above, you use a function to determine whether to invalidate the cached query result automatically. The function accepts the arguments passed to `query.index` as an array. + +Then, you enable auto-invalidation only if the `fields` passed to `query.index` don't include `custom_fields`. If this disables auto-invalidation, you must [invalidate](!resources!/references/caching-service#clear) the cached data manually. + + + +Learn more about automatic invalidation in the [Caching Module documentation](!resources!/infrastructure-modules/caching/concepts#automatic-cache-invalidation). + + + +### Set Caching Provider + +By default, the Caching Module uses the [default Caching Module Provider](!resources!/infrastructure-modules/caching/providers#default-caching-module-provider) to cache a query. + +Alternatively, you can set the caching provider to use for a query. This is useful if you have multiple caching providers configured, and you want to use a specific one for a query, or you want to specify a fallback provider. + +To configure the caching providers, use the `cache.providers` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + providers: ["caching-redis", "caching-memcached"] + } +}) +``` + +In the example above, you specify the providers with ID `caching-redis` and `caching-memcached` to cache the query results. These IDs must match the IDs of the providers in `medusa-config.ts`. + +When you pass multiple providers, the cache is stored and retrieved in those providers in order. + +You can also pass a function as the value of `cache.providers`: + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + providers: (args) => { + return args[0].filters.id === "test" ? ["caching-redis"] : ["caching-memcached"] + } + } +}) +``` + +In the example above, you use a function to determine the caching providers. The function accepts the arguments passed to `query.index` as an array. + +Then, you set the providers based on the ID of the product passed in the filters. + +--- + ## index Method Usage Examples The following sections show examples of how to use the `index` method in different scenarios. diff --git a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx index a379c5214b..dc92c48bb3 100644 --- a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx +++ b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx @@ -1,4 +1,4 @@ -import { TypeList, Tabs, TabsList, TabsTriggerVertical, TabsContent, TabsContentWrapper } from "docs-ui" +import { TypeList, Tabs, TabsList, TabsTriggerVertical, TabsContent, TabsContentWrapper, Prerequisites, CodeTabs, CodeTab } from "docs-ui" export const metadata = { title: `${pageNumber} Query`, @@ -98,8 +98,6 @@ const myWorkflow = createWorkflow( You can learn more about this step in the [useQueryGraphStep](!resources!/references/helper-steps/useQueryGraphStep) reference. -The rest of this chapter uses the `graph` method to explain the different usages of Query, but the same principles apply to `useQueryGraphStep`. - --- ## Querying the Graph @@ -116,6 +114,9 @@ Retrieve the records of a linked data model by passing in `fields` the data mode For example: + + + ```ts highlights={[["6"]]} const { data: posts } = await query.graph({ entity: "post", @@ -127,10 +128,30 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["6"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: [ + "id", + "title", + "product.*", + ], +}) +``` + + + + `.*` means that all of the data model's properties should be retrieved. You can also retrieve specific properties by replacing the `*` with the property name for each property. For example: + + + ```ts const { data: posts } = await query.graph({ entity: "post", @@ -143,6 +164,24 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: [ + "id", + "title", + "product.id", + "product.title", + ], +}) +``` + + + + In the example above, you retrieve only the `id` and `title` properties of the `product` linked to a `post`. ### Retrieve List Link Records @@ -151,6 +190,9 @@ If the linked data model has `isList` enabled in the link definition, pass in `f For example: + + + ```ts highlights={[["6"]]} const { data: posts } = await query.graph({ entity: "post", @@ -162,6 +204,23 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["6"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: [ + "id", + "title", + "products.*", + ], +}) +``` + + + + In the example above, you retrieve all products linked to a post. ### Apply Filters and Pagination on Linked Records @@ -228,6 +287,9 @@ The returned `data` is similar to the following: ## Apply Filters + + + ```ts highlights={[["4"], ["5"], ["6"]]} const { data: posts } = await query.graph({ entity: "post", @@ -238,12 +300,31 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["4"], ["5"], ["6"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + id: "post_123", + }, +}) +``` + + + + The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records. In the example above, you filter the `post` records by the ID `post_123`. You can also filter by multiple values of a property. For example: + + + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]} const { data: posts } = await query.graph({ entity: "post", @@ -257,6 +338,25 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + id: [ + "post_123", + "post_321", + ], + }, +}) +``` + + + + In the example above, you filter the `post` records by multiple IDs. @@ -280,6 +380,9 @@ Refer to the [Service Factory Reference](!resources!/service-factory-reference/t #### Filter by Not Matching a Value + + + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -292,10 +395,31 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + title: { + $ne: null, + }, + }, +}) +``` + + + + In the example above, only posts that have a title are retrieved. #### Filter by Not Matching Multiple Values + + + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -308,10 +432,31 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + title: { + $nin: ["My Post", "Another Post"], + }, + }, +}) +``` + + + + In the example above, only posts that don't have the title `My Post` or `Another Post` are retrieved. #### Filter by a Range + + + ```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]} const startToday = new Date() startToday.setHours(0, 0, 0, 0) @@ -331,6 +476,31 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]} +const startToday = new Date() +startToday.setHours(0, 0, 0, 0) + +const endToday = new Date() +endToday.setHours(23, 59, 59, 59) + +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + published_at: { + $gt: startToday, + $lt: endToday, + }, + }, +}) +``` + + + + In the example above, only posts that were published today are retrieved. #### Filter Text by Like Value @@ -341,6 +511,9 @@ This filter only applies to text-like properties, including `text`, `id`, and `e + + + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -353,10 +526,31 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + title: { + $like: "%My%", + }, + }, +}) +``` + + + + In the example above, only posts that have the word `My` in their title are retrieved. #### Filter a Relation's Property + + + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -369,12 +563,33 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + author: { + name: "John", + }, + }, +}) +``` + + + + While it's not possible to filter by a linked data model's property, you can filter by a relation's property (that is, the property of a related data model that is defined in the same module). In the example above, only posts that have an author with the name `John` are retrieved. #### Filter by Relation Property Not Matching Value + + + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]} const { data: posts } = await query.graph({ entity: "post", @@ -398,7 +613,36 @@ const { data: posts } = await query.graph({ }) ``` -When you need to filter by a relationship property whose value doesn't match a specific condition, you must use an `$or` operator that applies the following conditions: + + + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + author: { + $or: [ + { + name: { + $eq: null, + }, + }, + { + name: { + $ne: "John", + }, + }, + ], + }, + }, +}) +``` + + + + +To filter by a relationship property whose value doesn't match a specific condition, use an `$or` operator that applies the following conditions: 1. The relationship's property is not set. This is necessary to exclude posts that don't have an author. 2. The relationship's property is not equal to the specific value. @@ -409,6 +653,9 @@ So, in the example above, the query retrieves posts that either don't have an au ## Apply Pagination + + + ```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]} const { data: posts, @@ -423,6 +670,26 @@ const { }) ``` + + + +```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]} +const { + data: posts, + metadata +} = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + pagination: { + skip: 0, + take: 10, + }, +}) +``` + + + + The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records. To paginate the returned records, pass the following properties to `pagination`: @@ -452,6 +719,9 @@ When you provide the pagination fields, the `query.graph` method's returned obje ### Sort Records + + + ```ts highlights={[["5"], ["6"], ["7"]]} const { data: posts } = await query.graph({ entity: "post", @@ -464,6 +734,24 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["5"], ["6"], ["7"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + pagination: { + order: { + name: "DESC", + }, + }, +}) +``` + + + + Sorting doesn't work on fields of linked data models from other modules. @@ -491,6 +779,9 @@ The `withDeleted` property is available from [Medusa v2.8.5](https://github.com/ For example: + + + ```ts highlights={[["4", "withDeleted", "Include deleted posts in the results."]]} const { data: posts } = await query.graph({ entity: "post", @@ -499,6 +790,20 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={[["4", "withDeleted", "Include deleted posts in the results."]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + withDeleted: true, +}) +``` + + + + In the example above, you retrieve all posts, including deleted ones. ### Retrieve Only Deleted Records @@ -510,6 +815,9 @@ export const withDeletedHighlights = [ ["9", "withDeleted", "Include deleted posts in the results."], ] + + + ```ts highlights={withDeletedHighlights} const { data: posts } = await query.graph({ entity: "post", @@ -523,18 +831,40 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts highlights={withDeletedHighlights} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + deleted_at: { + $ne: null, + }, + }, + withDeleted: true, +}) +``` + + + + In the example above, you retrieve only deleted posts by enabling the `withDeleted` property and adding a filter to only retrieve records where the `deleted_at` property is not `null`. --- -## Configure Query to Throw Errors +## Configure Query to Throw Error -By default, if Query doesn't find records matching your query, it returns an empty array. You can add an option to configure Query to throw an error when no records are found. +By default, if Query doesn't find records matching your query, it returns an empty array. You can configure Query to throw an error when no records are found. The `query.graph` method accepts as a second parameter an object that can have a `throwIfKeyNotFound` property. Its value is a boolean indicating whether to throw an error if no record is found when filtering by IDs. By default, it's `false`. For example: + + + ```ts const { data: posts } = await query.graph({ entity: "post", @@ -547,7 +877,26 @@ const { data: posts } = await query.graph({ }) ``` -In the example above, if no post is found with the ID `post_123`, Query will throw an error. This is useful to stop execution when a record is expected to exist. + + + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + id: "post_123", + }, + options: { + throwIfKeyNotFound: true, + }, +}) +``` + + + + +In the example above, if no post is found with the ID `post_123`, Query throws an error. This is useful to stop execution when a record is expected to exist. ### Throw Error on Related Data Model @@ -555,6 +904,9 @@ The `throwIfKeyNotFound` option can also be used to throw an error if the ID of For example: + + + ```ts const { data: posts } = await query.graph({ entity: "post", @@ -568,12 +920,35 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title", "author.*"], + filters: { + id: "post_123", + author_id: "author_123", + }, + options: { + throwIfKeyNotFound: true, + } +}) +``` + + + + In the example above, Query throws an error either if no post is found with the ID `post_123` or if it's found but its author ID isn't `author_123`. In the above example, it's assumed that a post belongs to an author, so it has an `author_id` property. However, this also works in the opposite case, where an author has many posts. For example: + + + ```ts const { data: posts } = await query.graph({ entity: "author", @@ -589,10 +964,529 @@ const { data: posts } = await query.graph({ }) ``` + + + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "author", + fields: ["id", "name", "posts.*"], + filters: { + id: "author_123", + posts: { + id: "post_123", + }, + }, + options: { + throwIfKeyNotFound: true, + } +}) +``` + + + + In the example above, Query throws an error if no author is found with the ID `author_123` or if the author is found but doesn't have a post with the ID `post_123`. --- +## Cache Query Results + + + + + +Caching options are available from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + + + +You can cache Query results to improve performance and reduce database load. To do that, you can pass a `cache` property in the second parameter of the `query.graph` method. + +For example, to enable caching for a query: + + + + +```ts highlights={[["6", "enable", "Enable caching for this query."]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true + } +}) +``` + + + + +```ts highlights={[["6", "enable", "Enable caching for this query."]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true + } + } +}) +``` + + + + +In this example, you enable caching of the query's results. The next time the same query is executed, the results are returned from the cache instead of querying the database. + + + +Refer to the [Caching Module documentation](!resources!/infrastructure-modules/caching/concepts#caching-best-practices) for best practices on caching. + + + +### Cache Properties + +`cache` is an object that accepts the following properties: + + boolean \| undefined)`", + name: "enable", + description: "Whether to enable caching of query results. If a function is passed, it receives as a parameter the `query.graph` parameters, and returns a boolean indicating whether caching is enabled.", + defaultValue: "false" + }, + { + type: "`string` \| `((args: any[], cachingModule: ICachingModuleService) => string \| Promise)`", + name: "key", + description: "The key to cache the query results with. If no key is provided, the Caching Module will generate the key from the `query.graph` parameters.\n\nIf a function is passed, it receives the following properties:\n\n1. The parameters passed to `query.graph`.\n\n2. The [Caching Module's service](!resources!/references/caching-service), which you can use to perform caching operations.\n\nThe function must return a string indicating the cache key.", + }, + { + type: "`string[]` \| `((args: any[]) => string[] \| undefined)`", + name: "tags", + description: "The tags to associate with the cached results. Tags are useful to group related items. If no tag is provided, the Caching Module will generate relevant tags for the entity and its retrieved relations.\n\nIf a function is passed, it receives as a parameter the `query.index` parameters, and returns an array of strings indicating the cache tags." + }, + { + type: "`number` \| `((args: any[]) => number \| undefined)`", + name: "ttl", + description: "The time-to-live (TTL) for the cached results, in seconds. If no TTL is provided, the Caching Module Provider will receive the [configured TTL of the Caching Module](!resources!/infrastructure-modules/caching#caching-module-options), or it will use its own default value.\n\nIf a function is passed, it receives as a parameter the `query.graph` parameters, and returns a number indicating the TTL.", + }, + { + type: "`boolean` \| `((args: any[]) => boolean \| undefined)`", + name: "autoInvalidate", + description: "Whether to automatically invalidate the cached data when it expires.\n\nIf a function is passed, it receives as a parameter the `query.graph` parameters, and returns a boolean indicating whether to automatically invalidate the cache.", + defaultValue: "`true`" + }, + { + type: "`string[]` \| `((args: any[]) => string[] \| undefined)`", + name: "providers", + description: "The IDs of the providers to use for caching. If not provided, the [default Caching Module Provider](!resources!/infrastructure-modules/caching/providers#default-caching-module-provider) is used. If multiple providers are passed, the cache is stored and retrieved in those providers in order.\n\nIf a function is passed, it receives as a parameter the `query.graph` parameters, and return an array of strings indicating the providers to use." + } + ]} + sectionTitle="Cache Properties" +/> + +### Set Cache Key + +By default, the Caching Module generates a cache key for a query based on the arguments passed to `query.graph`. The cache key is a unique key that the cached result is stored with. + +Alternatively, you can set a custom cache key for a query. This is useful if you want to manage invalidating the cache manually. + +To set the cache key of a query, pass the `cache.key` option: + + + + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: "products-123456", + // to disable auto invalidation: + // autoInvalidate: false, + } +}) +``` + + + + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + key: "products-123456", + // to disable auto invalidation: + // autoInvalidate: false, + } + } +}) +``` + + + + +In the example above, you cache the query results with the `products-123456` key. + + + +You should generate cache keys with the Caching Module service's [computeKey method](!resources!/references/caching-service#computeKey) to ensure that the key is unique and follows best practices. + + + +You can also pass a function as the value of `cache.key`: + + + +Passing a function to `cache.key` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](../../workflows/variable-manipulation/page.mdx). You can alternatively create a step that uses Query directly, and use it in the workflow. + + + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: async (args, cachingModuleService) => { + return await cachingModuleService.computeKey({ + ...args, + prefix: "products" + }) + } + } +}) +``` + +In the example above, you pass a function to `key`. It accepts two parameters: + +1. The arguments of `query.graph` passed as an array. +2. The [Caching Module's service](!resources!/references/caching-service). + +You generate the key using the [computeKey method of the Caching Module's service](!resources!/references/caching-service#computeKey). The query results will be cached with that key. + +### Set Cache Tags + +By default, the Caching Module generates relevant tags for a query based on the entity and its retrieved relations. Cache tags are useful to group related items together, allowing you to [retrieve](!resources!/references/caching-service#get) or [invalidate](!resources!/references/caching-service#clear) items by common tags. + +Alternatively, you can set the cache tags of a query manually. This is useful if you want to manage invalidating the cache manually, or you want to group related cached items with custom tags. + +To set the cache tags of a query, pass the `cache.tags` option: + + + + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: ["Product:list:*"], + } +}) +``` + + + + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + tags: ["Product:list:*"], + // to disable auto invalidation: + // autoInvalidate: false, + } + } +}) +``` + + + + +In the example above, you cache the query results with the `Product:list:*` tag. + + + +The cache tag must follow the [Caching Tags Convention](!resources!/infrastructure-modules/caching/concepts#caching-tags-convention) to be automatically invalidated. + + + +You can also pass a function as the value of `cache.tags`: + + + +Passing a function to `cache.tags` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](../../workflows/variable-manipulation/page.mdx). You can alternatively create a step that uses Query directly, and use it in the workflow. + + + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: (args) => { + const collectionId = args[0].filter?.collection_id + return [ + ...args, + collectionId ? `ProductCollection:${collectionId}` : undefined, + ] + }, + } +}) +``` + +In the example above, you use a function to determine the cache tags. The function accepts the arguments passed to `query.graph` as an array. + +Then, you add the `ProductCollection:id` tag if `collection_id` is passed in the query filters. + +### Set TTL + +By default, the Caching Module will pass the [configured time-to-live (TTL)](!resources!/infrastructure-modules/caching#caching-module-options) to the Caching Module Provider when caching data. The Caching Module Provider may also have its own default TTL. The cache isn't invalidated until the configured TTL passes. + +Alternatively, you can set a custom TTL for a query. This is useful if you want the cached data to be invalidated sooner or later than the default TTL. + +To set the TTL of the cached query results to a custom value, use the `cache.ttl` option: + + + + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + ttl: 100, // 100 seconds + } +}) +``` + + + + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + ttl: 100, // 100 seconds + } + } +}) +``` + + + + +In the example above, you set the TTL of the cached query result to `100` seconds. It will be invalidated after that time. + +You can also pass a function as the value of `cache.ttl`: + + + +Passing a function to `cache.ttl` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](../../workflows/variable-manipulation/page.mdx). You can alternatively create a step that uses Query directly, and use it in the workflow. + + + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + ttl: (args) => { + return args[0].filters.id === "test" ? 10 : 100 + } + } +}) +``` + +In the example above, you use a function to determine the TTL. The function accepts the arguments passed to `query.graph` as an array. + +Then, you set the TTL based on the ID of the product passed in the filters. + +### Set Auto Invalidation + +By default, the Caching Module automatically invalidates cached query results when the data changes. + +Alternatively, you can disable auto invalidation of cached query results. This is useful if you want to manage invalidating the cache manually. + +To configure invalidation behavior, use the `cache.autoInvalidate` option: + + + + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: false, + } +}) +``` + + + + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + autoInvalidate: false, + } + } +}) +``` + + + + +In this example, you disable auto invalidation of the query result. You must [invalidate](!resources!/references/caching-service#clear) the cached data manually. + +You can also pass a function as the value of `cache.autoInvalidate`: + + + +Passing a function to `cache.autoInvalidate` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](../../workflows/variable-manipulation/page.mdx). You can alternatively create a step that uses Query directly, and use it in the workflow. + + + +```ts highlights={[["7"], ["8"], ["9"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: (args) => { + return !args[0].fields.includes("custom_field") + } + } +}) +``` + +In the example above, you use a function to determine whether to invalidate the cached query result automatically. The function accepts the arguments passed to `query.graph` as an array. + +Then, you enable auto-invalidation only if the `fields` passed to `query.graph` don't include `custom_fields`. If this disables auto-invalidation, you must [invalidate](!resources!/references/caching-service#clear) the cached data manually. + + + +Learn more about automatic invalidation in the [Caching Module documentation](!resources!/infrastructure-modules/caching/concepts#automatic-cache-invalidation). + + + +### Set Caching Provider + +By default, the Caching Module uses the [default Caching Module Provider](!resources!/infrastructure-modules/caching/providers#default-caching-module-provider) to cache a query. + +Alternatively, you can set the caching provider to use for a query. This is useful if you have multiple caching providers configured, and you want to use a specific one for a query, or you want to specify a fallback provider. + +To configure the caching providers, use the `cache.providers` option: + + + + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + providers: ["caching-redis", "caching-memcached"] + } +}) +``` + + + + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + providers: ["caching-redis", "caching-memcached"] + } + } +}) +``` + + + + +In the example above, you specify the providers with ID `caching-redis` and `caching-memcached` to cache the query results. These IDs must match the IDs of the providers in `medusa-config.ts`. + +When you pass multiple providers, the cache is stored and retrieved in those providers in order. + +You can also pass a function as the value of `cache.providers`: + + + +Passing a function to `cache.providers` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](../../workflows/variable-manipulation/page.mdx). You can alternatively create a step that uses Query directly, and use it in the workflow. + + + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + providers: (args) => { + return args[0].filters.id === "test" ? ["caching-redis"] : ["caching-memcached"] + } + } +}) +``` + +In the example above, you use a function to determine the caching providers. The function accepts the arguments passed to `query.graph` as an array. + +Then, you set the providers based on the ID of the product passed in the filters. + +--- + ## Request Query Configurations For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that: diff --git a/www/apps/book/app/learn/fundamentals/modules/infrastructure-modules/page.mdx b/www/apps/book/app/learn/fundamentals/modules/infrastructure-modules/page.mdx index 4b151729d3..b87d2a9a16 100644 --- a/www/apps/book/app/learn/fundamentals/modules/infrastructure-modules/page.mdx +++ b/www/apps/book/app/learn/fundamentals/modules/infrastructure-modules/page.mdx @@ -18,15 +18,21 @@ Since modules are interchangeable, you have more control over Medusa’s archite There are different Infrastructure Module types including: -![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727095814/Medusa%20Book/architectural-modules_bj9bb9.jpg) +![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1759762284/Medusa%20Book/service-infra_k3fcy0.jpg) -- Analytics Module: Integrates a third-party service to track and analyze user interactions and system events. -- Cache Module: Defines the caching mechanism or logic to cache computational results. -- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events. -- Workflow Engine Module: Integrates a service to store and track workflow executions and steps. -- File Module: Integrates a storage service to handle uploading and managing files. -- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers. -- Locking Module: Integrates a service that manages access to shared resources by multiple processes or threads. +- [Analytics Module](!resources!/infrastructure-modules/analytics): Integrates a third-party service to track and analyze user interactions and system events. +- [Caching Module](!resources!/infrastructure-modules/caching): Defines the caching mechanism or logic to cache computational results. +- [Event Module](!resources!/infrastructure-modules/event): Integrates a pub/sub service to handle subscribing to and emitting events. +- [Workflow Engine Module](!resources!/infrastructure-modules/workflow-engine): Integrates a service to store and track workflow executions and steps. +- [File Module](!resources!/infrastructure-modules/file): Integrates a storage service to handle uploading and managing files. +- [Notification Module](!resources!/infrastructure-modules/notification): Integrates a third-party service or defines custom logic to send notifications to users and customers. +- [Locking Module](!resources!/infrastructure-modules/locking): Integrates a service that manages access to shared resources by multiple processes or threads. + + + +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. + + --- diff --git a/www/apps/book/app/learn/introduction/architecture/page.mdx b/www/apps/book/app/learn/introduction/architecture/page.mdx index a791df39d1..7102cc5a3f 100644 --- a/www/apps/book/app/learn/introduction/architecture/page.mdx +++ b/www/apps/book/app/learn/introduction/architecture/page.mdx @@ -29,7 +29,7 @@ These layers of stack can be implemented within [plugins](../../fundamentals/plu -![Medusa application architecture diagram illustrating the HTTP layer flow: External clients (storefront and admin) send requests to API routes, which execute workflows containing business logic, which then interact with modules to perform data operations on PostgreSQL databases](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175296/Medusa%20Book/http-layer_sroafr.jpg) +![Medusa application architecture diagram illustrating the HTTP layer flow: External clients (storefront and admin) send requests to API routes, which execute workflows containing business logic, which then interact with modules to perform data operations on PostgreSQL databases](https://res.cloudinary.com/dza7lstvk/image/upload/v1759761784/Medusa%20Book/http-layer-new_hu0r3h.jpg) --- @@ -43,7 +43,7 @@ Modules can be implemented within [plugins](../../fundamentals/plugins/page.mdx) -![Database layer architecture diagram showing how Medusa modules establish connections to PostgreSQL databases through injected database connections, enabling data persistence and retrieval operations](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175379/Medusa%20Book/db-layer_pi7tix.jpg) +![Database layer architecture diagram showing how Medusa modules establish connections to PostgreSQL databases through injected database connections, enabling data persistence and retrieval operations](https://res.cloudinary.com/dza7lstvk/image/upload/v1759761866/Medusa%20Book/db-layer-new_faeksx.jpg) --- @@ -72,7 +72,7 @@ You can replace any of the third-party services mentioned above to build your pr [Infrastructure Modules](!resources!/infrastructure-modules) integrate third-party services and systems that customize Medusa's infrastructure. Medusa has the following Infrastructure Modules: - [Analytics Module](!resources!/infrastructure-modules/analytics): Tracks and analyzes user interactions and system events with third-party analytic providers. You can integrate [PostHog](!resources!/infrastructure-modules/analytics/posthog) as the analytics provider. -- [Cache Module](!resources!/infrastructure-modules/cache): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Cache Module](!resources!/infrastructure-modules/cache/redis). +- [Caching Module](!resources!/infrastructure-modules/caching): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Caching Module Provider](!resources!/infrastructure-modules/caching/providers/redis). - [Event Module](!resources!/infrastructure-modules/event): A pub/sub system that allows you to subscribe to events and trigger them. You can integrate [Redis](!resources!/infrastructure-modules/event/redis) as the pub/sub system. - [File Module](!resources!/infrastructure-modules/file): Manages file uploads and storage, such as upload of product images. You can integrate [AWS S3](!resources!/infrastructure-modules/file/s3) for file storage. - [Locking Module](!resources!/infrastructure-modules/locking): Manages access to shared resources by multiple processes or threads, preventing conflict between processes and ensuring data consistency. You can integrate [Redis](!resources!/infrastructure-modules/locking/redis) for locking. @@ -81,7 +81,13 @@ You can replace any of the third-party services mentioned above to build your pr All of the third-party services mentioned above can be replaced to help you build your preferred architecture and ecosystem. -![Diagram illustrating the Infrastructure Modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175342/Medusa%20Book/service-arch_ozvryw.jpg) + + +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. + + + +![Diagram illustrating the Infrastructure Modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1759762284/Medusa%20Book/service-infra_k3fcy0.jpg) --- @@ -89,4 +95,4 @@ All of the third-party services mentioned above can be replaced to help you buil The following diagram illustrates Medusa's architecture including all its layers. -![Complete Medusa architecture overview showing the full technology stack: client applications (storefront and admin) connecting through HTTP layer to workflows, which coordinate with commerce and Infrastructure Modules to manage data operations, third-party integrations, and database persistence](https://res.cloudinary.com/dza7lstvk/image/upload/v1727174897/Medusa%20Book/architectural-diagram-full.jpg) +![Complete Medusa architecture overview showing the full technology stack: client applications (storefront and admin) connecting through HTTP layer to workflows, which coordinate with commerce and Infrastructure Modules to manage data operations, third-party integrations, and database persistence](https://res.cloudinary.com/dza7lstvk/image/upload/v1759762329/Medusa%20Book/diagram-full-new_znssjy.jpg) diff --git a/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx b/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx index 4506bf8e22..975a50bdab 100644 --- a/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx +++ b/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx @@ -125,7 +125,7 @@ npm install -In Medusa v1, you needed to install Medusa modules like the Cache, Event, or Pricing modules. +In Medusa v1, you needed to install Medusa modules like the Event, Product, or Pricing modules. These modules are now available out of the box, and you don't need to install or configure them separately. @@ -375,7 +375,7 @@ While the `plugins` configuration hasn't changed, plugins available in Medusa v1 In Medusa v1, you had to configure modules like Inventory, Stock Location, Pricing, and Product. These modules are now available out of the box, and you don't need to install or configure them separately. -For the Cache and Event modules, refer to the [Redis Cache Module](!resources!/infrastructure-modules/cache/redis) and [Redis Event Module](!resources!/infrastructure-modules/event/redis) documentations to learn how to configure them in v2 if you had them configured in v1. +For the Cache and Event modules, refer to the [Redis Caching Module Provider](!resources!/infrastructure-modules/caching/providers/redis) and [Redis Event Module](!resources!/infrastructure-modules/event/redis) documentations to learn how to configure them in v2 if you had them configured in v1. #### Feature Flags diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index 3a00c44ca7..49261c31f2 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -969,7 +969,7 @@ The `projectConfig.redisPrefix` configuration defines a prefix on all keys store The value of this configuration is prepended to `sess:`. For example, if you set it to `medusa:`, then a key stored in Redis is prefixed by `medusa:sess`. -This configuration is not used for modules that also connect to Redis, such as the [Redis Cache Module](https://docs.medusajs.com/resources/infrastructure-modules/cache/redis/index.html.md). +This configuration is not used for modules that also connect to Redis, such as the [Redis Caching Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers/redis/index.html.md). #### Example @@ -985,9 +985,9 @@ module.exports = defineConfig({ ### redisUrl -The `projectConfig.redisUrl` configuration specifies the connection URL to Redis to store the Medusa server session. When specified, the Medusa server uses Redis to store the session data. Otherwie, the session data is stored in-memory. +The `projectConfig.redisUrl` configuration specifies the connection URL to Redis to store the Medusa server session. When specified, the Medusa server uses Redis to store the session data. Otherwise, the session data is stored in-memory. -This configuration is not used for modules that also connect to Redis, such as the [Redis Cache Module](https://docs.medusajs.com/resources/infrastructure-modules/cache/redis/index.html.md). You'll have to configure the Redis connection for those modules separately. +This configuration is not used for modules that also connect to Redis, such as the [Redis Caching Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers/redis/index.html.md). You'll have to configure the Redis connection for those modules separately. You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/). @@ -1024,7 +1024,7 @@ module.exports = defineConfig({ The `projectConfig.sessionOptions` configuration defines additional options to pass to [express-session](https://www.npmjs.com/package/express-session), which is used to store the Medusa server session. -This configuration is not used for modules that also connect to Redis, such as the [Redis Cache Module](https://docs.medusajs.com/resources/infrastructure-modules/cache/redis/index.html.md). +This configuration is not used for modules that also connect to Redis, such as the [Redis Caching Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers/redis/index.html.md). #### Example @@ -6507,11 +6507,11 @@ So, add the following script in `package.json`: ## 3. Install Production Modules and Providers -By default, your Medusa application uses modules and providers useful for development, such as the In-Memory Cache Module or the Local File Module Provider. +By default, your Medusa application uses modules and providers useful for development, such as the Local File Module Provider. It’s highly recommended to instead use modules and providers suitable for production, including: -- [Redis Cache Module](https://docs.medusajs.com/resources/infrastructure-modules/cache/redis/index.html.md) +- [Redis Caching Module](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers/redis/index.html.md) - [Redis Event Bus Module](https://docs.medusajs.com/resources/infrastructure-modules/event/redis/index.html.md) - [Workflow Engine Redis Module](https://docs.medusajs.com/resources/infrastructure-modules/workflow-engine/redis/index.html.md) - [Redis Locking Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/locking/redis/index.html.md) @@ -6519,6 +6519,8 @@ It’s highly recommended to instead use modules and providers suitable for prod - [S3 File Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/file/s3/index.html.md) (or other file module providers that are production-ready). - [SendGrid Notification Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/notification/sendgrid/index.html.md) (or other notification module providers that are production-ready). +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. + Then, add these modules in `medusa-config.ts`: ```ts title="medusa-config.ts" @@ -6528,9 +6530,18 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/medusa/cache-redis", + resolve: "@medusajs/medusa/caching", options: { - redisUrl: process.env.REDIS_URL, + providers: [ + { + resolve: "@medusajs/cache-redis", + id: "caching-redis", + is_default: true, + options: { + redisUrl: process.env.CACHE_REDIS_URL, + }, + }, + ], }, }, { @@ -13996,6 +14007,298 @@ For example, this is the response returned by the above API route: *** +## Cache Index Module Results + +### Prerequisites + +- [Caching Module installed with a provider.](https://docs.medusajs.com/resources/infrastructure-modules/caching#install-the-caching-module/index.html.md) + +Caching options are available from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +You can cache Index Module results to improve performance and reduce database load. To do that, you can pass a `cache` property in the second parameter of the `query.index` method. + +For example, to enable caching for a query: + +```ts highlights={[["6", "enable", "Enable caching for this query."]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true + } +}) +``` + +In this example, you enable caching of the query's results. The next time the same query is executed, the results are returned from the cache instead of querying the database. + +Refer to the [Caching Module documentation](https://docs.medusajs.com/resources/infrastructure-modules/caching/concepts#caching-best-practices/index.html.md) for best practices on caching. + +### Cache Properties + +`cache` is an object that accepts the following properties: + +- enable: (\`boolean\` | \`((args: any\[]) => boolean | undefined)\`) Whether to enable caching of query results. If a function is passed, it receives as a parameter the \`query.index\` parameters, and returns a boolean indicating whether caching is enabled. +- key: (\`string\` | \`((args: any\[], cachingModule: ICachingModuleService) => string | Promise\)\`) The key to cache the query results with. If no key is provided, the Caching Module will generate the key from the \`query.index\` parameters. + + If a function is passed, it receives the following properties: + + 1\. The parameters passed to \`query.index\`. + + 2\. The \[Caching Module's service]\(!resources!/references/caching-service), which you can use to perform caching operations. + + The function must return a string indicating the cache key. +- tags: (\`string\[]\` | \`((args: any\[]) => string\[] | undefined)\`) The tags to associate with the cached results. Tags are useful to group related items. If no tag is provided, the Caching Module will generate relevant tags based on the entity and its retrieved relations. + + If a function is passed, it receives as a parameter the \`query.index\` parameters, and returns an array of strings indicating the cache tags. +- ttl: (\`number\` | \`((args: any\[]) => number | undefined)\`) The time-to-live (TTL) for the cached results, in seconds. If no TTL is provided, the Caching Module Provider will receive the \[configured TTL of the Caching Module]\(!resources!/infrastructure-modules/caching#caching-module-options), or it will use its own default value. + + If a function is passed, it receives as a parameter the \`query.index\` parameters, and returns a number indicating the TTL. +- autoInvalidate: (\`boolean\` | \`((args: any\[]) => boolean | undefined)\`) Whether to automatically invalidate the cached data when it expires. + + If a function is passed, it receives as a parameter the \`query.index\` parameters, and returns a boolean indicating whether to automatically invalidate the cache. +- providers: (\`string\[]\` | \`((args: any\[]) => string\[] | undefined)\`) The IDs of the providers to use for caching. If not provided, the \[default Caching Module Provider]\(!resources!/infrastructure-modules/caching/providers#default-caching-module-provider) is used. If multiple providers are passed, the cache is stored and retrieved in those providers in order. + + If a function is passed, it receives as a parameter the \`query.index\` parameters, and return an array of strings indicating the providers to use. + +### Set Cache Key + +By default, the Caching Module generates a cache key for a query based on the arguments passed to `query.index`. The cache key is a unique key that the cached result is stored with. + +Alternatively, you can set a custom cache key for a query. This is useful if you want to manage invalidating the cache manually. + +To set the cache key of a query, pass the `cache.key` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: "products-123456", + // to disable auto invalidation: + // autoInvalidate: false, + } +}) +``` + +In the example above, you cache the query results with the `products-123456` key. + +You should generate cache keys with the Caching Module service's [computeKey method](https://docs.medusajs.com/resources/references/caching-service#computeKey/index.html.md) to ensure that the key is unique and follows best practices. + +You can also pass a function as the value of `cache.key`: + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: async (args, cachingModuleService) => { + return await cachingModuleService.computeKey({ + ...args, + prefix: "products" + }) + } + } +}) +``` + +In the example above, you pass a function to `key`. It accepts two parameters: + +1. The arguments of `query.index` passed as an array. +2. The [Caching Module's service](https://docs.medusajs.com/resources/references/caching-service/index.html.md). + +You generate the key using the [computeKey method of the Caching Module's service](https://docs.medusajs.com/resources/references/caching-service#computeKey/index.html.md). The query results will be cached with that key. + +### Set Cache Tags + +By default, the Caching Module generates relevant tags for a query based on the entity and its retrieved relations. Cache tags are useful to group related items together, allowing you to [retrieve](https://docs.medusajs.com/resources/references/caching-service#get/index.html.md) or [invalidate](https://docs.medusajs.com/resources/references/caching-service#clear/index.html.md) items by common tags. + +Alternatively, you can set the cache tags of a query manually. This is useful if you want to manage invalidating the cache manually, or you want to group related cached items with custom tags. + +To set the cache tags of a query, pass the `cache.tags` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: ["Product:list:*"], + } +}) +``` + +In the example above, you cache the query results with the `Product:list:*` tag. + +The cache tag must follow the [Caching Tags Convention](https://docs.medusajs.com/resources/infrastructure-modules/caching/concepts#caching-tags-convention/index.html.md) to be automatically invalidated. + +You can also pass a function as the value of `cache.tags`: + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: (args) => { + const collectionId = args[0].filter?.collection_id + return [ + ...args, + collectionId ? `ProductCollection:${collectionId}` : undefined, + ] + }, + } +}) +``` + +In the example above, you use a function to determine the cache tags. The function accepts the arguments passed to `query.index` as an array. + +Then, you add the `ProductCollection:id` tag if `collection_id` is passed in the query filters. + +### Set TTL + +By default, the Caching Module will pass the [configured time-to-live (TTL)](https://docs.medusajs.com/resources/infrastructure-modules/caching#caching-module-options/index.html.md) to the Caching Module Provider when caching data. The Caching Module Provider may also have its own default TTL. The cache isn't invalidated until the configured TTL passes. + +Alternatively, you can set a custom TTL for a query. This is useful if you want the cached data to be invalidated sooner or later than the default TTL. + +To set the TTL of the cached query results to a custom value, use the `cache.ttl` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + ttl: 100, // 100 seconds + } +}) +``` + +In the example above, you set the TTL of the cached query result to `100` seconds. It will be invalidated after that time. + +You can also pass a function as the value of `cache.ttl`: + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + ttl: (args) => { + return args[0].filters.id === "test" ? 10 : 100 + } + } +}) +``` + +In the example above, you use a function to determine the TTL. The function accepts the arguments passed to `query.index` as an array. + +Then, you set the TTL based on the ID of the product passed in the filters. + +### Set Auto Invalidation + +By default, the Caching Module automatically invalidates cached query results when the data changes. + +Alternatively, you can disable auto invalidation of cached query results. This is useful if you want to manage invalidating the cache manually. + +To configure invalidation behavior, use the `cache.autoInvalidate` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: false, + } +}) +``` + +In this example, you disable auto invalidation of the query result. You must [invalidate](https://docs.medusajs.com/resources/references/caching-service#clear/index.html.md) the cached data manually. + +You can also pass a function as the value of `cache.autoInvalidate`: + +```ts highlights={[["7"], ["8"], ["9"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: (args) => { + return !args[0].fields.includes("custom_field") + } + } +}) +``` + +In the example above, you use a function to determine whether to invalidate the cached query result automatically. The function accepts the arguments passed to `query.index` as an array. + +Then, you enable auto-invalidation only if the `fields` passed to `query.index` don't include `custom_fields`. If this disables auto-invalidation, you must [invalidate](https://docs.medusajs.com/resources/references/caching-service#clear/index.html.md) the cached data manually. + +Learn more about automatic invalidation in the [Caching Module documentation](https://docs.medusajs.com/resources/infrastructure-modules/caching/concepts#automatic-cache-invalidation/index.html.md). + +### Set Caching Provider + +By default, the Caching Module uses the [default Caching Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers#default-caching-module-provider/index.html.md) to cache a query. + +Alternatively, you can set the caching provider to use for a query. This is useful if you have multiple caching providers configured, and you want to use a specific one for a query, or you want to specify a fallback provider. + +To configure the caching providers, use the `cache.providers` option: + +```ts highlights={[["7"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + providers: ["caching-redis", "caching-memcached"] + } +}) +``` + +In the example above, you specify the providers with ID `caching-redis` and `caching-memcached` to cache the query results. These IDs must match the IDs of the providers in `medusa-config.ts`. + +When you pass multiple providers, the cache is stored and retrieved in those providers in order. + +You can also pass a function as the value of `cache.providers`: + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + providers: (args) => { + return args[0].filters.id === "test" ? ["caching-redis"] : ["caching-memcached"] + } + } +}) +``` + +In the example above, you use a function to determine the caching providers. The function accepts the arguments passed to `query.index` as an array. + +Then, you set the providers based on the ID of the product passed in the filters. + +*** + ## index Method Usage Examples The following sections show examples of how to use the `index` method in different scenarios. @@ -14882,8 +15185,6 @@ const myWorkflow = createWorkflow( You can learn more about this step in the [useQueryGraphStep](https://docs.medusajs.com/resources/references/helper-steps/useQueryGraphStep/index.html.md) reference. -The rest of this chapter uses the `graph` method to explain the different usages of Query, but the same principles apply to `useQueryGraphStep`. - *** ## Querying the Graph @@ -14900,6 +15201,8 @@ Retrieve the records of a linked data model by passing in `fields` the data mode For example: +### query.graph + ```ts highlights={[["6"]]} const { data: posts } = await query.graph({ entity: "post", @@ -14911,10 +15214,25 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["6"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: [ + "id", + "title", + "product.*", + ], +}) +``` + `.*` means that all of the data model's properties should be retrieved. You can also retrieve specific properties by replacing the `*` with the property name for each property. For example: +### query.graph + ```ts const { data: posts } = await query.graph({ entity: "post", @@ -14927,6 +15245,20 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: [ + "id", + "title", + "product.id", + "product.title", + ], +}) +``` + In the example above, you retrieve only the `id` and `title` properties of the `product` linked to a `post`. ### Retrieve List Link Records @@ -14935,6 +15267,8 @@ If the linked data model has `isList` enabled in the link definition, pass in `f For example: +### query.graph + ```ts highlights={[["6"]]} const { data: posts } = await query.graph({ entity: "post", @@ -14946,6 +15280,19 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["6"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: [ + "id", + "title", + "products.*", + ], +}) +``` + In the example above, you retrieve all products linked to a post. ### Apply Filters and Pagination on Linked Records @@ -15005,6 +15352,8 @@ The returned `data` is similar to the following: ## Apply Filters +### query.graph + ```ts highlights={[["4"], ["5"], ["6"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15015,12 +15364,26 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["4"], ["5"], ["6"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + id: "post_123", + }, +}) +``` + The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records. In the example above, you filter the `post` records by the ID `post_123`. You can also filter by multiple values of a property. For example: +### query.graph + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15034,6 +15397,21 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + id: [ + "post_123", + "post_321", + ], + }, +}) +``` + In the example above, you filter the `post` records by multiple IDs. Filters don't apply on fields of linked data models from other modules. Refer to the [Retrieve Linked Records](#retrieve-linked-records) section for an alternative solution. @@ -15053,6 +15431,8 @@ Refer to the [Service Factory Reference](https://docs.medusajs.com/resources/ser #### Filter by Not Matching a Value +### query.graph + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15065,10 +15445,26 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + title: { + $ne: null, + }, + }, +}) +``` + In the example above, only posts that have a title are retrieved. #### Filter by Not Matching Multiple Values +### query.graph + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15081,10 +15477,26 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + title: { + $nin: ["My Post", "Another Post"], + }, + }, +}) +``` + In the example above, only posts that don't have the title `My Post` or `Another Post` are retrieved. #### Filter by a Range +### query.graph + ```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]} const startToday = new Date() startToday.setHours(0, 0, 0, 0) @@ -15104,12 +15516,35 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]} +const startToday = new Date() +startToday.setHours(0, 0, 0, 0) + +const endToday = new Date() +endToday.setHours(23, 59, 59, 59) + +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + published_at: { + $gt: startToday, + $lt: endToday, + }, + }, +}) +``` + In the example above, only posts that were published today are retrieved. #### Filter Text by Like Value This filter only applies to text-like properties, including `text`, `id`, and `enum` properties. +### query.graph + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15122,10 +15557,26 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + title: { + $like: "%My%", + }, + }, +}) +``` + In the example above, only posts that have the word `My` in their title are retrieved. #### Filter a Relation's Property +### query.graph + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15138,12 +15589,28 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + author: { + name: "John", + }, + }, +}) +``` + While it's not possible to filter by a linked data model's property, you can filter by a relation's property (that is, the property of a related data model that is defined in the same module). In the example above, only posts that have an author with the name `John` are retrieved. #### Filter by Relation Property Not Matching Value +### query.graph + ```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15167,7 +15634,32 @@ const { data: posts } = await query.graph({ }) ``` -When you need to filter by a relationship property whose value doesn't match a specific condition, you must use an `$or` operator that applies the following conditions: +### useQueryGraphStep + +```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + author: { + $or: [ + { + name: { + $eq: null, + }, + }, + { + name: { + $ne: "John", + }, + }, + ], + }, + }, +}) +``` + +To filter by a relationship property whose value doesn't match a specific condition, use an `$or` operator that applies the following conditions: 1. The relationship's property is not set. This is necessary to exclude posts that don't have an author. 2. The relationship's property is not equal to the specific value. @@ -15178,6 +15670,8 @@ So, in the example above, the query retrieves posts that either don't have an au ## Apply Pagination +### query.graph + ```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]} const { data: posts, @@ -15192,6 +15686,22 @@ const { }) ``` +### useQueryGraphStep + +```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]} +const { + data: posts, + metadata +} = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + pagination: { + skip: 0, + take: 10, + }, +}) +``` + The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records. To paginate the returned records, pass the following properties to `pagination`: @@ -15207,6 +15717,8 @@ When you provide the pagination fields, the `query.graph` method's returned obje ### Sort Records +### query.graph + ```ts highlights={[["5"], ["6"], ["7"]]} const { data: posts } = await query.graph({ entity: "post", @@ -15219,6 +15731,20 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["5"], ["6"], ["7"]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + pagination: { + order: { + name: "DESC", + }, + }, +}) +``` + Sorting doesn't work on fields of linked data models from other modules. To sort returned records, pass an `order` property to `pagination`. @@ -15238,6 +15764,8 @@ The `withDeleted` property is available from [Medusa v2.8.5](https://github.com/ For example: +### query.graph + ```ts highlights={[["4", "withDeleted", "Include deleted posts in the results."]]} const { data: posts } = await query.graph({ entity: "post", @@ -15246,12 +15774,24 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={[["4", "withDeleted", "Include deleted posts in the results."]]} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + withDeleted: true, +}) +``` + In the example above, you retrieve all posts, including deleted ones. ### Retrieve Only Deleted Records To retrieve only deleted records, you can add a `deleted_at` filter and set its value to not `null`. For example: +### query.graph + ```ts highlights={withDeletedHighlights} const { data: posts } = await query.graph({ entity: "post", @@ -15265,18 +15805,35 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts highlights={withDeletedHighlights} +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + deleted_at: { + $ne: null, + }, + }, + withDeleted: true, +}) +``` + In the example above, you retrieve only deleted posts by enabling the `withDeleted` property and adding a filter to only retrieve records where the `deleted_at` property is not `null`. *** -## Configure Query to Throw Errors +## Configure Query to Throw Error -By default, if Query doesn't find records matching your query, it returns an empty array. You can add an option to configure Query to throw an error when no records are found. +By default, if Query doesn't find records matching your query, it returns an empty array. You can configure Query to throw an error when no records are found. The `query.graph` method accepts as a second parameter an object that can have a `throwIfKeyNotFound` property. Its value is a boolean indicating whether to throw an error if no record is found when filtering by IDs. By default, it's `false`. For example: +### query.graph + ```ts const { data: posts } = await query.graph({ entity: "post", @@ -15289,7 +15846,22 @@ const { data: posts } = await query.graph({ }) ``` -In the example above, if no post is found with the ID `post_123`, Query will throw an error. This is useful to stop execution when a record is expected to exist. +### useQueryGraphStep + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + filters: { + id: "post_123", + }, + options: { + throwIfKeyNotFound: true, + }, +}) +``` + +In the example above, if no post is found with the ID `post_123`, Query throws an error. This is useful to stop execution when a record is expected to exist. ### Throw Error on Related Data Model @@ -15297,6 +15869,8 @@ The `throwIfKeyNotFound` option can also be used to throw an error if the ID of For example: +### query.graph + ```ts const { data: posts } = await query.graph({ entity: "post", @@ -15310,12 +15884,30 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title", "author.*"], + filters: { + id: "post_123", + author_id: "author_123", + }, + options: { + throwIfKeyNotFound: true, + } +}) +``` + In the example above, Query throws an error either if no post is found with the ID `post_123` or if it's found but its author ID isn't `author_123`. In the above example, it's assumed that a post belongs to an author, so it has an `author_id` property. However, this also works in the opposite case, where an author has many posts. For example: +### query.graph + ```ts const { data: posts } = await query.graph({ entity: "author", @@ -15331,10 +15923,435 @@ const { data: posts } = await query.graph({ }) ``` +### useQueryGraphStep + +```ts +const { data: posts } = useQueryGraphStep({ + entity: "author", + fields: ["id", "name", "posts.*"], + filters: { + id: "author_123", + posts: { + id: "post_123", + }, + }, + options: { + throwIfKeyNotFound: true, + } +}) +``` + In the example above, Query throws an error if no author is found with the ID `author_123` or if the author is found but doesn't have a post with the ID `post_123`. *** +## Cache Query Results + +### Prerequisites + +- [Caching Module installed with a provider.](https://docs.medusajs.com/resources/infrastructure-modules/caching#install-the-caching-module/index.html.md) + +Caching options are available from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +You can cache Query results to improve performance and reduce database load. To do that, you can pass a `cache` property in the second parameter of the `query.graph` method. + +For example, to enable caching for a query: + +### query.graph + +```ts highlights={[["6", "enable", "Enable caching for this query."]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true + } +}) +``` + +### useQueryGraphStep + +```ts highlights={[["6", "enable", "Enable caching for this query."]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true + } + } +}) +``` + +In this example, you enable caching of the query's results. The next time the same query is executed, the results are returned from the cache instead of querying the database. + +Refer to the [Caching Module documentation](https://docs.medusajs.com/resources/infrastructure-modules/caching/concepts#caching-best-practices/index.html.md) for best practices on caching. + +### Cache Properties + +`cache` is an object that accepts the following properties: + +- enable: (\`boolean\` | \`((args: any\[]) => boolean | undefined)\`) Whether to enable caching of query results. If a function is passed, it receives as a parameter the \`query.graph\` parameters, and returns a boolean indicating whether caching is enabled. +- key: (\`string\` | \`((args: any\[], cachingModule: ICachingModuleService) => string | Promise\)\`) The key to cache the query results with. If no key is provided, the Caching Module will generate the key from the \`query.graph\` parameters. + + If a function is passed, it receives the following properties: + + 1\. The parameters passed to \`query.graph\`. + + 2\. The \[Caching Module's service]\(!resources!/references/caching-service), which you can use to perform caching operations. + + The function must return a string indicating the cache key. +- tags: (\`string\[]\` | \`((args: any\[]) => string\[] | undefined)\`) The tags to associate with the cached results. Tags are useful to group related items. If no tag is provided, the Caching Module will generate relevant tags for the entity and its retrieved relations. + + If a function is passed, it receives as a parameter the \`query.index\` parameters, and returns an array of strings indicating the cache tags. +- ttl: (\`number\` | \`((args: any\[]) => number | undefined)\`) The time-to-live (TTL) for the cached results, in seconds. If no TTL is provided, the Caching Module Provider will receive the \[configured TTL of the Caching Module]\(!resources!/infrastructure-modules/caching#caching-module-options), or it will use its own default value. + + If a function is passed, it receives as a parameter the \`query.graph\` parameters, and returns a number indicating the TTL. +- autoInvalidate: (\`boolean\` | \`((args: any\[]) => boolean | undefined)\`) Whether to automatically invalidate the cached data when it expires. + + If a function is passed, it receives as a parameter the \`query.graph\` parameters, and returns a boolean indicating whether to automatically invalidate the cache. +- providers: (\`string\[]\` | \`((args: any\[]) => string\[] | undefined)\`) The IDs of the providers to use for caching. If not provided, the \[default Caching Module Provider]\(!resources!/infrastructure-modules/caching/providers#default-caching-module-provider) is used. If multiple providers are passed, the cache is stored and retrieved in those providers in order. + + If a function is passed, it receives as a parameter the \`query.graph\` parameters, and return an array of strings indicating the providers to use. + +### Set Cache Key + +By default, the Caching Module generates a cache key for a query based on the arguments passed to `query.graph`. The cache key is a unique key that the cached result is stored with. + +Alternatively, you can set a custom cache key for a query. This is useful if you want to manage invalidating the cache manually. + +To set the cache key of a query, pass the `cache.key` option: + +### query.graph + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: "products-123456", + // to disable auto invalidation: + // autoInvalidate: false, + } +}) +``` + +### useQueryGraphStep + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + key: "products-123456", + // to disable auto invalidation: + // autoInvalidate: false, + } + } +}) +``` + +In the example above, you cache the query results with the `products-123456` key. + +You should generate cache keys with the Caching Module service's [computeKey method](https://docs.medusajs.com/resources/references/caching-service#computeKey/index.html.md) to ensure that the key is unique and follows best practices. + +You can also pass a function as the value of `cache.key`: + +Passing a function to `cache.key` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). You can alternatively create a step that uses Query directly, and use it in the workflow. + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + key: async (args, cachingModuleService) => { + return await cachingModuleService.computeKey({ + ...args, + prefix: "products" + }) + } + } +}) +``` + +In the example above, you pass a function to `key`. It accepts two parameters: + +1. The arguments of `query.graph` passed as an array. +2. The [Caching Module's service](https://docs.medusajs.com/resources/references/caching-service/index.html.md). + +You generate the key using the [computeKey method of the Caching Module's service](https://docs.medusajs.com/resources/references/caching-service#computeKey/index.html.md). The query results will be cached with that key. + +### Set Cache Tags + +By default, the Caching Module generates relevant tags for a query based on the entity and its retrieved relations. Cache tags are useful to group related items together, allowing you to [retrieve](https://docs.medusajs.com/resources/references/caching-service#get/index.html.md) or [invalidate](https://docs.medusajs.com/resources/references/caching-service#clear/index.html.md) items by common tags. + +Alternatively, you can set the cache tags of a query manually. This is useful if you want to manage invalidating the cache manually, or you want to group related cached items with custom tags. + +To set the cache tags of a query, pass the `cache.tags` option: + +### query.graph + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: ["Product:list:*"], + } +}) +``` + +### useQueryGraphStep + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + tags: ["Product:list:*"], + // to disable auto invalidation: + // autoInvalidate: false, + } + } +}) +``` + +In the example above, you cache the query results with the `Product:list:*` tag. + +The cache tag must follow the [Caching Tags Convention](https://docs.medusajs.com/resources/infrastructure-modules/caching/concepts#caching-tags-convention/index.html.md) to be automatically invalidated. + +You can also pass a function as the value of `cache.tags`: + +Passing a function to `cache.tags` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). You can alternatively create a step that uses Query directly, and use it in the workflow. + +```ts highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + tags: (args) => { + const collectionId = args[0].filter?.collection_id + return [ + ...args, + collectionId ? `ProductCollection:${collectionId}` : undefined, + ] + }, + } +}) +``` + +In the example above, you use a function to determine the cache tags. The function accepts the arguments passed to `query.graph` as an array. + +Then, you add the `ProductCollection:id` tag if `collection_id` is passed in the query filters. + +### Set TTL + +By default, the Caching Module will pass the [configured time-to-live (TTL)](https://docs.medusajs.com/resources/infrastructure-modules/caching#caching-module-options/index.html.md) to the Caching Module Provider when caching data. The Caching Module Provider may also have its own default TTL. The cache isn't invalidated until the configured TTL passes. + +Alternatively, you can set a custom TTL for a query. This is useful if you want the cached data to be invalidated sooner or later than the default TTL. + +To set the TTL of the cached query results to a custom value, use the `cache.ttl` option: + +### query.graph + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + ttl: 100, // 100 seconds + } +}) +``` + +### useQueryGraphStep + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + ttl: 100, // 100 seconds + } + } +}) +``` + +In the example above, you set the TTL of the cached query result to `100` seconds. It will be invalidated after that time. + +You can also pass a function as the value of `cache.ttl`: + +Passing a function to `cache.ttl` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). You can alternatively create a step that uses Query directly, and use it in the workflow. + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + ttl: (args) => { + return args[0].filters.id === "test" ? 10 : 100 + } + } +}) +``` + +In the example above, you use a function to determine the TTL. The function accepts the arguments passed to `query.graph` as an array. + +Then, you set the TTL based on the ID of the product passed in the filters. + +### Set Auto Invalidation + +By default, the Caching Module automatically invalidates cached query results when the data changes. + +Alternatively, you can disable auto invalidation of cached query results. This is useful if you want to manage invalidating the cache manually. + +To configure invalidation behavior, use the `cache.autoInvalidate` option: + +### query.graph + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: false, + } +}) +``` + +### useQueryGraphStep + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + autoInvalidate: false, + } + } +}) +``` + +In this example, you disable auto invalidation of the query result. You must [invalidate](https://docs.medusajs.com/resources/references/caching-service#clear/index.html.md) the cached data manually. + +You can also pass a function as the value of `cache.autoInvalidate`: + +Passing a function to `cache.autoInvalidate` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). You can alternatively create a step that uses Query directly, and use it in the workflow. + +```ts highlights={[["7"], ["8"], ["9"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + autoInvalidate: (args) => { + return !args[0].fields.includes("custom_field") + } + } +}) +``` + +In the example above, you use a function to determine whether to invalidate the cached query result automatically. The function accepts the arguments passed to `query.graph` as an array. + +Then, you enable auto-invalidation only if the `fields` passed to `query.graph` don't include `custom_fields`. If this disables auto-invalidation, you must [invalidate](https://docs.medusajs.com/resources/references/caching-service#clear/index.html.md) the cached data manually. + +Learn more about automatic invalidation in the [Caching Module documentation](https://docs.medusajs.com/resources/infrastructure-modules/caching/concepts#automatic-cache-invalidation/index.html.md). + +### Set Caching Provider + +By default, the Caching Module uses the [default Caching Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers#default-caching-module-provider/index.html.md) to cache a query. + +Alternatively, you can set the caching provider to use for a query. This is useful if you have multiple caching providers configured, and you want to use a specific one for a query, or you want to specify a fallback provider. + +To configure the caching providers, use the `cache.providers` option: + +### query.graph + +```ts highlights={[["7"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], +}, { + cache: { + enable: true, + providers: ["caching-redis", "caching-memcached"] + } +}) +``` + +### useQueryGraphStep + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + providers: ["caching-redis", "caching-memcached"] + } + } +}) +``` + +In the example above, you specify the providers with ID `caching-redis` and `caching-memcached` to cache the query results. These IDs must match the IDs of the providers in `medusa-config.ts`. + +When you pass multiple providers, the cache is stored and retrieved in those providers in order. + +You can also pass a function as the value of `cache.providers`: + +Passing a function to `cache.providers` is only supported in `query.graph`, not in `useQueryGraphStep`. This is due to variable-related restrictions in workflows, as explained in the [Data Manipulation in Workflows guide](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). You can alternatively create a step that uses Query directly, and use it in the workflow. + +```ts highlights={[["10"], ["11"], ["12"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title"], + filters: { + id: "prod_123" + } +}, { + cache: { + enable: true, + providers: (args) => { + return args[0].filters.id === "test" ? ["caching-redis"] : ["caching-memcached"] + } + } +}) +``` + +In the example above, you use a function to determine the caching providers. The function accepts the arguments passed to `query.graph` as an array. + +Then, you set the providers based on the ID of the product passed in the filters. + +*** + ## Request Query Configurations For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that: @@ -16820,15 +17837,17 @@ Since modules are interchangeable, you have more control over Medusa’s archite There are different Infrastructure Module types including: -![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727095814/Medusa%20Book/architectural-modules_bj9bb9.jpg) +![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1759762284/Medusa%20Book/service-infra_k3fcy0.jpg) -- Analytics Module: Integrates a third-party service to track and analyze user interactions and system events. -- Cache Module: Defines the caching mechanism or logic to cache computational results. -- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events. -- Workflow Engine Module: Integrates a service to store and track workflow executions and steps. -- File Module: Integrates a storage service to handle uploading and managing files. -- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers. -- Locking Module: Integrates a service that manages access to shared resources by multiple processes or threads. +- [Analytics Module](https://docs.medusajs.com/resources/infrastructure-modules/analytics/index.html.md): Integrates a third-party service to track and analyze user interactions and system events. +- [Caching Module](https://docs.medusajs.com/resources/infrastructure-modules/caching/index.html.md): Defines the caching mechanism or logic to cache computational results. +- [Event Module](https://docs.medusajs.com/resources/infrastructure-modules/event/index.html.md): Integrates a pub/sub service to handle subscribing to and emitting events. +- [Workflow Engine Module](https://docs.medusajs.com/resources/infrastructure-modules/workflow-engine/index.html.md): Integrates a service to store and track workflow executions and steps. +- [File Module](https://docs.medusajs.com/resources/infrastructure-modules/file/index.html.md): Integrates a storage service to handle uploading and managing files. +- [Notification Module](https://docs.medusajs.com/resources/infrastructure-modules/notification/index.html.md): Integrates a third-party service or defines custom logic to send notifications to users and customers. +- [Locking Module](https://docs.medusajs.com/resources/infrastructure-modules/locking/index.html.md): Integrates a service that manages access to shared resources by multiple processes or threads. + +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. *** @@ -22379,7 +23398,7 @@ In a common Medusa application, requests go through four layers in the stack. In These layers of stack can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). -![Medusa application architecture diagram illustrating the HTTP layer flow: External clients (storefront and admin) send requests to API routes, which execute workflows containing business logic, which then interact with modules to perform data operations on PostgreSQL databases](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175296/Medusa%20Book/http-layer_sroafr.jpg) +![Medusa application architecture diagram illustrating the HTTP layer flow: External clients (storefront and admin) send requests to API routes, which execute workflows containing business logic, which then interact with modules to perform data operations on PostgreSQL databases](https://res.cloudinary.com/dza7lstvk/image/upload/v1759761784/Medusa%20Book/http-layer-new_hu0r3h.jpg) *** @@ -22389,7 +23408,7 @@ The Medusa application injects into each module, including your [custom modules] Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). -![Database layer architecture diagram showing how Medusa modules establish connections to PostgreSQL databases through injected database connections, enabling data persistence and retrieval operations](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175379/Medusa%20Book/db-layer_pi7tix.jpg) +![Database layer architecture diagram showing how Medusa modules establish connections to PostgreSQL databases through injected database connections, enabling data persistence and retrieval operations](https://res.cloudinary.com/dza7lstvk/image/upload/v1759761866/Medusa%20Book/db-layer-new_faeksx.jpg) *** @@ -22414,7 +23433,7 @@ You can replace any of the third-party services mentioned above to build your pr [Infrastructure Modules](https://docs.medusajs.com/resources/infrastructure-modules/index.html.md) integrate third-party services and systems that customize Medusa's infrastructure. Medusa has the following Infrastructure Modules: - [Analytics Module](https://docs.medusajs.com/resources/infrastructure-modules/analytics/index.html.md): Tracks and analyzes user interactions and system events with third-party analytic providers. You can integrate [PostHog](https://docs.medusajs.com/resources/infrastructure-modules/analytics/posthog/index.html.md) as the analytics provider. -- [Cache Module](https://docs.medusajs.com/resources/infrastructure-modules/cache/index.html.md): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Cache Module](https://docs.medusajs.com/resources/infrastructure-modules/cache/redis/index.html.md). +- [Caching Module](https://docs.medusajs.com/resources/infrastructure-modules/caching/index.html.md): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Caching Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers/redis/index.html.md). - [Event Module](https://docs.medusajs.com/resources/infrastructure-modules/event/index.html.md): A pub/sub system that allows you to subscribe to events and trigger them. You can integrate [Redis](https://docs.medusajs.com/resources/infrastructure-modules/event/redis/index.html.md) as the pub/sub system. - [File Module](https://docs.medusajs.com/resources/infrastructure-modules/file/index.html.md): Manages file uploads and storage, such as upload of product images. You can integrate [AWS S3](https://docs.medusajs.com/resources/infrastructure-modules/file/s3/index.html.md) for file storage. - [Locking Module](https://docs.medusajs.com/resources/infrastructure-modules/locking/index.html.md): Manages access to shared resources by multiple processes or threads, preventing conflict between processes and ensuring data consistency. You can integrate [Redis](https://docs.medusajs.com/resources/infrastructure-modules/locking/redis/index.html.md) for locking. @@ -22423,7 +23442,9 @@ You can replace any of the third-party services mentioned above to build your pr All of the third-party services mentioned above can be replaced to help you build your preferred architecture and ecosystem. -![Diagram illustrating the Infrastructure Modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175342/Medusa%20Book/service-arch_ozvryw.jpg) +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. + +![Diagram illustrating the Infrastructure Modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1759762284/Medusa%20Book/service-infra_k3fcy0.jpg) *** @@ -22431,7 +23452,7 @@ All of the third-party services mentioned above can be replaced to help you buil The following diagram illustrates Medusa's architecture including all its layers. -![Complete Medusa architecture overview showing the full technology stack: client applications (storefront and admin) connecting through HTTP layer to workflows, which coordinate with commerce and Infrastructure Modules to manage data operations, third-party integrations, and database persistence](https://res.cloudinary.com/dza7lstvk/image/upload/v1727174897/Medusa%20Book/architectural-diagram-full.jpg) +![Complete Medusa architecture overview showing the full technology stack: client applications (storefront and admin) connecting through HTTP layer to workflows, which coordinate with commerce and Infrastructure Modules to manage data operations, third-party integrations, and database persistence](https://res.cloudinary.com/dza7lstvk/image/upload/v1759762329/Medusa%20Book/diagram-full-new_znssjy.jpg) # Build with AI Assistants and LLMs @@ -22636,7 +23657,7 @@ Once you're done, run the following command to install the new dependencies: npm install ``` -In Medusa v1, you needed to install Medusa modules like the Cache, Event, or Pricing modules. +In Medusa v1, you needed to install Medusa modules like the Event, Product, or Pricing modules. These modules are now available out of the box, and you don't need to install or configure them separately. @@ -22739,7 +23760,7 @@ While the `plugins` configuration hasn't changed, plugins available in Medusa v1 In Medusa v1, you had to configure modules like Inventory, Stock Location, Pricing, and Product. These modules are now available out of the box, and you don't need to install or configure them separately. -For the Cache and Event modules, refer to the [Redis Cache Module](https://docs.medusajs.com/resources/infrastructure-modules/cache/redis/index.html.md) and [Redis Event Module](https://docs.medusajs.com/resources/infrastructure-modules/event/redis/index.html.md) documentations to learn how to configure them in v2 if you had them configured in v1. +For the Cache and Event modules, refer to the [Redis Caching Module Provider](https://docs.medusajs.com/resources/infrastructure-modules/caching/providers/redis/index.html.md) and [Redis Event Module](https://docs.medusajs.com/resources/infrastructure-modules/event/redis/index.html.md) documentations to learn how to configure them in v2 if you had them configured in v1. #### Feature Flags @@ -40167,6 +41188,10 @@ You'll now track the order placement event whenever an order is placed in your M In this guide, you’ll learn how to create a Cache Module. +{/* TODO add link */} + +The Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). [Create a Caching Module Provider](#) instead. + ## 1. Create Module Directory Start by creating a new directory for your module. For example, `src/modules/my-cache`. @@ -40341,6 +41366,8 @@ This module is helpful for development or when you’re testing out Medusa, but For production, it’s recommended to use modules like [Redis Cache Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/cache/redis/index.html.md). +The In-Memory Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Use the [Caching Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/index.html.md) instead. + *** ## Register the In-Memory Cache Module @@ -40377,6 +41404,8 @@ module.exports = defineConfig({ In this document, you'll learn what a Cache Module is and how to use it in your Medusa application. +The Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Use the [Caching Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/index.html.md) instead. + ## What is a Cache Module? A Cache Module is used to cache the results of computations such as price selection or various tax calculations. @@ -40441,7 +41470,7 @@ Medusa provides the following Cache Modules. You can use one of them, or [Create The Redis Cache Module uses Redis to cache data in your store. In production, it's recommended to use this module. -Our Cloud offering automatically provisions a Redis instance and configures the Redis Cache Module for you. Learn more in the [Redis](https://docs.medusajs.com/cloud/redis/index.html.md) Cloud documentation. +The Redis Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Use the [Redis Caching Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers/redis/index.html.md) instead. *** @@ -40454,10 +41483,6 @@ Our Cloud offering automatically provisions a Redis instance and configures the Add the module into the `modules` property of the exported object in `medusa-config.ts`: ```ts title="medusa-config.ts" highlights={highlights} -import { Modules } from "@medusajs/framework/utils" - -// ... - module.exports = defineConfig({ // ... modules: [ @@ -40505,6 +41530,1836 @@ Connection to Redis in module 'cache-redis' established ``` +# Caching Module Concepts + +In this guide, you'll learn about the main concepts of the [Caching Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/index.html.md), including cache keys, cache tags, and automatic cache invalidation. + +## Cache Keys + +Cache keys uniquely identify cached data in the caching service. The Caching Module automatically generates cache keys when you cache data with Query or the Index Module. + +### Custom Cache Keys + +When you cache custom data with the Caching Module's service, you can generate a cache key using the [computeKey](https://docs.medusajs.com/references/caching-service#computeKey/index.html.md) method. This method generates a unique key based on the data you want to cache. + +For example: + +```ts +const data = { id: "prod_123", title: "Product 123" } +const key = await cachingModuleService.computeKey(data) +await cachingModuleService.set({ + key, + tags: ["Product:prod_123", "Product:list:*"], + data +}) +``` + +The `computeKey` method takes an object as input and generates a unique key based on its properties. + +The generated key is a hash string that uniquely identifies the data. The has doesn't change based on the order of properties in the object. + +For example, the following two objects generate the same cache key: + +```ts +const key1 = await cachingModuleService.computeKey({ id: "prod_123", title: "Product 123" }) +const key2 = await cachingModuleService.computeKey({ title: "Product 123", id: "prod_123" }) + +console.log(key1 === key2) // true +``` + +### When to Use Custom Cache Keys? + +Use custom cache keys when you're caching custom data with the Caching Module's service. This ensures that the cached data is uniquely identified and can be retrieved or invalidated correctly. + +You can also pass a custom key to Query or the Index Module when caching data. This is useful when you want to use the key for custom invalidation or retrieval. + +Learn more about passing custom keys in the [Query guide](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query#set-cache-key/index.html.md). + +*** + +## Cache Tags + +Cache tags are useful for grouping cached data, making it easier to invalidate or retrieve related cached entries. + +When you cache data with the Query or Index Module, the Caching Module automatically generates cache tags based on the entity being queried and its retrieved relations. + +When you cache custom data with the Caching Module's service, you can pass custom tags to the `set` and `get` methods. + +### Caching Tags Convention + +The Caching Module generates cache tags in the following format: + +- `Entity:id`: Cache tag for a single record of an entity. For example, `Product:prod_123` for caching a single product with the ID `prod_123`. +- `Entity:list:*`: Cache tag for a list of records of an entity. For example, `Product:list:*` for caching a list of products. + +`Entity` is the pascal-cased name of the data model, which you pass as the first parameter to `model.define` when defining the model. + +When you use custom tags, ensure they adhere to the above convention. Otherwise, the Caching Module cannot automatically invalidate your cached data. You'll have to [invalidate](https://docs.medusajs.com/references/caching-service#clear/index.html.md) the cached data manually. + +For example: + +```ts +const key = await cachingModuleService.computeKey(data) +await cachingModuleService.set({ + key, + tags: ["Product:list:*", "Product:prod_123"], + data +}) +``` + +### When to Use Custom Tags? + +Use custom tags when you want to group cached data for custom invalidation or retrieval. + +Note that if your custom tags do not follow the [Caching Tags Convention](#caching-tags-convention), the Caching Module cannot automatically invalidate your cached data. You must manually [invalidate](https://docs.medusajs.com/references/caching-service#clear/index.html.md) the cached data when it changes. + +*** + +## Automatic Cache Invalidation + +### When is Cache Automatically Invalidated? + +The Caching Module automatically invalidates cached data when the underlying data changes through database operations such as create, update, or delete. + +For example, if you cache a list of products with the tag `Product:list:*` and a new product is created, the Caching Module automatically invalidates the cached list of products. + +This ensures that your application always serves fresh data and avoids serving stale or outdated information. + +The following table shows when the Caching Module invalidates cached data based on different database operations: + +|Database Operation|Invalidated Cache Tags| +|---|---| +|Create|\`Entity:list:\*\`| +|Update|\`Entity:\{id}\`| +|Delete|\`Entity:list:\*\`| + +### Which Data is Automatically Invalidated? + +The Caching Module automatically invalidates your cached data when: + +1. The data includes an `id` field. This is used internally to map the data to the corresponding cache tags. + - When retrieving data with Query or the Index Module, ensure the `id` field is included in the `fields` option. + +```ts +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], // Ensure 'id' is included, or pass '*' + options: { + cache: { + enable: true, + }, + }, +}) +``` + +- When caching custom data with the Caching Module's service, ensure the data object includes an `id` property. + +2. Custom tags follow the [Caching Tags Convention](#caching-tags-convention). + +```ts +const data = { id: "prod_123", title: "Product 123" } +const key = await cachingModuleService.computeKey(data) +await cachingModuleService.set({ + key, + tags: ["Product:prod_123", "Product:list:*"], + data +}) +``` + +3. The `autoInvalidate` option is not set or is set to `true`. This option is enabled by default. + +```ts +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + // This is enabled by default + // autoInvalidate: true, + }, + }, +}) +``` + +### Invalidation of Related Entities + +If the cached data includes relations, and the relation is updated, the Caching Module also invalidates the cache tags for the related entity. + +For example, consider the following Query usage: + +```ts +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title", "variants.*"], + options: { + cache: { + enable: true, + }, + }, +}) +``` + +If the product's variant is updated, the Caching Module invalidates the cache tags for both the `Product` and `ProductVariant` entities. + +### When to Disable Automatic Invalidation? + +Disabling automatic invalidation means the data remains in the cache until it expires (based on the TTL) or is manually invalidated. + +Consider disabling automatic invalidation in the following cases: + +1. You're caching data that rarely changes, such as a list of countries. +2. You want to manage cache invalidation manually, such as when caching data as part of a custom workflow where you want to control when the cache is invalidated. +3. You're caching data that does not belong to a Medusa data model, such as data from an external API or computed values. This data is not automatically invalidated by default. + +Disable automatic invalidation by setting the `autoInvalidate` option to `false` when caching data with Query, the Index Module, or the Caching Module's service. + +When you disable automatic invalidation, manually [invalidate](https://docs.medusajs.com/references/caching-service#clear/index.html.md) the cached data when it changes. + +*** + +## Caching Best Practices + +### Cache Rarely-Changing Data + +Cache data that is read frequently but changes infrequently, such as product information, categories, or static content. + +Caching such data can significantly improve performance and reduce database load. + +### Do Not Cache Dynamic Data + +Avoid caching data that changes frequently or is user-specific, such as shopping cart contents, user sessions, or product pricing. + +Caching such data can lead to inconsistencies and stale information being served to users. It also increases bandwidth and memory usage, as the cache is updated frequently. + +### Do Not Cache Frequently Updated Data + +Avoid caching data that is updated frequently, such as inventory levels or order statuses. + +Caching such data increases the overhead of cache invalidation and may lead to performance degradation. + + +# Create Memcached Caching Module Provider + +In this tutorial, you'll learn how to create a Memcached [Caching Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers/index.html.md) for your Medusa application. + +[Memcached](https://memcached.org/) is a high-performance, distributed memory caching system that speeds up dynamic web applications by reducing database load. + +By the end of this tutorial, you'll be able to cache data in your Medusa application using Memcached. + +Refer to the [Caching Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/index.html.md) documentation to learn more about caching in Medusa. + +![Diagram illustrating the Memcached caching module provider in a Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1759925175/Medusa%20Resources/memcached-summary_ztlanl.jpg) + +[Full Code](https://github.com/medusajs/examples/tree/main/memcached-caching): Find the complete code on GitHub + +*** + +### Prerequisites + + + +## 1. Install Memcached Client + +To interact with Memcached, you'll need to install the `memjs` client. You can install it in your Medusa project by running the following command: + +```bash npm2yarn +npm install memjs +``` + +*** + +## 2. Create Module Directory + +A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/memcached` in your Medusa project. + +*** + +## 3. Create Memcached Connection Loader + +Next, establish a connection to Memcached in your module using a [Loader](https://docs.medusajs.com/docs/learn/fundamentals/modules/loaders/index.html.md). A loader is an asynchronous function that runs when the module is initialized. It's useful for setting up connections to external services, such as databases or caching systems. + +Create the file `src/modules/memcached/loaders/connection.ts` with the following content: + +```ts title="src/modules/memcached/loaders/connection.ts" highlights={loaderHighlights} +import { LoaderOptions } from "@medusajs/framework/types" +import * as memjs from "memjs" + +export type ModuleOptions = { + serverUrls?: string[] + username?: string + password?: string + options?: memjs.ClientOptions + cachePrefix?: string + defaultTtl?: number // Default TTL in seconds + compression?: { + enabled?: boolean + threshold?: number // Minimum size in bytes to compress + level?: number // Compression level (1-9) + } +} + +export default async function connection({ + container, + options, +}: LoaderOptions) { + const logger = container.resolve("logger") + const { + serverUrls = ["127.0.0.1:11211"], + username, + password, + options: clientOptions + } = options || {} + + try { + logger.info("Connecting to Memcached...") + + // Create Memcached client + const client = memjs.Client.create(serverUrls.join(','), { + username, + password, + ...clientOptions, + }) + + // Test the connection + await new Promise((resolve, reject) => { + client.stats((err, stats) => { + if (err) { + logger.error("Failed to connect to Memcached:", err) + reject(err) + } else { + logger.info("Successfully connected to Memcached") + resolve() + } + }) + }) + + // Register the client in the container + container.register({ + memcachedClient: { + resolve: () => client, + }, + }) + + } catch (error) { + logger.error("Failed to initialize Memcached connection:", error) + throw error + } +} +``` + +You first define module options that are passed to the Memcached Module Provider. You'll set those up later when you [register the module in your Medusa application](#register-memcached-module-provider). The module accepts the following options: + +- `serverUrls`: An array of Memcached server URLs. Defaults to `["127.0.0.1:11211"]`. +- `username`: The username for authenticating with the Memcached server (if required). +- `password`: The password for authenticating with the Memcached server (if required). +- `options`: Additional options to pass to the Memcached client. +- `cachePrefix`: A prefix to use for all cache keys to avoid collisions. Defaults to `"medusa"`. +- `defaultTtl`: The default time-to-live (TTL) for cached items, in seconds. Defaults to `3600` (1 hour). +- `compression`: Configuration for data compression: + - `enabled`: Whether to enable compression. Defaults to `true`. + - `threshold`: The minimum size in bytes for data to be compressed. Defaults to `2048` (2KB). + - `level`: The compression level (1-9). Defaults to `6`. + +Then, export a loader function. This function receives an object with the following properties: + +- `container`: The [Module container](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md) that holds Framework and module-specific resources. +- `options`: The options passed to the module when it's registered in the Medusa application. + +In the loader, you create a Memcached client and test the connection. If the connection is successful, you register the client in the container, allowing you later to access it in the module's service. + +*** + +## 4. Create Memcached Module Provider Service + +You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can interact with third-party services to perform operations. + +In this step, you'll create the service of the Memcached Module Provider. This service must implement the `ICachingProviderService` and implement its methods. + +To create the service, create the file `src/modules/memcached/service.ts` with the following content: + +```ts title="src/modules/memcached/service.ts" highlights={serviceHighlights1} +import { ICachingProviderService } from "@medusajs/framework/types" +import * as memjs from "memjs" +import { ModuleOptions } from "./loaders/connection" + +type InjectedDependencies = { + memcachedClient: memjs.Client +} + +class MemcachedCachingProviderService implements ICachingProviderService { + static identifier = "memcached-cache" + + protected client: memjs.Client + protected options_: ModuleOptions + protected readonly CACHE_PREFIX: string + protected readonly TAG_PREFIX: string + protected readonly OPTIONS_PREFIX: string + protected readonly KEY_TAGS_PREFIX: string + protected readonly TAG_KEYS_PREFIX: string + protected readonly compressionEnabled: boolean + protected readonly compressionThreshold: number + protected readonly compressionLevel: number + protected readonly defaultTtl: number + + constructor( + { memcachedClient }: InjectedDependencies, + options: ModuleOptions + ) { + this.client = memcachedClient + this.options_ = options + + // Set all prefixes with the main prefix + const mainPrefix = options.cachePrefix || "medusa" + this.CACHE_PREFIX = `${mainPrefix}:` + this.TAG_PREFIX = `${mainPrefix}:tag:` + this.OPTIONS_PREFIX = `${mainPrefix}:opt:` + this.KEY_TAGS_PREFIX = `${mainPrefix}:key_tags:` + this.TAG_KEYS_PREFIX = `${mainPrefix}:tag_keys:` + + // Set compression options + this.compressionEnabled = options.compression?.enabled ?? true + this.compressionThreshold = options.compression?.threshold ?? 2048 // 2KB default + this.compressionLevel = options.compression?.level ?? 6 // Balanced compression + + // Set default TTL + this.defaultTtl = options.defaultTtl ?? 3600 // 1 hour default + } +} +``` + +You create the service that implements the `ICachingProviderService` interface. You define in the class some protected properties to hold dependencies, the Memcached client, and configuration options. + +You also define a constructor in the service. Service constructors accept two parameters: + +1. The Module container, from which you can resolve dependencies. In this case, you resolve the `memcachedClient` that you registered in the loader. +2. The options passed to the module when it's registered in the Medusa application. + +You'll get a type error at this point because you haven't implemented the methods of the `ICachingProviderService` interface yet. You'll implement them next, along with utility methods. + +### Compression Utility Methods + +Before implementing the caching methods, you'll implement two utility methods to handle data compression and decompression. + +These methods use the `zlib` library to compress data before storing it in Memcached and decompress it when retrieving it. This optimizes storage and network usage, especially for large data. + +First, add the following imports at the top of the file: + +```ts title="src/modules/memcached/service.ts" +import { deflate, inflate } from "zlib" +import { promisify } from "util" + +const deflateAsync = promisify(deflate) +const inflateAsync = promisify(inflate) +``` + +Then, add the following methods to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async compressData(data: string): Promise<{ + data: string; + compressed: boolean + }> { + if (!this.compressionEnabled || data.length < this.compressionThreshold) { + return { data, compressed: false } + } + + const buffer = Buffer.from(data, 'utf8') + const compressed = await deflateAsync(buffer) + const compressedData = compressed.toString('base64') + + // Only use compression if it actually reduces size + if (compressedData.length < data.length) { + return { data: compressedData, compressed: true } + } + + return { data, compressed: false } + } + + private async decompressData( + data: string, + compressed: boolean + ): Promise { + if (!compressed) { + return data + } + + const buffer = Buffer.from(data, 'base64') + const decompressed = await inflateAsync(buffer) + return decompressed.toString('utf8') + } +} +``` + +You define two private methods: + +- `compressData`: Takes a string `data` as input and compresses it using `zlib.deflate` if compression is enabled and the data size exceeds the defined threshold. It returns an object containing the (possibly compressed) data and a boolean indicating whether compression was applied. +- `decompressData`: Takes a string `data` and a boolean `compressed` as input. If `compressed` is `true`, it decompresses the data using `zlib.inflate`. Otherwise, it returns the data as is. + +### Set Methods + +Next, you'll implement the [set method](https://docs.medusajs.com/references/caching-module-provider#set/index.html.md) of the `ICachingProviderService` interface. This method stores a value in the cache with an optional time-to-live (TTL) and associated tags. + +#### setKeyTags Helper Method + +Before implementing the method, you'll implement the helper method `setKeyTags`. Since Memcached doesn't support tagging natively, you'll need to manage tags manually by storing mappings between keys and tags. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async setKeyTags( + key: string, + tags: string[], + setOptions: memjs.InsertOptions + ): Promise { + const timestamp = Math.floor(Date.now() / 1000) + const tagNamespaces: Record = {} + const operations: Promise[] = [] + + // Batch all namespace operations + for (const tag of tags) { + const tagKey = this.TAG_PREFIX + tag + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + + // Get namespace version + operations.push( + (async () => { + const result = await this.client.get(tagKey) + if (!result.value) { + tagNamespaces[tag] = timestamp.toString() + await this.client.add(tagKey, timestamp.toString()) + } else { + tagNamespaces[tag] = result.value.toString() + } + })() + ) + + // Add key to tag's key list + operations.push( + (async () => { + const result = await this.client.get(tagKeysKey) + let keys: string[] = [] + if (result.value) { + keys = JSON.parse(result.value.toString()) as string[] + } + if (!keys.includes(key)) { + keys.push(key) + await this.client.set(tagKeysKey, JSON.stringify(keys), setOptions) + } + })() + ) + } + + await Promise.all(operations) + + // Store the tag namespaces for this key + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const serializedTags = JSON.stringify(tagNamespaces) + await this.client.set(keyTagsKey, serializedTags, setOptions) + } +} +``` + +The `setKeyTags` method takes a cache `key`, an array of `tags`, and Memcached `setOptions`. + +In the method, you: + +1. Get the current timestamp to use as a namespace version for tags. +2. Iterate over the provided tags and for each tag: + - Retrieve the current namespace version from Memcached. If it doesn't exist, set it to the current timestamp. + - Retrieve the list of keys associated with the tag. If the current key is not in the list, add it and update the list in Memcached. +3. Store the mapping of tags and their namespace versions for the given key. + +You'll use this method in the `set` method to manage tags when storing a value. + +#### set Method + +Add the following `set` method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + async set({ + key, + data, + ttl, + tags, + options, + }: { + key: string + data: any + ttl?: number + tags?: string[] + options?: { autoInvalidate?: boolean } + }): Promise { + const prefixedKey = this.CACHE_PREFIX + key + const serializedData = JSON.stringify(data) + const setOptions: memjs.InsertOptions = {} + + // Use provided TTL or default TTL + setOptions.expires = ttl ?? this.defaultTtl + + // Compress data if enabled + const { + data: finalData, + compressed + } = await this.compressData(serializedData) + + // Batch operations for better performance + const operations: Promise[] = [ + this.client.set(prefixedKey, finalData, setOptions) + ] + + // Always store options (including compression flag) to allow checking them later + const optionsKey = this.OPTIONS_PREFIX + key + const optionsData = { ...options, compressed } + operations.push( + this.client.set(optionsKey, JSON.stringify(optionsData), setOptions) + ) + + // Handle tags using namespace simulation with batching + if (tags && tags.length > 0) { + operations.push(this.setKeyTags(key, tags, setOptions)) + } + + await Promise.all(operations) + } +} +``` + +The `set` method takes an object with the following properties: + +- `key`: The cache key. +- `data`: The value to cache. +- `ttl`: Optional time-to-live for the cached value, in seconds. +- `tags`: Optional array of tags to associate with the cached value. +- `options`: Optional additional options, such as `autoInvalidate`, which indicates whether to automatically invalidate the cache based on tags. + +In the method, you: + +1. Store the value in Memcached with the specified TTL. You store the compressed data if compression is enabled and necessary. +2. Store the options (including whether the data was compressed) in a separate key to allow checking them later. + - This is necessary to determine whether the data can be automatically invalidated based on tags. +3. If tags are provided, call the `setKeyTags` method to set up the tag mappings. + +You batch all operations using `Promise.all` for better performance. + +### Get Methods + +Next, you'll implement the [get method](https://docs.medusajs.com/references/caching-module-provider#get/index.html.md) of the `ICachingProviderService` interface. This method retrieves a value from the cache either by its key or by associated tags. + +Before implementing the method, you need two helper methods. + +#### validateKeyByTags Helper Method + +The first helper method validates that a key is still valid based on its tags. You'll use this method when retrieving a value by its key to ensure that the value hasn't been invalidated by tag updates. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async validateKeyByTags(key: string, tags: string[]): Promise { + if (!tags || tags.length === 0) { + return true // No tags to validate + } + + // Get the stored tag namespaces for this key + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const keyTagsResult = await this.client.get(keyTagsKey) + + if (!keyTagsResult.value) { + return true // No stored tags, assume valid + } + + const storedTags = JSON.parse(keyTagsResult.value.toString()) + + // Batch all namespace checks for better performance + const tagKeys = Object.keys(storedTags).map(tag => this.TAG_PREFIX + tag) + const tagResults = await Promise.all( + tagKeys.map(tagKey => this.client.get(tagKey)) + ) + + // Check if any tag namespace is missing or changed + for (let i = 0; i < tagResults.length; i++) { + const tag = Object.keys(storedTags)[i] + const tagResult = tagResults[i] + + if (tagResult.value) { + const currrentTag = tagResult.value.toString() + // If the namespace has changed since the key was stored, it's invalid + if (currrentTag !== storedTags[tag]) { + return false + } + } else { + // Namespace doesn't exist - this means it was reclaimed after being incremented + // This indicates the tag was cleared, so the key should be considered invalid + return false + } + } + + return true + } +} +``` + +The `validateKeyByTags` method accepts a cache `key` and an array of `tags`. + +In the method, you: + +- Retrieve from Memcached the tag namespaces for the given key. + - If no tags are stored, you assume the key is valid and return `true`. +- For each stored tag, retrieve its current namespace version from Memcached. +- Compare the current namespace version with the stored version. If any tag's namespace has changed or is missing, return `false`, indicating that the key is invalid. Otherwise, return `true`. + +#### getByTags Helper Method + +The second helper method retrieves the cached data associated with specified tags. You'll use this method when retrieving a value by tags. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async getByTags(tags: string[]): Promise { + if (!tags || tags.length === 0) { + return null + } + + // Get all keys associated with each tag + const tagKeysOperations = tags.map(tag => { + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + return this.client.get(tagKeysKey) + }) + + const tagKeysResults = await Promise.all(tagKeysOperations) + + // Collect all unique keys from all tags + const allKeys = new Set() + for (const result of tagKeysResults) { + if (result.value) { + const keys = JSON.parse(result.value.toString()) as string[] + keys.forEach(key => allKeys.add(key)) + } + } + + if (allKeys.size === 0) { + return null + } + + // Get all cached data for the collected keys + const dataOperations = Array.from(allKeys).map(async key => { + const prefixedKey = this.CACHE_PREFIX + key + const result = await this.client.get(prefixedKey) + + if (!result.value) { + return { key, data: null } + } + + const dataString = result.value.toString() + + // Check if data is compressed + const optionsKey = this.OPTIONS_PREFIX + key + const optionsResult = await this.client.get(optionsKey) + let compressed = false + + if (optionsResult.value) { + const options = JSON.parse(optionsResult.value.toString()) + compressed = options.compressed || false + } + + // Decompress if needed + const decompressedData = await this.decompressData(dataString, compressed) + return { key, data: JSON.parse(decompressedData) } + }) + + const dataResults = await Promise.all(dataOperations) + + // Filter out null data and validate tags for each key + const validData: any[] = [] + for (const { key, data } of dataResults) { + if (data !== null) { + // Validate that this key is still valid for the requested tags + const isValid = await this.validateKeyByTags(key, tags) + if (isValid) { + validData.push(data) + } + } + } + + return Object.keys(validData).length > 0 ? validData : null + } +} +``` + +The `getByTags` method takes an array of `tags`. + +In the method, you: + +- Retrieve from Memcached all keys associated with each tag. You collect all unique keys from the results. +- For each unique key, retrieve the cached data with its options. + - If the data is compressed, decompress it using the `decompressData` method. +- Validate each key using the `validateKeyByTags` method to ensure that the data is still valid based on its tags. +- Return an array of valid cached data or `null` if no valid data is found. + +You can now implement the `get` method using these helper methods. + +#### get Method + +Add the `get` method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + async get({ + key, + tags, + }: { + key?: string + tags?: string[] + }): Promise { + if (key) { + const prefixedKey = this.CACHE_PREFIX + key + + // Get the stored tags for this key and validate them + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const keyTagsResult = await this.client.get(keyTagsKey) + + if (keyTagsResult.value) { + const storedTags = JSON.parse(keyTagsResult.value.toString()) + const tagNames = Object.keys(storedTags) + + const isValid = await this.validateKeyByTags(key, tagNames) + if (!isValid) { + return null + } + } + + const result = await this.client.get(prefixedKey) + if (result.value) { + const dataString = result.value.toString() + + // Check if data is compressed (look for compression flag in options) + const optionsKey = this.OPTIONS_PREFIX + key + const optionsResult = await this.client.get(optionsKey) + let compressed = false + + if (optionsResult.value) { + const options = JSON.parse(optionsResult.value.toString()) + compressed = options.compressed || false + } + + // Decompress if needed + const decompressedData = await this.decompressData(dataString, compressed) + return JSON.parse(decompressedData) + } + return null + } + + if (tags && tags.length > 0) { + // Retrieve data by tags - get all keys associated with the tags + return await this.getByTags(tags) + } + + return null + } +} +``` + +The `get` method takes an object with optional `key` and `tags` properties. + +In the method: + +- If a `key` is provided, you give it a higher priority and retrieve the cached value for that key. + - You first check if the key has associated tags and validate them using the `validateKeyByTags` method, ensuring the cached value is still valid. + - You decompress the data if it was stored in a compressed format. + - If the key is not found or is invalid, you return `null`. Otherwise, you return the cached value. +- If no `key` is provided but `tags` are, you call the `getByTags` method to retrieve all cached values associated with the provided tags. +- If neither `key` nor `tags` are provided, you return `null`. + +### Clear Methods + +Finally, you'll implement the [clear method](https://docs.medusajs.com/references/caching-module-provider#clear/index.html.md) of the `ICachingProviderService` interface. + +The `clear` method removes a cached value either by its key or by associated tags. It also receives an optional `options` parameter to control whether to automatically invalidate the cache based on tags: + +- If `options` isn't set, you clear all cached values associated with the provided tags. +- If `options.autoInvalidate` is `true`, you only invalidate the keys of the provided tags whose options allow automatic invalidation. + +Before implementing the `clear` method, you'll implement four helper methods to handle tag and key invalidation. + +#### removeKeysFromTag Helper Method + +The `removeKeysFromTag` method removes a list of keys from a tag's key list in Memcached. This is useful when invalidating by key or when clearing keys associated with a tag. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async removeKeysFromTag(tag: string, keysToRemove: string[]): Promise { + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + const tagKeysResult = await this.client.get(tagKeysKey) + + if (!tagKeysResult.value) { + return // No keys to remove + } + + let keys: string[] = JSON.parse(tagKeysResult.value.toString()) as string[] + + // Remove the specified keys + keys = keys.filter(key => !keysToRemove.includes(key)) + + if (keys.length === 0) { + // If no keys left, delete the tag keys entry + await this.client.delete(tagKeysKey) + } else { + // Update the tag keys list + await this.client.set(tagKeysKey, JSON.stringify(keys)) + } + } +} +``` + +The `removeKeysFromTag` method takes a `tag` and an array of `keysToRemove`. + +In the method, you: + +1. Retrieve the list of keys associated with the tag from Memcached. +2. Filter out the keys that need to be removed. +3. If no keys are left, delete the tag's key list entry. Otherwise, update the list in Memcached. + +#### clearByKey Helper Method + +Next, you'll implement the `clearByKey` method. It removes a cached value by its key and updates the associated tags to remove the key from their key lists. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async clearByKey(key: string): Promise { + // Get the key's tags before deleting to clean up tag key lists + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const keyTagsResult = await this.client.get(keyTagsKey) + + const operations: Promise[] = [ + this.client.delete(this.CACHE_PREFIX + key), + this.client.delete(this.OPTIONS_PREFIX + key), + this.client.delete(keyTagsKey) + ] + + // If the key has tags, remove it from tag key lists + if (keyTagsResult.value) { + const storedTags = JSON.parse(keyTagsResult.value.toString()) + const tagNames = Object.keys(storedTags) + + // Batch tag cleanup operations + const tagCleanupOperations = tagNames.map(async tag => { + await this.removeKeysFromTag(tag, [key]) + }) + operations.push(...tagCleanupOperations) + } + + await Promise.all(operations) + } +} +``` + +The `clearByKey` method takes a cache `key`. + +In the method, you: + +1. Retrieve the tags associated with the key before deleting it. +2. Delete the cached value, its options, and the key's tag mapping from Memcached. +3. If the key has associated tags, call the `removeKeysFromTag` method for each tag to remove the key from their key lists. +4. Batch all operations using `Promise.all` for better performance. + +#### clearByTags Helper Method + +Next, you'll implement the `clearByTags` method. It removes all cached values associated with the provided tags. You'll use this method when the `options` parameter isn't set in the `clear` method. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async clearByTags(tags: string[]): Promise { + const operations = tags.map(async tag => { + const tagKey = this.TAG_PREFIX + tag + const result = await this.client.increment(tagKey, 1) + if (result === null) { + // Key doesn't exist, create it with current timestamp + const timestamp = Math.floor(Date.now() / 1000) + await this.client.add(tagKey, timestamp.toString()) + } + }) + + await Promise.all(operations) + } +} +``` + +The `clearByTags` method takes an array of `tags`. + +In the method, you loop over the tags to increment their namespace versions in Memcached. If a tag's namespace doesn't exist, you create it with the current timestamp. + +By incrementing the namespace version, you effectively invalidate the tag and all associated keys, as they will no longer match the stored namespace versions. The namespace version will also be replaced in Memcached after being reclaimed. + +Learn more about this invalidation strategy in [Memcached's documentation](https://docs.memcached.org/userguide/usecases/). + +#### clearByTagsWithAutoInvalidate Helper Method + +The `clearByTagsWithAutoInvalidate` method removes cached values associated with the provided tags, but only for keys whose options allow automatic invalidation. You'll use this method when the `options.autoInvalidate` parameter is `true` in the `clear` method. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async clearByTagsWithAutoInvalidate(tags: string[]): Promise { + for (const tag of tags) { + // Get the list of keys associated with this tag + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + const tagKeysResult = await this.client.get(tagKeysKey) + + if (!tagKeysResult.value) { + continue + } + + const keys = JSON.parse(tagKeysResult.value.toString()) as string[] + + // Check each key's options and delete if autoInvalidate is true + const keysToRemove: string[] = [] + for (const key of keys) { + const optionsKey = `${this.OPTIONS_PREFIX}${key}` + const optionsResult = await this.client.get(optionsKey) + + if (optionsResult.value) { + const options = JSON.parse(optionsResult.value.toString()) + if (options.autoInvalidate) { + // Delete the key and its associated data + await this.client.delete(this.CACHE_PREFIX + key) + await this.client.delete(optionsKey) + await this.client.delete(`${this.KEY_TAGS_PREFIX}${key}`) + keysToRemove.push(key) + } + } + } + + // Remove deleted keys from the tag's key list + if (keysToRemove.length > 0) { + await this.removeKeysFromTag(tag, keysToRemove) + } + } + } +} +``` + +You define the `clearByTagsWithAutoInvalidate` method, which takes an array of `tags`. + +In the method, you loop over the tags to: + +1. Retrieve the list of keys associated with each tag from Memcached. +2. For each key, retrieve its options and check if `autoInvalidate` is `true`. +3. If `autoInvalidate` is `true`, delete the cached value, its options, and its tag mapping from Memcached. You also keep track of the keys that were deleted. +4. After processing all keys for a tag, call the `removeKeysFromTag` method to remove the deleted keys from the tag's key list. + +#### clear Method + +Finally, add the `clear` method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + async clear({ + key, + tags, + options, + }: { + key?: string + tags?: string[] + options?: { autoInvalidate?: boolean } + }): Promise { + if (key) { + await this.clearByKey(key) + } + + if (tags?.length) { + if (!options) { + // Clear all items with the specified tags + await this.clearByTags(tags) + } else if (options.autoInvalidate) { + // Clear only items with autoInvalidate option set to true + await this.clearByTagsWithAutoInvalidate(tags) + } + } + } +} +``` + +The `clear` method takes an object with optional `key`, `tags`, and `options` properties. + +In the method: + +- If a `key` is provided, you call the `clearByKey` method to remove the cached value and update associated tags. +- If `tags` are provided: + - If `options` isn't set, you call the `clearByTags` method to invalidate all cached values associated with the tags. + - If `options.autoInvalidate` is `true`, you call the `clearByTagsWithAutoInvalidate` method to invalidate only the keys whose options allow automatic invalidation. + +You've now implemented all methods of the `ICachingProviderService` interface in the `MemcachedCachingProviderService` class. + +*** + +## 5. Export Memcached Module Provider Definition + +The final piece of a module provider is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa which module this provider belongs to, its loaders, and its service. + +Create the file `src/modules/memcached/index.ts` with the following content: + +```ts title="src/modules/memcached/index.ts" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" +import MemcachedCachingProviderService from "./service" +import connection from "./loaders/connection" + +export default ModuleProvider(Modules.CACHING, { + services: [MemcachedCachingProviderService], + loaders: [connection], +}) +``` + +You use the `ModuleProvider` function from the Modules SDK to create the module provider's definition. It accepts two parameters: + +1. The module this provider belongs to. In this case, the `Modules.CACHING` module. +2. An object with the provider's `services` and `loaders`. + +*** + +## 6. Register Memcached Module Provider + +The last step is to register the Memcached Module Provider in your Medusa application. + +### Enable Caching Feature Flag + +First, enable the [Caching Module's feature flag](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching#1-enable-caching-feature-flag/index.html.md) by setting the following environment variable: + +```bash +MEDUSA_FF_CACHING=true +``` + +### Register Memcached Module Provider + +Then, in `medusa-config.ts`, add a new entry in the `modules` array to register the Memcached Module Provider: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + in_memory: { + enable: true + }, + providers: [ + { + resolve: "./src/modules/memcached", + id: "caching-memcached", + // Optional, makes this the default caching provider + is_default: true, + options: { + serverUrls: process.env.MEMCACHED_SERVERS?.split(',') || + ["127.0.0.1:11211"], + // add other optional options here... + }, + }, + // other caching providers... + ], + } + } + ] +}) +``` + +You register the `@medusajs/medusa/caching` module and add the Memcached Module Provider to its `providers` array. + +You pass the options to configure the Memcached client and the module's behavior. These are the same [options you defined in the ModuleOptions type in the loader](#3-create-memcached-connection-loader). + +### Add Environment Variables + +Make sure you set the necessary environment variables in your `.env` file. For example: + +```bash +MEMCACHED_SERVERS=127.0.0.1:11211 # Comma-separated list of Memcached server URLs +# Add other optional variables as needed +``` + +You set the `MEMCACHED_SERVERS` variable to specify the Memcached server URLs. You can also set other optional variables like `MEMCACHED_USERNAME` and `MEMCACHED_PASSWORD` based on your use case. + +*** + +## Test the Memcached Caching Provider + +To test that the Memcached Caching Provider is working, start the Medusa application with the following command: + +```bash npm2yarn +npm run dev +``` + +You'll see in the logs that the Memcached connection is established successfully: + +```bash +info: Connecting to Memcached... +info: Successfully connected to Memcached +``` + +If you set the `is_default` option to `true` in the provider registration, the Memcached Caching Provider will be used for all caching operations in the Medusa application. + +### Create Test API Route + +To verify that the caching is working, you can create a simple [API route](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md) that retrieves data with caching. + +To create an API route, create a new file at `src/api/test-cache/route.ts` with the following content: + +```ts title="src/api/test-cache/route.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const query = req.scope.resolve("query") + + // Test caching with a simple query + const { data } = await query.graph({ + entity: "product", + fields: ["id", "title", "handle"], + }, { + cache: { + enable: true, + // For testing purposes + key: "test-cache-products", + // If you didn't set is_default to true, uncomment the following line + // providers: ["caching-memcached"], + } + }) + + res.status(200).json({ + message: "Products retrieved with Memcached caching", + data, + }) +} +``` + +This creates a `GET` route at `/test-cache` that retrieves products using Query with caching enabled. The `key` so that you can easily check the cached data in Memcached for testing purposes. + +Then, send a `GET` request to the `/test-cache` endpoint: + +```bash +curl http://localhost:9000/test-cache +``` + +You'll receive the list of products in the response. + +You can then check that the data is cached in Memcached using the [memcached-cli](https://github.com/pd4d10/memcached-cli) tool. + +First, establish a connection to your Memcached server: + +```bash +npx memcached-cli localhost:11211 # Replace with your Memcached server URL +``` + +Then, retrieve the cached data using the key you specified in the API route: + +```bash +get medusa:test-cache-products +``` + +Notice that you prefix the key with `medusa:`, which is the default prefix unless you set the `keyPrefix` option in the provider registration. + +*** + +## Next Steps + +If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth understanding of all the concepts you've used in this guide and more. + +To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md). + +### Troubleshooting + +If you encounter issues during your development, check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/index.html.md). + +### Getting Help + +If you encounter issues not covered in the troubleshooting guides: + +1. Visit the [Medusa GitHub repository](https://github.com/medusajs/medusa) to report issues or ask questions. +2. Join the [Medusa Discord community](https://discord.gg/medusajs) for real-time support from community members. + + +# Migrate from Cache Module to Caching Module + +In this guide, you'll learn how to migrate from the deprecated [Cache Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/cache/index.html.md) to the new [Caching Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/index.html.md). + +The Caching Module is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +1. Set up a Cache Module in `medusa-config.ts`. +2. Used the Cache Module's service in their code. + +If you haven't done either of these, you don't need to migrate to the Caching Module. You can refer to the [Caching Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching#install-the-caching-module/index.html.md) to learn how to set it up. + +## Why Migrate to the Caching Module? + +The Caching Module provides improved performance, flexibility, and scalability compared to the deprecated Cache Module. + +It also offers a better developer experience, making it easier to cache Query results and other data. + +Additionally, the Caching Module supports registering multiple caching providers, such as Redis and Memcached, allowing you to use different caching backends in your application. + +*** + +## Architectural Module Changes + +The Cache Module implements caching logic, including integration with third-party caching services like Redis. For example, the Redis Cache Module handled connecting to the Redis server and performing caching operations. + +The Caching Module, on the other hand, follows a provider-based architecture. The Caching Module provides the interface you use to manage cached data, while Caching Module Providers implement the actual caching logic. You can choose which provider to use, such as the Redis Caching Module Provider or a custom provider you create. + +This separation of concerns allows for greater flexibility and extensibility. You can easily switch between different caching providers or create your own custom provider without modifying the core Caching Module. + +![Diagram illustrating the change in architecture from Cache Module to Caching Module](https://res.cloudinary.com/dza7lstvk/image/upload/v1759845565/Medusa%20Resources/cache-to-caching_yxaped.jpg) + +*** + +## How to Migrate to the Caching Module + +### Prerequisites + +- [Updated your Medusa application to v2.11.0 or later](https://github.com/medusajs/medusa/releases/tag/v2.11.0) + +### 1. Remove the Cache Module + +The first step is to remove the Cache Module from your `medusa-config.ts` file. + +For example, if you set up the Redis Cache Module, remove the following code from `medusa-config.ts`: + +```ts title="medusa-config.ts" highlights={[["5"], ["6"], ["7"], ["8"], ["9"], ["10"]]} +module.exports = defineConfig({ + // ... + modules: [ + // REMOVE THE FOLLOWING LINES + { + resolve: "@medusajs/medusa/cache-redis", + options: { + redisUrl: process.env.CACHE_REDIS_URL, + }, + }, + ], +}) +``` + +### 2. Install and Register the Caching Module + +The Caching Module is installed by default in your application. To use it, enable the caching feature flag and register the module in your `medusa-config.ts` file. + +Refer to the [Caching Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching#install-the-caching-module/index.html.md) for setup instructions. + +### 3. (Optional) Update Your Code to Use the Caching Module's Service + +If you're using the Cache Module's service in your code, update it to use the Caching Module's service instead. + +#### Container Key Change + +Previously, you resolved the Cache Module's service using the `Modules.CACHE` or `cache` key. + +Now, use the `Modules.CACHING` or `caching` key to resolve the Caching Module's service. For example: + +```ts +const cachingModuleService = container.resolve(Modules.CACHING) +// or +const cachingModuleService = container.resolve("caching") +``` + +#### Method Changes + +The Caching Module's service has similar methods to the Cache Module's service: + +1. `get` -> Use the Caching Module's [get method](https://docs.medusajs.com/references/caching-service#get/index.html.md). +2. `set` -> Use the Caching Module's [set method](https://docs.medusajs.com/references/caching-service#set/index.html.md). +3. `invalidate` -> Use the Caching Module's [clear method](https://docs.medusajs.com/references/caching-service#clear/index.html.md). + +### 4. (Optional) Create Custom Caching Module Provider + +If you have a custom Cache Module, recreate it as a custom Caching Module Provider. For example, recreate a custom Memcached Cache Module as a Caching Module Provider. + +The Caching Module Provider's service has similar methods to the Cache Module's service. Refer to the [Create Caching Module Provider guide](https://docs.medusajs.com/references/caching-module-provider/index.html.md) for instructions on creating a custom Caching Module Provider. + +*** + +## Understand Caching Changes + +With the Cache Module, you handled caching and invalidation manually. + +The Caching Module can now handle caching and invalidation automatically for certain operations, such as caching Query results. You can still use the Caching Module's service to cache custom data. + +Learn more about the Caching Module in the [Caching Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/index.html.md). + + +# Caching Module + +In this guide, you'll learn about the Caching Module and its providers. + +The Caching Module is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). It replaces the deprecated [Cache Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/cache/index.html.md). + +## What is the Caching Module? + +The Caching Module provides functionality to cache data in your Medusa application, improving performance and reducing latency for frequently accessed data. + +For example, Medusa uses the Caching Module to cache product information, and you can cache custom data such as brand information. + +The Caching Module stores and retrieves cached data using the caching service you integrate, such as [Redis](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers/redis/index.html.md) or [Memcached](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/index.html.md). This provides flexibility in customizing your Medusa application's infrastructure to meet your performance and scalability requirements. + +![Diagram illustrating the Caching Module architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1759846791/Medusa%20Resources/caching-overview_tz91tw.jpg) + +### Caching Module vs Cache Module + +Before Medusa v2.11.0, you used the [Cache Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/cache/index.html.md) to cache data. The Cache Module is now deprecated and has been replaced by the Caching Module. + +If you're using the Cache Module in your application, refer to the [migrate to the Caching Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/migrate-cache/index.html.md). + +*** + +## Install the Caching Module + +The Caching Module is installed by default in your application. To use it, enable the caching feature flag and register the module in your `medusa-config.ts` file. + +{/* + +Caching features are enabled by default for Cloud users. Learn more in the [Medusa Cache](!cloud!/cache) guide. + + */} + +### 1. Enable Caching Feature Flag + +The caching feature is currently behind a feature flag. To enable it, set the `MEDUSA_FF_CACHING` environment variable to `true` in your `.env` file: + +```bash +MEDUSA_FF_CACHING=true +``` + +This enables you to use the Caching Module and activates caching features in Medusa's core. + +### 2. Register the Caching Module + +Next, add the Caching Module to the `modules` property of the exported object in `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + resolve: "@medusajs/caching-redis", + id: "caching-redis", + // Optional, makes this the default caching provider + is_default: true, + options: { + redisUrl: process.env.CACHE_REDIS_URL, + // more options... + }, + }, + ] + } + } + ] +}) +``` + +This registers the Caching Module in your application with the [Redis Caching Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers/redis/index.html.md). + +The Caching Module requires at least one Caching Module Provider to be registered. If you do not register any providers, the Caching Module throws an error when your application starts. + +### What is a Caching Module Provider? + +A Caching Module Provider implements the underlying logic for caching data, such as integrating third-party caching services. The Caching Module then uses the registered Caching Module Provider to handle caching operations. + +Refer to the [Caching Module Providers guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers/index.html.md) to learn more about Caching Module Providers in Medusa, and how to configure the default provider. + +- [Redis](https://docs.medusajs.com/infrastructure-modules/caching/providers/redis/index.html.md) +- [Memcached](https://docs.medusajs.com/infrastructure-modules/caching/guides/memcached/index.html.md) + +### What Data is Cached by Default? + +After you enable the Caching Module, Medusa automatically caches data for several business-critical APIs to boost performance and throughput. This includes all cart-related operations, where the following data is cached: + +- Regions +- Promotion codes +- Variant price sets +- Variants +- Shipping options +- Sales channels +- Customers + +*** + +## How to Use the Caching Module + +You can cache data with the Caching Module in two ways: by caching data retrieved with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) or the [Index Module](https://docs.medusajs.com/docs/learn/fundamentals/module-links/index-module/index.html.md), or by directly using the [Caching Module's service](https://docs.medusajs.com/references/caching-service/index.html.md). + +### 1. Caching with Query or Index Module + +You can cache results from Query and the Index Module by passing the `cache` option to the `query.graph` and `query.index` methods, or to the `useQueryGraphStep` in workflows. + +For example: + +```ts highlights={[["14"], ["15"], ["16"]]} +import { + createWorkflow, + WorkflowResponse +} from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +export const workflow = createWorkflow( + "workflow-1", + () => { + const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + }, + }, + }) + + return new WorkflowResponse(products) + } +) +``` + +The `useQueryGraphStep` accepts an `options.cache` property that enables and configures caching of the results. + +When caching is enabled, the Caching Module stores the results in the underlying caching service (for example, Redis). Subsequent calls to the same query retrieve the results from the cache, improving performance. + +Query and the Index Module accept other caching options. Learn more in the [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query#cache-query-results/index.html.md) and [Index Module](https://docs.medusajs.com/docs/learn/fundamentals/module-links/index-module#cache-index-module-results/index.html.md) guides. + +### 2. Caching with the Caching Module's Service + +You can also use the Caching Module's service directly to cache custom data in your Medusa application. + +For example, resolve the Caching Module's service in a workflow step and use its methods to cache brand information: + +```ts highlights={cachingHighlights} +import { Modules } from "@medusajs/framework/utils" +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" + +type StepInput = { + filters: Record +} + +export const getBrandStep = createStep( + "get-brand-step", + async (input: StepInput, { container }) => { + const cachingModuleService = container.resolve(Modules.CACHING) + const brandModuleService = container.resolve("brand") + + const cacheKey = await cachingModuleService.computeKey(input.filters) + const cachedValue = await cachingModuleService.get({ + tags: ["brand"] + }) + + if (cachedValue) { + return new StepResponse(cachedValue) + } + + const brand = await brandModuleService.getBrand(input.filters) + + await cachingModuleService.set({ + key: cacheKey, + tags: [`Brand:${brand.id}`], + data: brand + }) + + return new StepResponse(brand) + } +) +``` + +In the example above, you create a step that resolves the Caching Module's service from the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md). + +Then, you use the `get` method of the service to retrieve a cached value with the tag `"brand"`. You can also use other methods such as `set` to cache a value and `clear` to remove a cached value. + +Learn about other methods and options of the Caching Module in the [Use Caching Module](https://docs.medusajs.com/references/caching-service/index.html.md) guide. + +### Which Caching Method Should You Use? + +|Caching Method|When to Use| +|---|---| +|Query or Index Module|Ideal for standard data retrieval scenarios, such as fetching products or custom data.| +|Caching Module's Service|Suitable for caching computed values, external API responses, or to control caching behavior.| + +Caching data with Query or the Index Module is ideal when retrieving standard data, such as products or custom entities. The Caching Module automatically handles cache keys and invalidation for you. + +If you need to cache custom data like computed values or external API responses, or you want finer control over caching behavior, use the Caching Module's service directly. In this case, you must manage cache keys yourself, though you can still enable automatic invalidation using the service's `set` method. + +*** + +## Caching Module Options + +You can pass the following options to the Caching Module when registering it in your `medusa-config.ts` file: + +|Option|Description|Default| +|---|---|---| +|\`ttl\`|A number indicating the default time-to-live (TTL) in seconds for cached items. After this period, cached items will be removed from the cache. +This number is passed to the underlying caching provider if no TTL is specified when caching data.|\`3600\`| +|\`providers\`|An array of caching providers to use. This allows you to configure multiple caching providers for different use cases.|No providers by default| + +*** + +## Caching Concepts + +To learn more about caching concepts such as cache keys, cache tags, and automatic cache invalidation, refer to the [Caching Module Concepts guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/concepts/index.html.md). + + +# Caching Module Providers + +In this guide, you'll learn about Caching Module Providers in Medusa, including how to configure and use them in your Medusa application. + +## What is a Caching Module Provider? + +A Caching Module Provider implements the logic for caching data, such as integrating third-party caching services. The [Caching Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/index.html.md) then uses the registered Caching Module Providers to handle caching data. + +Medusa provides the [Redis Caching Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers/redis/index.html.md) that you can use in development and production. You can also [Create a Caching Provider](https://docs.medusajs.com/references/caching-module-provider/index.html.md). + +- [Redis](https://docs.medusajs.com/infrastructure-modules/caching/providers/redis/index.html.md) +- [Memcached](https://docs.medusajs.com/infrastructure-modules/caching/guides/memcached/index.html.md) + +*** + +## Default Caching Module Provider + +You can register multiple Caching Module Providers and specify which one to use as the default. The Caching Module uses the default provider for all caching operations unless you specify a specific provider. + +### How is the Default Provider Selected? + +The Caching Module determines the default Caching Module Provider based on the following scenarios: + +|Scenario|Default Provider| +|---|---|---| +|One provider is registered.|The registered provider.| +|Multiple providers and one of them has an |The provider with the | + +If none of the above scenarios apply, the Caching Module throws an error during startup indicating that no default provider is configured. + +### Setting the Default Caching Module Provider + +To specify a provider as the default, you can set its `is_default` option to `true` when registering it in the `provider` array of the Caching Module. + +For example: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + id: "caching-redis", + resolve: "@medusajs/caching-redis", + is_default: true, // Set as the default provider + options: { + // Redis options... + }, + }, + { + id: "caching-memcached", + resolve: "./path/to/your/memcached-provider", + options: { + // Memcached options... + }, + } + ] + } + } + ] +}) +``` + +In this example, the Redis Caching Module Provider is set as the default provider by setting `is_default: true`. The Memcached Caching Module Provider is also registered but not set as the default. + +*** + +## Caching with Specific Providers + +Whether you're caching data with Query, the Index Module, or directly using the Caching Module's service, you can specify an array of provider IDs to use for that specific caching operation. + +For example, considering you have the [above configuration](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching#setting-the-default-caching-module-provider/index.html.md) with both Redis and Memcached providers registered, you can specify which provider to use when caching data: + +### Query / Index Module + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + providers: ["caching-memcached"] // Specify Memcached provider + }, + }, +}) +``` + +### Caching Module Service + +```ts highlights={[["6"]]} +const cachingModuleService = container.resolve(Modules.CACHING) + +await cachingModuleService.set({ + key: "product-list", + data: products, + providers: ["caching-memcached"] // Specify Memcached provider +}) +``` + +In this example, both Query and the Caching Module's service use the Memcached provider, overriding the default Redis provider. + +The ID you pass is the same ID you specified in `medusa-config.ts` when registering the provider. + + +# Redis Caching Module Provider + +The Redis Caching Module Provider is a robust caching solution that leverages [Redis](https://redis.io/) to store cached data. Redis offers high performance, scalability, and data persistence, making it an ideal choice for production environments. + +The Caching Module and its providers are available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +*** + +## Register the Redis Caching Module + +### Prerequisites + +- [Redis installed and Redis server running](https://redis.io/docs/getting-started/installation/) + +To use the Redis Caching Module Provider, you need to register it in the `providers` array of the Caching Module in your `medusa-config.ts`. + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + resolve: "@medusajs/caching-redis", + id: "caching-redis", + // Optional, makes this the default caching provider + is_default: true, + options: { + redisUrl: process.env.CACHE_REDIS_URL, + // more options... + }, + }, + // other caching providers... + ], + }, + }, + ], +}) +``` + +Notice that you pass an `id` property to the provider. The provider will be registered with that ID, which you can use to explicitly [specify the provider when caching data](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers#how-to-use-the-caching-module/index.html.md). + +### Environment Variables + +Make sure to add the following environment variable: + +```bash +CACHE_REDIS_URL=redis://localhost:6379 +``` + +### Redis Caching Module Options + +|Option|Description|Default| +|---|---|---| +|\`redisUrl\`|The connection URL for the Redis server.|Required. An error is thrown if not provided.| +|\`ttl\`|A number indicating the default time-to-live (TTL) in seconds for cached items. After this period, cached items will be removed from the cache.|\`3600\`| +|\`prefix\`|A string to prefix all cache keys with. This is useful for namespacing your cache keys, especially when sharing a Redis instance with other applications or modules.|No prefix by default| +|\`compressionThreshold\`|A number indicating the size threshold in bytes above which cached items will be compressed before being stored in Redis. This helps save memory when caching large items.|\`1024\`| + +*** + +## Test the Redis Caching Module + +You can test the Redis Caching Module by caching data using the Query or Index Module, or by directly using the Caching Module's service, as described in the [Caching Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/caching/providers#how-to-use-the-caching-module/index.html.md). + +If you don't set the Redis Caching Module Provider as the default, you can explicitly specify its provider ID `caching-redis` when caching data with Query, the Index Module, or directly with the Caching Module's service. + +`caching-redis` is the ID you set in the `medusa-config.ts` file when registering the Redis Caching Module Provider. + +For example, you can create a workflow in `src/workflows/cache-products.ts` that caches products using the Redis Caching Module Provider: + +```ts title="src/workflows/cache-products.ts" highlights={[["14"], ["15"], ["16"], ["17"]]} +import { + createWorkflow, + WorkflowResponse +} from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +export const cacheProductsWorkflow = createWorkflow( + "cache-products", + () => { + const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + providers: ["caching-redis"], + }, + }, + }) + + return new WorkflowResponse(products) + } +) +``` + +Next, execute that workflow in an [API route](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). For example, create a route at `src/api/cache-product/route.ts` with the following content: + +```ts title="src/api/cache-product/route.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" +import { cacheProductsWorkflow } from "../../workflows/cache-products" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const { result } = await cacheProductsWorkflow(req.scope) + .run({}) + + res.status(200).json(result) +} +``` + +Finally, start your Medusa server with the following command: + +```bash npm2yarn +npm run dev +``` + +Then, make a `GET` request to the `/cache-product` endpoint: + +```bash +curl http://localhost:9000/cache-product +``` + +You should receive a response with the list of products. The first time you make this request, the products will be fetched from the database and cached in Redis. Subsequent requests will retrieve the products from the cache, resulting in improved performance. + + # How to Create an Event Module In this guide, you’ll learn how to create an Event Module. @@ -41021,7 +43876,7 @@ Then, you use the `retrieveFile` method of the File Module to retrieve the URL o *** -### What is a File Module Provider? +## What is a File Module Provider? A File Module Provider implements the underlying logic of handling uploads and downloads of assets, such as integrating third-party services. The File Module then uses the registered File Module Provider to handle file operations. @@ -42103,14 +44958,16 @@ The Analytics Module exposes functionalities to track and analyze user interacti - [Local](https://docs.medusajs.com/infrastructure-modules/analytics/local/index.html.md) - [PostHog](https://docs.medusajs.com/infrastructure-modules/analytics/posthog/index.html.md) -## Cache Module +## Caching Module -A Cache Module is used to cache the results of computations such as price selection or various tax calculations. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/cache/index.html.md). +The Caching Module provides functionality to cache data in your Medusa application, improving performance and reducing latency for frequently accessed data. -The following Cache modules are provided by Medusa. You can also create your own cache module as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/cache/create/index.html.md). +The following Caching modules are provided by Medusa. You can also create a custom Caching Module Provider as explained in the [Create Caching Module Provider guide](#). -- [In-Memory](https://docs.medusajs.com/infrastructure-modules/cache/in-memory/index.html.md) -- [Redis](https://docs.medusajs.com/infrastructure-modules/cache/redis/index.html.md) +The Caching Module is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). It replaces the deprecated [Cache Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/infrastructure-modules/cache/index.html.md). + +- [Redis](https://docs.medusajs.com/infrastructure-modules/caching/providers/redis/index.html.md) +- [Memcached](#) *** diff --git a/www/apps/cloud/app/projects/page.mdx b/www/apps/cloud/app/projects/page.mdx index 0776c044cf..b290baa92f 100644 --- a/www/apps/cloud/app/projects/page.mdx +++ b/www/apps/cloud/app/projects/page.mdx @@ -70,7 +70,7 @@ Your existing Medusa application doesn't need specific configurations to be depl - Create the necessary [server and worker instances](!docs!/learn/production/worker-mode). - Scale your Medusa application's resources based on the traffic it receives. - Set up and configure production resources and modules for your Medusa application: - - [Redis Cache Module](!resources!/infrastructure-modules/cache/redis) + - [Redis Caching Module Provider](!resources!/infrastructure-modules/caching/providers/redis) - [Redis Event Module](!resources!/infrastructure-modules/event/redis) - [Redis Locking Module Provider](!resources!/infrastructure-modules/locking/redis) - [Redis Workflow Engine Module](!resources!/infrastructure-modules/workflow-engine/redis) @@ -78,6 +78,12 @@ Your existing Medusa application doesn't need specific configurations to be depl So, make sure to remove any of these modules from your `medusa-config.ts` file, unless you want to use custom options for them. In that case, you're expected to manually set up and manage those resources externally and configure them in your Medusa application. + + +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. If you're still using the Cache Module, make sure to remove it from your `medusa-config.ts` file as well. + + + ### Project Creation Steps To create a project from an existing Medusa application: diff --git a/www/apps/cloud/app/redis/page.mdx b/www/apps/cloud/app/redis/page.mdx index 17256fba12..29bfd3d4d4 100644 --- a/www/apps/cloud/app/redis/page.mdx +++ b/www/apps/cloud/app/redis/page.mdx @@ -21,7 +21,7 @@ Each project environment has a dedicated Redis server. These Redis servers are e Medusa automatically configures your Medusa application hosted on Cloud to use Redis-based Infrastructure Modules, including: - [Redis Event Module](!resources!/infrastructure-modules/event/redis) -- [Redis Cache Module](!resources!/infrastructure-modules/cache/redis) +- [Redis Caching Module Provider](!resources!/infrastructure-modules/caching/providers/redis) - [Redis Locking Module Provider](!resources!/infrastructure-modules/locking/redis) - [Redis Workflow Engine Module](!resources!/infrastructure-modules/workflow-engine/redis) @@ -33,6 +33,12 @@ If you're using a Medusa version before v2.7.0, contact support for assistance i + + +The Caching Module was introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) to replace the deprecated Cache Module. + + + ### Access Redis Configurations on Cloud Since Cloud is a managed service, you can't directly access your Redis instance or its configurations. Medusa also doesn't expose the Redis instance connection or configuration details. diff --git a/www/apps/resources/app/infrastructure-modules/cache/create/page.mdx b/www/apps/resources/app/infrastructure-modules/cache/create/page.mdx index 45d110ae1e..3dda0e23a2 100644 --- a/www/apps/resources/app/infrastructure-modules/cache/create/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/cache/create/page.mdx @@ -14,6 +14,14 @@ export const metadata = { In this guide, you’ll learn how to create a Cache Module. +{/* TODO add link */} + + + +The Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). [Create a Caching Module Provider](#) instead. + + + ## 1. Create Module Directory Start by creating a new directory for your module. For example, `src/modules/my-cache`. diff --git a/www/apps/resources/app/infrastructure-modules/cache/in-memory/page.mdx b/www/apps/resources/app/infrastructure-modules/cache/in-memory/page.mdx index 280910882d..e0f55f8f97 100644 --- a/www/apps/resources/app/infrastructure-modules/cache/in-memory/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/cache/in-memory/page.mdx @@ -12,6 +12,12 @@ This module is helpful for development or when you’re testing out Medusa, but For production, it’s recommended to use modules like [Redis Cache Module](../redis/page.mdx). + + +The In-Memory Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Use the [Caching Module](../../caching/page.mdx) instead. + + + --- ## Register the In-Memory Cache Module diff --git a/www/apps/resources/app/infrastructure-modules/cache/page.mdx b/www/apps/resources/app/infrastructure-modules/cache/page.mdx index 8d4445cf10..1b32c8248e 100644 --- a/www/apps/resources/app/infrastructure-modules/cache/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/cache/page.mdx @@ -8,6 +8,12 @@ export const metadata = { In this document, you'll learn what a Cache Module is and how to use it in your Medusa application. + + +The Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Use the [Caching Module](../caching/page.mdx) instead. + + + ## What is a Cache Module? A Cache Module is used to cache the results of computations such as price selection or various tax calculations. diff --git a/www/apps/resources/app/infrastructure-modules/cache/redis/page.mdx b/www/apps/resources/app/infrastructure-modules/cache/redis/page.mdx index 202425d703..7f50db836d 100644 --- a/www/apps/resources/app/infrastructure-modules/cache/redis/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/cache/redis/page.mdx @@ -8,9 +8,9 @@ export const metadata = { The Redis Cache Module uses Redis to cache data in your store. In production, it's recommended to use this module. - + -Our Cloud offering automatically provisions a Redis instance and configures the Redis Cache Module for you. Learn more in the [Redis](!cloud!/redis) Cloud documentation. +The Redis Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Use the [Redis Caching Module Provider](../../caching/providers/redis/page.mdx) instead. @@ -32,10 +32,6 @@ export const highlights = [ ] ```ts title="medusa-config.ts" highlights={highlights} -import { Modules } from "@medusajs/framework/utils" - -// ... - module.exports = defineConfig({ // ... modules: [ diff --git a/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx new file mode 100644 index 0000000000..348f9bd844 --- /dev/null +++ b/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx @@ -0,0 +1,247 @@ +export const metadata = { + title: `Caching Module Concepts`, +} + +# {metadata.title} + +In this guide, you'll learn about the main concepts of the [Caching Module](../page.mdx), including cache keys, cache tags, and automatic cache invalidation. + +## Cache Keys + +Cache keys uniquely identify cached data in the caching service. The Caching Module automatically generates cache keys when you cache data with Query or the Index Module. + +### Custom Cache Keys + +When you cache custom data with the Caching Module's service, you can generate a cache key using the [computeKey](/references/caching-service#computeKey) method. This method generates a unique key based on the data you want to cache. + +For example: + +```ts +const data = { id: "prod_123", title: "Product 123" } +const key = await cachingModuleService.computeKey(data) +await cachingModuleService.set({ + key, + tags: ["Product:prod_123", "Product:list:*"], + data +}) +``` + +The `computeKey` method takes an object as input and generates a unique key based on its properties. + +The generated key is a hash string that uniquely identifies the data. The has doesn't change based on the order of properties in the object. + +For example, the following two objects generate the same cache key: + +```ts +const key1 = await cachingModuleService.computeKey({ id: "prod_123", title: "Product 123" }) +const key2 = await cachingModuleService.computeKey({ title: "Product 123", id: "prod_123" }) + +console.log(key1 === key2) // true +``` + +### When to Use Custom Cache Keys? + +Use custom cache keys when you're caching custom data with the Caching Module's service. This ensures that the cached data is uniquely identified and can be retrieved or invalidated correctly. + +You can also pass a custom key to Query or the Index Module when caching data. This is useful when you want to use the key for custom invalidation or retrieval. + +Learn more about passing custom keys in the [Query guide](!docs!/learn/fundamentals/module-links/query#set-cache-key). + +--- + +## Cache Tags + +Cache tags are useful for grouping cached data, making it easier to invalidate or retrieve related cached entries. + +When you cache data with the Query or Index Module, the Caching Module automatically generates cache tags based on the entity being queried and its retrieved relations. + +When you cache custom data with the Caching Module's service, you can pass custom tags to the `set` and `get` methods. + +### Caching Tags Convention + +The Caching Module generates cache tags in the following format: + +- `Entity:id`: Cache tag for a single record of an entity. For example, `Product:prod_123` for caching a single product with the ID `prod_123`. +- `Entity:list:*`: Cache tag for a list of records of an entity. For example, `Product:list:*` for caching a list of products. + + + +`Entity` is the pascal-cased name of the data model, which you pass as the first parameter to `model.define` when defining the model. + + + +When you use custom tags, ensure they adhere to the above convention. Otherwise, the Caching Module cannot automatically invalidate your cached data. You'll have to [invalidate](/references/caching-service#clear) the cached data manually. + +For example: + +```ts +const key = await cachingModuleService.computeKey(data) +await cachingModuleService.set({ + key, + tags: ["Product:list:*", "Product:prod_123"], + data +}) +``` + +### When to Use Custom Tags? + +Use custom tags when you want to group cached data for custom invalidation or retrieval. + +Note that if your custom tags do not follow the [Caching Tags Convention](#caching-tags-convention), the Caching Module cannot automatically invalidate your cached data. You must manually [invalidate](/references/caching-service#clear) the cached data when it changes. + +--- + +## Automatic Cache Invalidation + +### When is Cache Automatically Invalidated? + +The Caching Module automatically invalidates cached data when the underlying data changes through database operations such as create, update, or delete. + +For example, if you cache a list of products with the tag `Product:list:*` and a new product is created, the Caching Module automatically invalidates the cached list of products. + +This ensures that your application always serves fresh data and avoids serving stale or outdated information. + +The following table shows when the Caching Module invalidates cached data based on different database operations: + + + + + + Database Operation + + + Invalidated Cache Tags + + + + + + + Create + + + `Entity:list:*` (`Product:list:*`) + + + + + Update + + + `Entity:{id}`, `Entity:list:*` if the list includes the updated record. (`Product:prod_123`, `Product:list:*`) + + + + + Delete + + + `Entity:list:*` (`Product:list:*`) + + + +
+ +### Which Data is Automatically Invalidated? + +The Caching Module automatically invalidates your cached data when: + +1. The data includes an `id` field. This is used internally to map the data to the corresponding cache tags. + - When retrieving data with Query or the Index Module, ensure the `id` field is included in the `fields` option. + +```ts +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], // Ensure 'id' is included, or pass '*' + options: { + cache: { + enable: true, + }, + }, +}) +``` + + - When caching custom data with the Caching Module's service, ensure the data object includes an `id` property. +2. Custom tags follow the [Caching Tags Convention](#caching-tags-convention). + +```ts +const data = { id: "prod_123", title: "Product 123" } +const key = await cachingModuleService.computeKey(data) +await cachingModuleService.set({ + key, + tags: ["Product:prod_123", "Product:list:*"], + data +}) +``` + +3. The `autoInvalidate` option is not set or is set to `true`. This option is enabled by default. + +```ts +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + // This is enabled by default + // autoInvalidate: true, + }, + }, +}) +``` + +### Invalidation of Related Entities + +If the cached data includes relations, and the relation is updated, the Caching Module also invalidates the cache tags for the related entity. + +For example, consider the following Query usage: + +```ts +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title", "variants.*"], + options: { + cache: { + enable: true, + }, + }, +}) +``` + +If the product's variant is updated, the Caching Module invalidates the cache tags for both the `Product` and `ProductVariant` entities. + +### When to Disable Automatic Invalidation? + +Disabling automatic invalidation means the data remains in the cache until it expires (based on the TTL) or is manually invalidated. + +Consider disabling automatic invalidation in the following cases: + +1. You're caching data that rarely changes, such as a list of countries. +2. You want to manage cache invalidation manually, such as when caching data as part of a custom workflow where you want to control when the cache is invalidated. +3. You're caching data that does not belong to a Medusa data model, such as data from an external API or computed values. This data is not automatically invalidated by default. + +Disable automatic invalidation by setting the `autoInvalidate` option to `false` when caching data with Query, the Index Module, or the Caching Module's service. + +When you disable automatic invalidation, manually [invalidate](/references/caching-service#clear) the cached data when it changes. + +--- + +## Caching Best Practices + +### Cache Rarely-Changing Data + +Cache data that is read frequently but changes infrequently, such as product information, categories, or static content. + +Caching such data can significantly improve performance and reduce database load. + +### Do Not Cache Dynamic Data + +Avoid caching data that changes frequently or is user-specific, such as shopping cart contents, user sessions, or product pricing. + +Caching such data can lead to inconsistencies and stale information being served to users. It also increases bandwidth and memory usage, as the cache is updated frequently. + +### Do Not Cache Frequently Updated Data + +Avoid caching data that is updated frequently, such as inventory levels or order statuses. + +Caching such data increases the overhead of cache invalidation and may lead to performance degradation. diff --git a/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx new file mode 100644 index 0000000000..2091959751 --- /dev/null +++ b/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx @@ -0,0 +1,1107 @@ +import { Prerequisites, Card } from "docs-ui" +import { Github } from "@medusajs/icons" + +export const metadata = { + title: `Create Memcached Caching Module Provider`, +} + +# {metadata.title} + +In this tutorial, you'll learn how to create a Memcached [Caching Module Provider](../../providers/page.mdx) for your Medusa application. + +[Memcached](https://memcached.org/) is a high-performance, distributed memory caching system that speeds up dynamic web applications by reducing database load. + +By the end of this tutorial, you'll be able to cache data in your Medusa application using Memcached. + + + +Refer to the [Caching Module](../../page.mdx) documentation to learn more about caching in Medusa. + + + +![Diagram illustrating the Memcached caching module provider in a Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1759925175/Medusa%20Resources/memcached-summary_ztlanl.jpg) + + + +--- + + + +## 1. Install Memcached Client + +To interact with Memcached, you'll need to install the `memjs` client. You can install it in your Medusa project by running the following command: + +```bash npm2yarn +npm install memjs +``` + +--- + +## 2. Create Module Directory + +A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/memcached` in your Medusa project. + +--- + +## 3. Create Memcached Connection Loader + +Next, establish a connection to Memcached in your module using a [Loader](!docs!/learn/fundamentals/modules/loaders). A loader is an asynchronous function that runs when the module is initialized. It's useful for setting up connections to external services, such as databases or caching systems. + +Create the file `src/modules/memcached/loaders/connection.ts` with the following content: + +export const loaderHighlights = [ + ["5", "serverUrls", "Memcached server URLs"], + ["6", "username", "Username for authentication"], + ["7", "password", "Password for authentication"], + ["8", "options", "Additional Memcached client options"], + ["9", "cachePrefix", "Prefix for cache keys"], + ["10", "defaultTtl", "Default time-to-live for cached items"], + ["11", "compression", "Compression settings"], + ["12", "enable", "Enable compression"], + ["13", "threshold", "Minimum size to compress"], + ["14", "level", "Compression level"], + ["34", "client", "Create Memcached client"], + ["42", "stats", "Test the connection to Memcached"], + ["54", "register", "Register the client in the container"], +] + +```ts title="src/modules/memcached/loaders/connection.ts" highlights={loaderHighlights} +import { LoaderOptions } from "@medusajs/framework/types" +import * as memjs from "memjs" + +export type ModuleOptions = { + serverUrls?: string[] + username?: string + password?: string + options?: memjs.ClientOptions + cachePrefix?: string + defaultTtl?: number // Default TTL in seconds + compression?: { + enabled?: boolean + threshold?: number // Minimum size in bytes to compress + level?: number // Compression level (1-9) + } +} + +export default async function connection({ + container, + options, +}: LoaderOptions) { + const logger = container.resolve("logger") + const { + serverUrls = ["127.0.0.1:11211"], + username, + password, + options: clientOptions + } = options || {} + + try { + logger.info("Connecting to Memcached...") + + // Create Memcached client + const client = memjs.Client.create(serverUrls.join(','), { + username, + password, + ...clientOptions, + }) + + // Test the connection + await new Promise((resolve, reject) => { + client.stats((err, stats) => { + if (err) { + logger.error("Failed to connect to Memcached:", err) + reject(err) + } else { + logger.info("Successfully connected to Memcached") + resolve() + } + }) + }) + + // Register the client in the container + container.register({ + memcachedClient: { + resolve: () => client, + }, + }) + + } catch (error) { + logger.error("Failed to initialize Memcached connection:", error) + throw error + } +} +``` + +You first define module options that are passed to the Memcached Module Provider. You'll set those up later when you [register the module in your Medusa application](#register-memcached-module-provider). The module accepts the following options: + +- `serverUrls`: An array of Memcached server URLs. Defaults to `["127.0.0.1:11211"]`. +- `username`: The username for authenticating with the Memcached server (if required). +- `password`: The password for authenticating with the Memcached server (if required). +- `options`: Additional options to pass to the Memcached client. +- `cachePrefix`: A prefix to use for all cache keys to avoid collisions. Defaults to `"medusa"`. +- `defaultTtl`: The default time-to-live (TTL) for cached items, in seconds. Defaults to `3600` (1 hour). +- `compression`: Configuration for data compression: + - `enabled`: Whether to enable compression. Defaults to `true`. + - `threshold`: The minimum size in bytes for data to be compressed. Defaults to `2048` (2KB). + - `level`: The compression level (1-9). Defaults to `6`. + +Then, export a loader function. This function receives an object with the following properties: + +- `container`: The [Module container](!docs!/learn/fundamentals/modules/container) that holds Framework and module-specific resources. +- `options`: The options passed to the module when it's registered in the Medusa application. + +In the loader, you create a Memcached client and test the connection. If the connection is successful, you register the client in the container, allowing you later to access it in the module's service. + +--- + +## 4. Create Memcached Module Provider Service + +You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can interact with third-party services to perform operations. + +In this step, you'll create the service of the Memcached Module Provider. This service must implement the `ICachingProviderService` and implement its methods. + +To create the service, create the file `src/modules/memcached/service.ts` with the following content: + +export const serviceHighlights1 = [ + ["25", "memcachedClient", "Resolve Memcached client from the module container."], + ["26", "options", "Options passed to the module provider."] +] + +```ts title="src/modules/memcached/service.ts" highlights={serviceHighlights1} +import { ICachingProviderService } from "@medusajs/framework/types" +import * as memjs from "memjs" +import { ModuleOptions } from "./loaders/connection" + +type InjectedDependencies = { + memcachedClient: memjs.Client +} + +class MemcachedCachingProviderService implements ICachingProviderService { + static identifier = "memcached-cache" + + protected client: memjs.Client + protected options_: ModuleOptions + protected readonly CACHE_PREFIX: string + protected readonly TAG_PREFIX: string + protected readonly OPTIONS_PREFIX: string + protected readonly KEY_TAGS_PREFIX: string + protected readonly TAG_KEYS_PREFIX: string + protected readonly compressionEnabled: boolean + protected readonly compressionThreshold: number + protected readonly compressionLevel: number + protected readonly defaultTtl: number + + constructor( + { memcachedClient }: InjectedDependencies, + options: ModuleOptions + ) { + this.client = memcachedClient + this.options_ = options + + // Set all prefixes with the main prefix + const mainPrefix = options.cachePrefix || "medusa" + this.CACHE_PREFIX = `${mainPrefix}:` + this.TAG_PREFIX = `${mainPrefix}:tag:` + this.OPTIONS_PREFIX = `${mainPrefix}:opt:` + this.KEY_TAGS_PREFIX = `${mainPrefix}:key_tags:` + this.TAG_KEYS_PREFIX = `${mainPrefix}:tag_keys:` + + // Set compression options + this.compressionEnabled = options.compression?.enabled ?? true + this.compressionThreshold = options.compression?.threshold ?? 2048 // 2KB default + this.compressionLevel = options.compression?.level ?? 6 // Balanced compression + + // Set default TTL + this.defaultTtl = options.defaultTtl ?? 3600 // 1 hour default + } +} +``` + +You create the service that implements the `ICachingProviderService` interface. You define in the class some protected properties to hold dependencies, the Memcached client, and configuration options. + +You also define a constructor in the service. Service constructors accept two parameters: + +1. The Module container, from which you can resolve dependencies. In this case, you resolve the `memcachedClient` that you registered in the loader. +2. The options passed to the module when it's registered in the Medusa application. + +You'll get a type error at this point because you haven't implemented the methods of the `ICachingProviderService` interface yet. You'll implement them next, along with utility methods. + +### Compression Utility Methods + +Before implementing the caching methods, you'll implement two utility methods to handle data compression and decompression. + +These methods use the `zlib` library to compress data before storing it in Memcached and decompress it when retrieving it. This optimizes storage and network usage, especially for large data. + +First, add the following imports at the top of the file: + +```ts title="src/modules/memcached/service.ts" +import { deflate, inflate } from "zlib" +import { promisify } from "util" + +const deflateAsync = promisify(deflate) +const inflateAsync = promisify(inflate) +``` + +Then, add the following methods to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async compressData(data: string): Promise<{ + data: string; + compressed: boolean + }> { + if (!this.compressionEnabled || data.length < this.compressionThreshold) { + return { data, compressed: false } + } + + const buffer = Buffer.from(data, 'utf8') + const compressed = await deflateAsync(buffer) + const compressedData = compressed.toString('base64') + + // Only use compression if it actually reduces size + if (compressedData.length < data.length) { + return { data: compressedData, compressed: true } + } + + return { data, compressed: false } + } + + private async decompressData( + data: string, + compressed: boolean + ): Promise { + if (!compressed) { + return data + } + + const buffer = Buffer.from(data, 'base64') + const decompressed = await inflateAsync(buffer) + return decompressed.toString('utf8') + } +} +``` + +You define two private methods: + +- `compressData`: Takes a string `data` as input and compresses it using `zlib.deflate` if compression is enabled and the data size exceeds the defined threshold. It returns an object containing the (possibly compressed) data and a boolean indicating whether compression was applied. +- `decompressData`: Takes a string `data` and a boolean `compressed` as input. If `compressed` is `true`, it decompresses the data using `zlib.inflate`. Otherwise, it returns the data as is. + +### Set Methods + +Next, you'll implement the [set method](/references/caching-module-provider#set) of the `ICachingProviderService` interface. This method stores a value in the cache with an optional time-to-live (TTL) and associated tags. + +#### setKeyTags Helper Method + +Before implementing the method, you'll implement the helper method `setKeyTags`. Since Memcached doesn't support tagging natively, you'll need to manage tags manually by storing mappings between keys and tags. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async setKeyTags( + key: string, + tags: string[], + setOptions: memjs.InsertOptions + ): Promise { + const timestamp = Math.floor(Date.now() / 1000) + const tagNamespaces: Record = {} + const operations: Promise[] = [] + + // Batch all namespace operations + for (const tag of tags) { + const tagKey = this.TAG_PREFIX + tag + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + + // Get namespace version + operations.push( + (async () => { + const result = await this.client.get(tagKey) + if (!result.value) { + tagNamespaces[tag] = timestamp.toString() + await this.client.add(tagKey, timestamp.toString()) + } else { + tagNamespaces[tag] = result.value.toString() + } + })() + ) + + // Add key to tag's key list + operations.push( + (async () => { + const result = await this.client.get(tagKeysKey) + let keys: string[] = [] + if (result.value) { + keys = JSON.parse(result.value.toString()) as string[] + } + if (!keys.includes(key)) { + keys.push(key) + await this.client.set(tagKeysKey, JSON.stringify(keys), setOptions) + } + })() + ) + } + + await Promise.all(operations) + + // Store the tag namespaces for this key + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const serializedTags = JSON.stringify(tagNamespaces) + await this.client.set(keyTagsKey, serializedTags, setOptions) + } +} +``` + +The `setKeyTags` method takes a cache `key`, an array of `tags`, and Memcached `setOptions`. + +In the method, you: + +1. Get the current timestamp to use as a namespace version for tags. +2. Iterate over the provided tags and for each tag: + - Retrieve the current namespace version from Memcached. If it doesn't exist, set it to the current timestamp. + - Retrieve the list of keys associated with the tag. If the current key is not in the list, add it and update the list in Memcached. +3. Store the mapping of tags and their namespace versions for the given key. + +You'll use this method in the `set` method to manage tags when storing a value. + +#### set Method + +Add the following `set` method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + async set({ + key, + data, + ttl, + tags, + options, + }: { + key: string + data: any + ttl?: number + tags?: string[] + options?: { autoInvalidate?: boolean } + }): Promise { + const prefixedKey = this.CACHE_PREFIX + key + const serializedData = JSON.stringify(data) + const setOptions: memjs.InsertOptions = {} + + // Use provided TTL or default TTL + setOptions.expires = ttl ?? this.defaultTtl + + // Compress data if enabled + const { + data: finalData, + compressed + } = await this.compressData(serializedData) + + // Batch operations for better performance + const operations: Promise[] = [ + this.client.set(prefixedKey, finalData, setOptions) + ] + + // Always store options (including compression flag) to allow checking them later + const optionsKey = this.OPTIONS_PREFIX + key + const optionsData = { ...options, compressed } + operations.push( + this.client.set(optionsKey, JSON.stringify(optionsData), setOptions) + ) + + // Handle tags using namespace simulation with batching + if (tags && tags.length > 0) { + operations.push(this.setKeyTags(key, tags, setOptions)) + } + + await Promise.all(operations) + } +} +``` + +The `set` method takes an object with the following properties: + +- `key`: The cache key. +- `data`: The value to cache. +- `ttl`: Optional time-to-live for the cached value, in seconds. +- `tags`: Optional array of tags to associate with the cached value. +- `options`: Optional additional options, such as `autoInvalidate`, which indicates whether to automatically invalidate the cache based on tags. + +In the method, you: + +1. Store the value in Memcached with the specified TTL. You store the compressed data if compression is enabled and necessary. +2. Store the options (including whether the data was compressed) in a separate key to allow checking them later. + - This is necessary to determine whether the data can be automatically invalidated based on tags. +3. If tags are provided, call the `setKeyTags` method to set up the tag mappings. + +You batch all operations using `Promise.all` for better performance. + +### Get Methods + +Next, you'll implement the [get method](/references/caching-module-provider#get) of the `ICachingProviderService` interface. This method retrieves a value from the cache either by its key or by associated tags. + +Before implementing the method, you need two helper methods. + +#### validateKeyByTags Helper Method + +The first helper method validates that a key is still valid based on its tags. You'll use this method when retrieving a value by its key to ensure that the value hasn't been invalidated by tag updates. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async validateKeyByTags(key: string, tags: string[]): Promise { + if (!tags || tags.length === 0) { + return true // No tags to validate + } + + // Get the stored tag namespaces for this key + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const keyTagsResult = await this.client.get(keyTagsKey) + + if (!keyTagsResult.value) { + return true // No stored tags, assume valid + } + + const storedTags = JSON.parse(keyTagsResult.value.toString()) + + // Batch all namespace checks for better performance + const tagKeys = Object.keys(storedTags).map(tag => this.TAG_PREFIX + tag) + const tagResults = await Promise.all( + tagKeys.map(tagKey => this.client.get(tagKey)) + ) + + // Check if any tag namespace is missing or changed + for (let i = 0; i < tagResults.length; i++) { + const tag = Object.keys(storedTags)[i] + const tagResult = tagResults[i] + + if (tagResult.value) { + const currrentTag = tagResult.value.toString() + // If the namespace has changed since the key was stored, it's invalid + if (currrentTag !== storedTags[tag]) { + return false + } + } else { + // Namespace doesn't exist - this means it was reclaimed after being incremented + // This indicates the tag was cleared, so the key should be considered invalid + return false + } + } + + return true + } +} +``` + +The `validateKeyByTags` method accepts a cache `key` and an array of `tags`. + +In the method, you: + +- Retrieve from Memcached the tag namespaces for the given key. + - If no tags are stored, you assume the key is valid and return `true`. +- For each stored tag, retrieve its current namespace version from Memcached. +- Compare the current namespace version with the stored version. If any tag's namespace has changed or is missing, return `false`, indicating that the key is invalid. Otherwise, return `true`. + +#### getByTags Helper Method + +The second helper method retrieves the cached data associated with specified tags. You'll use this method when retrieving a value by tags. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async getByTags(tags: string[]): Promise { + if (!tags || tags.length === 0) { + return null + } + + // Get all keys associated with each tag + const tagKeysOperations = tags.map(tag => { + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + return this.client.get(tagKeysKey) + }) + + const tagKeysResults = await Promise.all(tagKeysOperations) + + // Collect all unique keys from all tags + const allKeys = new Set() + for (const result of tagKeysResults) { + if (result.value) { + const keys = JSON.parse(result.value.toString()) as string[] + keys.forEach(key => allKeys.add(key)) + } + } + + if (allKeys.size === 0) { + return null + } + + // Get all cached data for the collected keys + const dataOperations = Array.from(allKeys).map(async key => { + const prefixedKey = this.CACHE_PREFIX + key + const result = await this.client.get(prefixedKey) + + if (!result.value) { + return { key, data: null } + } + + const dataString = result.value.toString() + + // Check if data is compressed + const optionsKey = this.OPTIONS_PREFIX + key + const optionsResult = await this.client.get(optionsKey) + let compressed = false + + if (optionsResult.value) { + const options = JSON.parse(optionsResult.value.toString()) + compressed = options.compressed || false + } + + // Decompress if needed + const decompressedData = await this.decompressData(dataString, compressed) + return { key, data: JSON.parse(decompressedData) } + }) + + const dataResults = await Promise.all(dataOperations) + + // Filter out null data and validate tags for each key + const validData: any[] = [] + for (const { key, data } of dataResults) { + if (data !== null) { + // Validate that this key is still valid for the requested tags + const isValid = await this.validateKeyByTags(key, tags) + if (isValid) { + validData.push(data) + } + } + } + + return Object.keys(validData).length > 0 ? validData : null + } +} +``` + +The `getByTags` method takes an array of `tags`. + +In the method, you: + +- Retrieve from Memcached all keys associated with each tag. You collect all unique keys from the results. +- For each unique key, retrieve the cached data with its options. + - If the data is compressed, decompress it using the `decompressData` method. +- Validate each key using the `validateKeyByTags` method to ensure that the data is still valid based on its tags. +- Return an array of valid cached data or `null` if no valid data is found. + +You can now implement the `get` method using these helper methods. + +#### get Method + +Add the `get` method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + async get({ + key, + tags, + }: { + key?: string + tags?: string[] + }): Promise { + if (key) { + const prefixedKey = this.CACHE_PREFIX + key + + // Get the stored tags for this key and validate them + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const keyTagsResult = await this.client.get(keyTagsKey) + + if (keyTagsResult.value) { + const storedTags = JSON.parse(keyTagsResult.value.toString()) + const tagNames = Object.keys(storedTags) + + const isValid = await this.validateKeyByTags(key, tagNames) + if (!isValid) { + return null + } + } + + const result = await this.client.get(prefixedKey) + if (result.value) { + const dataString = result.value.toString() + + // Check if data is compressed (look for compression flag in options) + const optionsKey = this.OPTIONS_PREFIX + key + const optionsResult = await this.client.get(optionsKey) + let compressed = false + + if (optionsResult.value) { + const options = JSON.parse(optionsResult.value.toString()) + compressed = options.compressed || false + } + + // Decompress if needed + const decompressedData = await this.decompressData(dataString, compressed) + return JSON.parse(decompressedData) + } + return null + } + + if (tags && tags.length > 0) { + // Retrieve data by tags - get all keys associated with the tags + return await this.getByTags(tags) + } + + return null + } +} +``` + +The `get` method takes an object with optional `key` and `tags` properties. + +In the method: + +- If a `key` is provided, you give it a higher priority and retrieve the cached value for that key. + - You first check if the key has associated tags and validate them using the `validateKeyByTags` method, ensuring the cached value is still valid. + - You decompress the data if it was stored in a compressed format. + - If the key is not found or is invalid, you return `null`. Otherwise, you return the cached value. +- If no `key` is provided but `tags` are, you call the `getByTags` method to retrieve all cached values associated with the provided tags. +- If neither `key` nor `tags` are provided, you return `null`. + +### Clear Methods + +Finally, you'll implement the [clear method](/references/caching-module-provider#clear) of the `ICachingProviderService` interface. + +The `clear` method removes a cached value either by its key or by associated tags. It also receives an optional `options` parameter to control whether to automatically invalidate the cache based on tags: + +- If `options` isn't set, you clear all cached values associated with the provided tags. +- If `options.autoInvalidate` is `true`, you only invalidate the keys of the provided tags whose options allow automatic invalidation. + +Before implementing the `clear` method, you'll implement four helper methods to handle tag and key invalidation. + +#### removeKeysFromTag Helper Method + +The `removeKeysFromTag` method removes a list of keys from a tag's key list in Memcached. This is useful when invalidating by key or when clearing keys associated with a tag. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async removeKeysFromTag(tag: string, keysToRemove: string[]): Promise { + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + const tagKeysResult = await this.client.get(tagKeysKey) + + if (!tagKeysResult.value) { + return // No keys to remove + } + + let keys: string[] = JSON.parse(tagKeysResult.value.toString()) as string[] + + // Remove the specified keys + keys = keys.filter(key => !keysToRemove.includes(key)) + + if (keys.length === 0) { + // If no keys left, delete the tag keys entry + await this.client.delete(tagKeysKey) + } else { + // Update the tag keys list + await this.client.set(tagKeysKey, JSON.stringify(keys)) + } + } +} +``` + +The `removeKeysFromTag` method takes a `tag` and an array of `keysToRemove`. + +In the method, you: + +1. Retrieve the list of keys associated with the tag from Memcached. +2. Filter out the keys that need to be removed. +3. If no keys are left, delete the tag's key list entry. Otherwise, update the list in Memcached. + +#### clearByKey Helper Method + +Next, you'll implement the `clearByKey` method. It removes a cached value by its key and updates the associated tags to remove the key from their key lists. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async clearByKey(key: string): Promise { + // Get the key's tags before deleting to clean up tag key lists + const keyTagsKey = `${this.KEY_TAGS_PREFIX}${key}` + const keyTagsResult = await this.client.get(keyTagsKey) + + const operations: Promise[] = [ + this.client.delete(this.CACHE_PREFIX + key), + this.client.delete(this.OPTIONS_PREFIX + key), + this.client.delete(keyTagsKey) + ] + + // If the key has tags, remove it from tag key lists + if (keyTagsResult.value) { + const storedTags = JSON.parse(keyTagsResult.value.toString()) + const tagNames = Object.keys(storedTags) + + // Batch tag cleanup operations + const tagCleanupOperations = tagNames.map(async tag => { + await this.removeKeysFromTag(tag, [key]) + }) + operations.push(...tagCleanupOperations) + } + + await Promise.all(operations) + } +} +``` + +The `clearByKey` method takes a cache `key`. + +In the method, you: + +1. Retrieve the tags associated with the key before deleting it. +2. Delete the cached value, its options, and the key's tag mapping from Memcached. +3. If the key has associated tags, call the `removeKeysFromTag` method for each tag to remove the key from their key lists. +4. Batch all operations using `Promise.all` for better performance. + +#### clearByTags Helper Method + +Next, you'll implement the `clearByTags` method. It removes all cached values associated with the provided tags. You'll use this method when the `options` parameter isn't set in the `clear` method. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async clearByTags(tags: string[]): Promise { + const operations = tags.map(async tag => { + const tagKey = this.TAG_PREFIX + tag + const result = await this.client.increment(tagKey, 1) + if (result === null) { + // Key doesn't exist, create it with current timestamp + const timestamp = Math.floor(Date.now() / 1000) + await this.client.add(tagKey, timestamp.toString()) + } + }) + + await Promise.all(operations) + } +} +``` + +The `clearByTags` method takes an array of `tags`. + +In the method, you loop over the tags to increment their namespace versions in Memcached. If a tag's namespace doesn't exist, you create it with the current timestamp. + +By incrementing the namespace version, you effectively invalidate the tag and all associated keys, as they will no longer match the stored namespace versions. The namespace version will also be replaced in Memcached after being reclaimed. + + + +Learn more about this invalidation strategy in [Memcached's documentation](https://docs.memcached.org/userguide/usecases/). + + + +#### clearByTagsWithAutoInvalidate Helper Method + +The `clearByTagsWithAutoInvalidate` method removes cached values associated with the provided tags, but only for keys whose options allow automatic invalidation. You'll use this method when the `options.autoInvalidate` parameter is `true` in the `clear` method. + +Add the following method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + private async clearByTagsWithAutoInvalidate(tags: string[]): Promise { + for (const tag of tags) { + // Get the list of keys associated with this tag + const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` + const tagKeysResult = await this.client.get(tagKeysKey) + + if (!tagKeysResult.value) { + continue + } + + const keys = JSON.parse(tagKeysResult.value.toString()) as string[] + + // Check each key's options and delete if autoInvalidate is true + const keysToRemove: string[] = [] + for (const key of keys) { + const optionsKey = `${this.OPTIONS_PREFIX}${key}` + const optionsResult = await this.client.get(optionsKey) + + if (optionsResult.value) { + const options = JSON.parse(optionsResult.value.toString()) + if (options.autoInvalidate) { + // Delete the key and its associated data + await this.client.delete(this.CACHE_PREFIX + key) + await this.client.delete(optionsKey) + await this.client.delete(`${this.KEY_TAGS_PREFIX}${key}`) + keysToRemove.push(key) + } + } + } + + // Remove deleted keys from the tag's key list + if (keysToRemove.length > 0) { + await this.removeKeysFromTag(tag, keysToRemove) + } + } + } +} +``` + +You define the `clearByTagsWithAutoInvalidate` method, which takes an array of `tags`. + +In the method, you loop over the tags to: + +1. Retrieve the list of keys associated with each tag from Memcached. +2. For each key, retrieve its options and check if `autoInvalidate` is `true`. +3. If `autoInvalidate` is `true`, delete the cached value, its options, and its tag mapping from Memcached. You also keep track of the keys that were deleted. +4. After processing all keys for a tag, call the `removeKeysFromTag` method to remove the deleted keys from the tag's key list. + +#### clear Method + +Finally, add the `clear` method to the `MemcachedCachingProviderService` class: + +```ts title="src/modules/memcached/service.ts" +class MemcachedCachingProviderService implements ICachingProviderService { + // ... + async clear({ + key, + tags, + options, + }: { + key?: string + tags?: string[] + options?: { autoInvalidate?: boolean } + }): Promise { + if (key) { + await this.clearByKey(key) + } + + if (tags?.length) { + if (!options) { + // Clear all items with the specified tags + await this.clearByTags(tags) + } else if (options.autoInvalidate) { + // Clear only items with autoInvalidate option set to true + await this.clearByTagsWithAutoInvalidate(tags) + } + } + } +} +``` + +The `clear` method takes an object with optional `key`, `tags`, and `options` properties. + +In the method: + +- If a `key` is provided, you call the `clearByKey` method to remove the cached value and update associated tags. +- If `tags` are provided: + - If `options` isn't set, you call the `clearByTags` method to invalidate all cached values associated with the tags. + - If `options.autoInvalidate` is `true`, you call the `clearByTagsWithAutoInvalidate` method to invalidate only the keys whose options allow automatic invalidation. + +You've now implemented all methods of the `ICachingProviderService` interface in the `MemcachedCachingProviderService` class. + +--- + +## 5. Export Memcached Module Provider Definition + +The final piece of a module provider is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa which module this provider belongs to, its loaders, and its service. + +Create the file `src/modules/memcached/index.ts` with the following content: + +```ts title="src/modules/memcached/index.ts" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" +import MemcachedCachingProviderService from "./service" +import connection from "./loaders/connection" + +export default ModuleProvider(Modules.CACHING, { + services: [MemcachedCachingProviderService], + loaders: [connection], +}) +``` + +You use the `ModuleProvider` function from the Modules SDK to create the module provider's definition. It accepts two parameters: + +1. The module this provider belongs to. In this case, the `Modules.CACHING` module. +2. An object with the provider's `services` and `loaders`. + +--- + +## 6. Register Memcached Module Provider + +The last step is to register the Memcached Module Provider in your Medusa application. + +### Enable Caching Feature Flag + +First, enable the [Caching Module's feature flag](../../page.mdx#1-enable-caching-feature-flag) by setting the following environment variable: + +```bash +MEDUSA_FF_CACHING=true +``` + +### Register Memcached Module Provider + +Then, in `medusa-config.ts`, add a new entry in the `modules` array to register the Memcached Module Provider: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + in_memory: { + enable: true + }, + providers: [ + { + resolve: "./src/modules/memcached", + id: "caching-memcached", + // Optional, makes this the default caching provider + is_default: true, + options: { + serverUrls: process.env.MEMCACHED_SERVERS?.split(',') || + ["127.0.0.1:11211"], + // add other optional options here... + }, + }, + // other caching providers... + ], + } + } + ] +}) +``` + +You register the `@medusajs/medusa/caching` module and add the Memcached Module Provider to its `providers` array. + +You pass the options to configure the Memcached client and the module's behavior. These are the same [options you defined in the ModuleOptions type in the loader](#3-create-memcached-connection-loader). + +### Add Environment Variables + +Make sure you set the necessary environment variables in your `.env` file. For example: + +```bash +MEMCACHED_SERVERS=127.0.0.1:11211 # Comma-separated list of Memcached server URLs +# Add other optional variables as needed +``` + +You set the `MEMCACHED_SERVERS` variable to specify the Memcached server URLs. You can also set other optional variables like `MEMCACHED_USERNAME` and `MEMCACHED_PASSWORD` based on your use case. + +--- + +## Test the Memcached Caching Provider + +To test that the Memcached Caching Provider is working, start the Medusa application with the following command: + +```bash npm2yarn +npm run dev +``` + +You'll see in the logs that the Memcached connection is established successfully: + +```bash +info: Connecting to Memcached... +info: Successfully connected to Memcached +``` + +If you set the `is_default` option to `true` in the provider registration, the Memcached Caching Provider will be used for all caching operations in the Medusa application. + +### Create Test API Route + +To verify that the caching is working, you can create a simple [API route](!docs!/learn/fundamentals/api-routes) that retrieves data with caching. + +To create an API route, create a new file at `src/api/test-cache/route.ts` with the following content: + +```ts title="src/api/test-cache/route.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const query = req.scope.resolve("query") + + // Test caching with a simple query + const { data } = await query.graph({ + entity: "product", + fields: ["id", "title", "handle"], + }, { + cache: { + enable: true, + // For testing purposes + key: "test-cache-products", + // If you didn't set is_default to true, uncomment the following line + // providers: ["caching-memcached"], + } + }) + + res.status(200).json({ + message: "Products retrieved with Memcached caching", + data, + }) +} +``` + +This creates a `GET` route at `/test-cache` that retrieves products using Query with caching enabled. The `key` so that you can easily check the cached data in Memcached for testing purposes. + +Then, send a `GET` request to the `/test-cache` endpoint: + +```bash +curl http://localhost:9000/test-cache +``` + +You'll receive the list of products in the response. + +You can then check that the data is cached in Memcached using the [memcached-cli](https://github.com/pd4d10/memcached-cli) tool. + +First, establish a connection to your Memcached server: + +```bash +npx memcached-cli localhost:11211 # Replace with your Memcached server URL +``` + +Then, retrieve the cached data using the key you specified in the API route: + +```bash +get medusa:test-cache-products +``` + +Notice that you prefix the key with `medusa:`, which is the default prefix unless you set the `keyPrefix` option in the provider registration. + +--- + +## Next Steps + +If you're new to Medusa, check out the [main documentation](!docs!/learn), where you'll get a more in-depth understanding of all the concepts you've used in this guide and more. + +To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](../../../../commerce-modules/page.mdx). + +### Troubleshooting + +If you encounter issues during your development, check out the [troubleshooting guides](../../../../troubleshooting/page.mdx). + +### Getting Help + +If you encounter issues not covered in the troubleshooting guides: + +1. Visit the [Medusa GitHub repository](https://github.com/medusajs/medusa) to report issues or ask questions. +2. Join the [Medusa Discord community](https://discord.gg/medusajs) for real-time support from community members. diff --git a/www/apps/resources/app/infrastructure-modules/caching/migrate-cache/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/migrate-cache/page.mdx new file mode 100644 index 0000000000..1f90b5324c --- /dev/null +++ b/www/apps/resources/app/infrastructure-modules/caching/migrate-cache/page.mdx @@ -0,0 +1,124 @@ +import { Prerequisites } from "docs-ui" + +export const metadata = { + title: `Migrate from Cache Module to Caching Module`, +} + +# {metadata.title} + +In this guide, you'll learn how to migrate from the deprecated [Cache Module](../../cache/page.mdx) to the new [Caching Module](../page.mdx). + + + +The Caching Module is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + + + + + +1. Set up a Cache Module in `medusa-config.ts`. +2. Used the Cache Module's service in their code. + +If you haven't done either of these, you don't need to migrate to the Caching Module. You can refer to the [Caching Module guide](../page.mdx#install-the-caching-module) to learn how to set it up. + + + +## Why Migrate to the Caching Module? + +The Caching Module provides improved performance, flexibility, and scalability compared to the deprecated Cache Module. + +It also offers a better developer experience, making it easier to cache Query results and other data. + +Additionally, the Caching Module supports registering multiple caching providers, such as Redis and Memcached, allowing you to use different caching backends in your application. + +--- + +## Architectural Module Changes + +The Cache Module implements caching logic, including integration with third-party caching services like Redis. For example, the Redis Cache Module handled connecting to the Redis server and performing caching operations. + +The Caching Module, on the other hand, follows a provider-based architecture. The Caching Module provides the interface you use to manage cached data, while Caching Module Providers implement the actual caching logic. You can choose which provider to use, such as the Redis Caching Module Provider or a custom provider you create. + +This separation of concerns allows for greater flexibility and extensibility. You can easily switch between different caching providers or create your own custom provider without modifying the core Caching Module. + +![Diagram illustrating the change in architecture from Cache Module to Caching Module](https://res.cloudinary.com/dza7lstvk/image/upload/v1759845565/Medusa%20Resources/cache-to-caching_yxaped.jpg) + +--- + +## How to Migrate to the Caching Module + + + +### 1. Remove the Cache Module + +The first step is to remove the Cache Module from your `medusa-config.ts` file. + +For example, if you set up the Redis Cache Module, remove the following code from `medusa-config.ts`: + +```ts title="medusa-config.ts" highlights={[["5"], ["6"], ["7"], ["8"], ["9"], ["10"]]} +module.exports = defineConfig({ + // ... + modules: [ + // REMOVE THE FOLLOWING LINES + { + resolve: "@medusajs/medusa/cache-redis", + options: { + redisUrl: process.env.CACHE_REDIS_URL, + }, + }, + ], +}) +``` + +### 2. Install and Register the Caching Module + +The Caching Module is installed by default in your application. To use it, enable the caching feature flag and register the module in your `medusa-config.ts` file. + +Refer to the [Caching Module guide](../page.mdx#install-the-caching-module) for setup instructions. + +### 3. (Optional) Update Your Code to Use the Caching Module's Service + +If you're using the Cache Module's service in your code, update it to use the Caching Module's service instead. + +#### Container Key Change + +Previously, you resolved the Cache Module's service using the `Modules.CACHE` or `cache` key. + +Now, use the `Modules.CACHING` or `caching` key to resolve the Caching Module's service. For example: + +```ts +const cachingModuleService = container.resolve(Modules.CACHING) +// or +const cachingModuleService = container.resolve("caching") +``` + +#### Method Changes + +The Caching Module's service has similar methods to the Cache Module's service: + +1. `get` -> Use the Caching Module's [get method](/references/caching-service#get). +2. `set` -> Use the Caching Module's [set method](/references/caching-service#set). +3. `invalidate` -> Use the Caching Module's [clear method](/references/caching-service#clear). + +### 4. (Optional) Create Custom Caching Module Provider + +If you have a custom Cache Module, recreate it as a custom Caching Module Provider. For example, recreate a custom Memcached Cache Module as a Caching Module Provider. + +The Caching Module Provider's service has similar methods to the Cache Module's service. Refer to the [Create Caching Module Provider guide](/references/caching-module-provider) for instructions on creating a custom Caching Module Provider. + +--- + +## Understand Caching Changes + +With the Cache Module, you handled caching and invalidation manually. + +The Caching Module can now handle caching and invalidation automatically for certain operations, such as caching Query results. You can still use the Caching Module's service to cache custom data. + +Learn more about the Caching Module in the [Caching Module guide](../page.mdx). \ No newline at end of file diff --git a/www/apps/resources/app/infrastructure-modules/caching/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/page.mdx new file mode 100644 index 0000000000..6637ce8f25 --- /dev/null +++ b/www/apps/resources/app/infrastructure-modules/caching/page.mdx @@ -0,0 +1,318 @@ +import { CardList, Card, Table, CodeTabs, CodeTab } from "docs-ui" + +export const metadata = { + title: `Caching Module`, +} + +# {metadata.title} + +In this guide, you'll learn about the Caching Module and its providers. + + + +The Caching Module is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). It replaces the deprecated [Cache Module](../cache/page.mdx). + + + +## What is the Caching Module? + +The Caching Module provides functionality to cache data in your Medusa application, improving performance and reducing latency for frequently accessed data. + +For example, Medusa uses the Caching Module to cache product information, and you can cache custom data such as brand information. + +The Caching Module stores and retrieves cached data using the caching service you integrate, such as [Redis](./providers/redis/page.mdx) or [Memcached](./guides/memcached/page.mdx). This provides flexibility in customizing your Medusa application's infrastructure to meet your performance and scalability requirements. + +![Diagram illustrating the Caching Module architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1759846791/Medusa%20Resources/caching-overview_tz91tw.jpg) + +### Caching Module vs Cache Module + +Before Medusa v2.11.0, you used the [Cache Module](../cache/page.mdx) to cache data. The Cache Module is now deprecated and has been replaced by the Caching Module. + +If you're using the Cache Module in your application, refer to the [migrate to the Caching Module](./migrate-cache/page.mdx). + +--- + +## Install the Caching Module + +The Caching Module is installed by default in your application. To use it, enable the caching feature flag and register the module in your `medusa-config.ts` file. + +{/* + +Caching features are enabled by default for Cloud users. Learn more in the [Medusa Cache](!cloud!/cache) guide. + + */} + +### 1. Enable Caching Feature Flag + +The caching feature is currently behind a feature flag. To enable it, set the `MEDUSA_FF_CACHING` environment variable to `true` in your `.env` file: + +```bash +MEDUSA_FF_CACHING=true +``` + +This enables you to use the Caching Module and activates caching features in Medusa's core. + +### 2. Register the Caching Module + +Next, add the Caching Module to the `modules` property of the exported object in `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + resolve: "@medusajs/caching-redis", + id: "caching-redis", + // Optional, makes this the default caching provider + is_default: true, + options: { + redisUrl: process.env.CACHE_REDIS_URL, + // more options... + }, + }, + ] + } + } + ] +}) +``` + +This registers the Caching Module in your application with the [Redis Caching Module Provider](./providers/redis/page.mdx). + + + +The Caching Module requires at least one Caching Module Provider to be registered. If you do not register any providers, the Caching Module throws an error when your application starts. + + + +### What is a Caching Module Provider? + +A Caching Module Provider implements the underlying logic for caching data, such as integrating third-party caching services. The Caching Module then uses the registered Caching Module Provider to handle caching operations. + +Refer to the [Caching Module Providers guide](./providers/page.mdx) to learn more about Caching Module Providers in Medusa, and how to configure the default provider. + + + +### What Data is Cached by Default? + +After you enable the Caching Module, Medusa automatically caches data for several business-critical APIs to boost performance and throughput. This includes all cart-related operations, where the following data is cached: + +- Regions +- Promotion codes +- Variant price sets +- Variants +- Shipping options +- Sales channels +- Customers + +--- + +## How to Use the Caching Module + +You can cache data with the Caching Module in two ways: by caching data retrieved with [Query](!docs!/learn/fundamentals/module-links/query) or the [Index Module](!docs!/learn/fundamentals/module-links/index-module), or by directly using the [Caching Module's service](/references/caching-service). + +### 1. Caching with Query or Index Module + +You can cache results from Query and the Index Module by passing the `cache` option to the `query.graph` and `query.index` methods, or to the `useQueryGraphStep` in workflows. + +For example: + +```ts highlights={[["14"], ["15"], ["16"]]} +import { + createWorkflow, + WorkflowResponse +} from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +export const workflow = createWorkflow( + "workflow-1", + () => { + const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + }, + }, + }) + + return new WorkflowResponse(products) + } +) +``` + +The `useQueryGraphStep` accepts an `options.cache` property that enables and configures caching of the results. + +When caching is enabled, the Caching Module stores the results in the underlying caching service (for example, Redis). Subsequent calls to the same query retrieve the results from the cache, improving performance. + +Query and the Index Module accept other caching options. Learn more in the [Query](!docs!/learn/fundamentals/module-links/query#cache-query-results) and [Index Module](!docs!/learn/fundamentals/module-links/index-module#cache-index-module-results) guides. + +### 2. Caching with the Caching Module's Service + +You can also use the Caching Module's service directly to cache custom data in your Medusa application. + +For example, resolve the Caching Module's service in a workflow step and use its methods to cache brand information: + +export const cachingHighlights = [ + ["14", "computeKey", "Compute the cache key."], + ["15", "get", "Retrieve the cached value for the `brand` tag."], + ["19", "cachedValue", "If a cached value exists, return it."], + ["23", "getBrand", "Fetch the brand data if not cached."], + ["25", "set", "Cache the fetched brand data with the `brand` tag."] +] + +```ts highlights={cachingHighlights} +import { Modules } from "@medusajs/framework/utils" +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" + +type StepInput = { + filters: Record +} + +export const getBrandStep = createStep( + "get-brand-step", + async (input: StepInput, { container }) => { + const cachingModuleService = container.resolve(Modules.CACHING) + const brandModuleService = container.resolve("brand") + + const cacheKey = await cachingModuleService.computeKey(input.filters) + const cachedValue = await cachingModuleService.get({ + tags: ["brand"] + }) + + if (cachedValue) { + return new StepResponse(cachedValue) + } + + const brand = await brandModuleService.getBrand(input.filters) + + await cachingModuleService.set({ + key: cacheKey, + tags: [`Brand:${brand.id}`], + data: brand + }) + + return new StepResponse(brand) + } +) +``` + +In the example above, you create a step that resolves the Caching Module's service from the [Medusa container](!docs!/learn/fundamentals/medusa-container). + +Then, you use the `get` method of the service to retrieve a cached value with the tag `"brand"`. You can also use other methods such as `set` to cache a value and `clear` to remove a cached value. + +Learn about other methods and options of the Caching Module in the [Use Caching Module](/references/caching-service) guide. + +### Which Caching Method Should You Use? + + + + + + Caching Method + + + When to Use + + + + + + + Query or Index Module + + + Ideal for standard data retrieval scenarios, such as fetching products or custom data. + + + + + Caching Module's Service + + + Suitable for caching computed values, external API responses, or to control caching behavior. + + + +
+ +Caching data with Query or the Index Module is ideal when retrieving standard data, such as products or custom entities. The Caching Module automatically handles cache keys and invalidation for you. + +If you need to cache custom data like computed values or external API responses, or you want finer control over caching behavior, use the Caching Module's service directly. In this case, you must manage cache keys yourself, though you can still enable automatic invalidation using the service's `set` method. + +--- + +## Caching Module Options + +You can pass the following options to the Caching Module when registering it in your `medusa-config.ts` file: + + + + + + Option + + + Description + + + Default + + + + + + + `ttl` + + + A number indicating the default time-to-live (TTL) in seconds for cached items. After this period, cached items will be removed from the cache. + This number is passed to the underlying caching provider if no TTL is specified when caching data. + + + `3600` (1 hour) + + + + + `providers` + + + An array of caching providers to use. This allows you to configure multiple caching providers for different use cases. + + + No providers by default + + + +
+ +--- + +## Caching Concepts + +To learn more about caching concepts such as cache keys, cache tags, and automatic cache invalidation, refer to the [Caching Module Concepts guide](./concepts/page.mdx). \ No newline at end of file diff --git a/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx new file mode 100644 index 0000000000..460c62f2bd --- /dev/null +++ b/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx @@ -0,0 +1,157 @@ +import { CardList, Card, Table, CodeTabs, CodeTab } from "docs-ui" + +export const metadata = { + title: `Caching Module Providers`, +} + +# {metadata.title} + +In this guide, you'll learn about Caching Module Providers in Medusa, including how to configure and use them in your Medusa application. + +## What is a Caching Module Provider? + +A Caching Module Provider implements the logic for caching data, such as integrating third-party caching services. The [Caching Module](../page.mdx) then uses the registered Caching Module Providers to handle caching data. + +Medusa provides the [Redis Caching Module Provider](./redis/page.mdx) that you can use in development and production. You can also [Create a Caching Provider](/references/caching-module-provider). + + + +--- + +## Default Caching Module Provider + +You can register multiple Caching Module Providers and specify which one to use as the default. The Caching Module uses the default provider for all caching operations unless you specify a specific provider. + +### How is the Default Provider Selected? + +The Caching Module determines the default Caching Module Provider based on the following scenarios: + + + + + Scenario + Default Provider + + + + + + One provider is registered. + + + The registered provider. + + + + + Multiple providers and one of them has an `is_default` flag. + + + The provider with the `is_default` flag set to `true`. + + + +
+ +If none of the above scenarios apply, the Caching Module throws an error during startup indicating that no default provider is configured. + +### Setting the Default Caching Module Provider + +To specify a provider as the default, you can set its `is_default` option to `true` when registering it in the `provider` array of the Caching Module. + +For example: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + id: "caching-redis", + resolve: "@medusajs/caching-redis", + is_default: true, // Set as the default provider + options: { + // Redis options... + }, + }, + { + id: "caching-memcached", + resolve: "./path/to/your/memcached-provider", + options: { + // Memcached options... + }, + } + ] + } + } + ] +}) +``` + +In this example, the Redis Caching Module Provider is set as the default provider by setting `is_default: true`. The Memcached Caching Module Provider is also registered but not set as the default. + +--- + +## Caching with Specific Providers + +Whether you're caching data with Query, the Index Module, or directly using the Caching Module's service, you can specify an array of provider IDs to use for that specific caching operation. + +For example, considering you have the [above configuration](../page.mdx#setting-the-default-caching-module-provider) with both Redis and Memcached providers registered, you can specify which provider to use when caching data: + + + + +```ts highlights={[["7"]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + providers: ["caching-memcached"] // Specify Memcached provider + }, + }, +}) +``` + + + + +```ts highlights={[["6"]]} +const cachingModuleService = container.resolve(Modules.CACHING) + +await cachingModuleService.set({ + key: "product-list", + data: products, + providers: ["caching-memcached"] // Specify Memcached provider +}) +``` + + + + +In this example, both Query and the Caching Module's service use the Memcached provider, overriding the default Redis provider. + +The ID you pass is the same ID you specified in `medusa-config.ts` when registering the provider. \ No newline at end of file diff --git a/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx new file mode 100644 index 0000000000..6389a8bd0c --- /dev/null +++ b/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx @@ -0,0 +1,198 @@ +import { Table, Prerequisites } from "docs-ui" + +export const metadata = { + title: `Redis Caching Module Provider`, +} + +# {metadata.title} + +The Redis Caching Module Provider is a robust caching solution that leverages [Redis](https://redis.io/) to store cached data. Redis offers high performance, scalability, and data persistence, making it an ideal choice for production environments. + + + +The Caching Module and its providers are available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + + + +--- + +## Register the Redis Caching Module + + + +To use the Redis Caching Module Provider, you need to register it in the `providers` array of the Caching Module in your `medusa-config.ts`. + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + resolve: "@medusajs/caching-redis", + id: "caching-redis", + // Optional, makes this the default caching provider + is_default: true, + options: { + redisUrl: process.env.CACHE_REDIS_URL, + // more options... + }, + }, + // other caching providers... + ], + }, + }, + ], +}) +``` + +Notice that you pass an `id` property to the provider. The provider will be registered with that ID, which you can use to explicitly [specify the provider when caching data](../page.mdx#how-to-use-the-caching-module). + +### Environment Variables + +Make sure to add the following environment variable: + +```bash +CACHE_REDIS_URL=redis://localhost:6379 +``` + +### Redis Caching Module Options + + + + + + Option + + + Description + + + Default + + + + + + + `redisUrl` + + + The connection URL for the Redis server. + + + Required. An error is thrown if not provided. + + + + + `ttl` + + + A number indicating the default time-to-live (TTL) in seconds for cached items. After this period, cached items will be removed from the cache. + + + `3600` (1 hour) + + + + + `prefix` + + + A string to prefix all cache keys with. This is useful for namespacing your cache keys, especially when sharing a Redis instance with other applications or modules. + + + No prefix by default + + + + + `compressionThreshold` + + + A number indicating the size threshold in bytes above which cached items will be compressed before being stored in Redis. This helps save memory when caching large items. + + + `1024` (1 KB) + + + +
+ +--- + +## Test the Redis Caching Module + +You can test the Redis Caching Module by caching data using the Query or Index Module, or by directly using the Caching Module's service, as described in the [Caching Module guide](../page.mdx#how-to-use-the-caching-module). + +If you don't set the Redis Caching Module Provider as the default, you can explicitly specify its provider ID `caching-redis` when caching data with Query, the Index Module, or directly with the Caching Module's service. + + + +`caching-redis` is the ID you set in the `medusa-config.ts` file when registering the Redis Caching Module Provider. + + + +For example, you can create a workflow in `src/workflows/cache-products.ts` that caches products using the Redis Caching Module Provider: + +```ts title="src/workflows/cache-products.ts" highlights={[["14"], ["15"], ["16"], ["17"]]} +import { + createWorkflow, + WorkflowResponse +} from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +export const cacheProductsWorkflow = createWorkflow( + "cache-products", + () => { + const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title"], + options: { + cache: { + enable: true, + providers: ["caching-redis"], + }, + }, + }) + + return new WorkflowResponse(products) + } +) +``` + +Next, execute that workflow in an [API route](!docs!/learn/fundamentals/api-routes). For example, create a route at `src/api/cache-product/route.ts` with the following content: + +```ts title="src/api/cache-product/route.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" +import { cacheProductsWorkflow } from "../../workflows/cache-products" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const { result } = await cacheProductsWorkflow(req.scope) + .run({}) + + res.status(200).json(result) +} +``` + +Finally, start your Medusa server with the following command: + +```bash npm2yarn +npm run dev +``` + +Then, make a `GET` request to the `/cache-product` endpoint: + +```bash +curl http://localhost:9000/cache-product +``` + +You should receive a response with the list of products. The first time you make this request, the products will be fetched from the database and cached in Redis. Subsequent requests will retrieve the products from the cache, resulting in improved performance. \ No newline at end of file diff --git a/www/apps/resources/app/infrastructure-modules/file/page.mdx b/www/apps/resources/app/infrastructure-modules/file/page.mdx index d75a730b5d..f9e8909a88 100644 --- a/www/apps/resources/app/infrastructure-modules/file/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/file/page.mdx @@ -60,7 +60,7 @@ Then, you use the `retrieveFile` method of the File Module to retrieve the URL o --- -### What is a File Module Provider? +## What is a File Module Provider? A File Module Provider implements the underlying logic of handling uploads and downloads of assets, such as integrating third-party services. The File Module then uses the registered File Module Provider to handle file operations. diff --git a/www/apps/resources/app/infrastructure-modules/page.mdx b/www/apps/resources/app/infrastructure-modules/page.mdx index 132f05ad94..5ab0766a22 100644 --- a/www/apps/resources/app/infrastructure-modules/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/page.mdx @@ -47,29 +47,36 @@ The Analytics Module exposes functionalities to track and analyze user interacti -## Cache Module +## Caching Module -A Cache Module is used to cache the results of computations such as price selection or various tax calculations. Learn more in [this documentation](./cache/page.mdx). +The Caching Module provides functionality to cache data in your Medusa application, improving performance and reducing latency for frequently accessed data. -The following Cache modules are provided by Medusa. You can also create your own cache module as explained in [this guide](./cache/create/page.mdx). +The following Caching modules are provided by Medusa. You can also create a custom Caching Module Provider as explained in the [Create Caching Module Provider guide](#). + + + +The Caching Module is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). It replaces the deprecated [Cache Module](./cache/page.mdx). + + diff --git a/www/apps/resources/app/sitemap.ts b/www/apps/resources/app/sitemap.ts index 5d6fa7abe0..072ad2473f 100644 --- a/www/apps/resources/app/sitemap.ts +++ b/www/apps/resources/app/sitemap.ts @@ -37,6 +37,12 @@ export default function sitemap(): MetadataRoute.Sitemap { { url: `${config.baseUrl}${basePathUrl("/references/cache-service")}`, }, + { + url: `${config.baseUrl}${basePathUrl("/references/caching-module-provider")}`, + }, + { + url: `${config.baseUrl}${basePathUrl("/references/caching-service")}`, + }, { url: `${config.baseUrl}${basePathUrl("/references/file-service")}`, }, diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 5a0899debe..a734ebaa52 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -6622,5 +6622,6 @@ export const generatedEditDates = { "app/data-model-repository-reference/methods/upsertWithReplace/page.mdx": "2025-10-09T11:44:53.535Z", "app/how-to-tutorials/tutorials/agentic-commerce/page.mdx": "2025-10-09T11:25:48.831Z", "app/storefront-development/production-optimizations/page.mdx": "2025-10-03T13:28:37.909Z", + "app/infrastructure-modules/caching/page.mdx": "2025-10-13T11:46:36.452Z", "app/troubleshooting/subscribers/not-working/page.mdx": "2025-10-16T09:25:57.376Z" } \ No newline at end of file diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs index e5fc7596c7..0d524dfbd6 100644 --- a/www/apps/resources/generated/files-map.mjs +++ b/www/apps/resources/generated/files-map.mjs @@ -831,6 +831,30 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/infrastructure-modules/cache/redis/page.mdx", "pathname": "/infrastructure-modules/cache/redis" }, + { + "filePath": "/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx", + "pathname": "/infrastructure-modules/caching/concepts" + }, + { + "filePath": "/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx", + "pathname": "/infrastructure-modules/caching/guides/memcached" + }, + { + "filePath": "/www/apps/resources/app/infrastructure-modules/caching/migrate-cache/page.mdx", + "pathname": "/infrastructure-modules/caching/migrate-cache" + }, + { + "filePath": "/www/apps/resources/app/infrastructure-modules/caching/page.mdx", + "pathname": "/infrastructure-modules/caching" + }, + { + "filePath": "/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx", + "pathname": "/infrastructure-modules/caching/providers" + }, + { + "filePath": "/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx", + "pathname": "/infrastructure-modules/caching/providers/redis" + }, { "filePath": "/www/apps/resources/app/infrastructure-modules/event/create/page.mdx", "pathname": "/infrastructure-modules/event/create" @@ -1931,6 +1955,14 @@ export const filesMap = [ "filePath": "/www/apps/resources/references/cache/interfaces/cache.ICacheService/page.mdx", "pathname": "/references/cache/interfaces/cache.ICacheService" }, + { + "filePath": "/www/apps/resources/references/caching/interfaces/caching.ICachingModuleService/page.mdx", + "pathname": "/references/caching/interfaces/caching.ICachingModuleService" + }, + { + "filePath": "/www/apps/resources/references/caching/interfaces/caching.ICachingProviderService/page.mdx", + "pathname": "/references/caching/interfaces/caching.ICachingProviderService" + }, { "filePath": "/www/apps/resources/references/cart/IBigNumber/methods/cart.IBigNumber.toJSON/page.mdx", "pathname": "/references/cart/IBigNumber/methods/cart.IBigNumber.toJSON" @@ -14187,6 +14219,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/references/modules/cache/page.mdx", "pathname": "/references/modules/cache" }, + { + "filePath": "/www/apps/resources/references/modules/caching/page.mdx", + "pathname": "/references/modules/caching" + }, { "filePath": "/www/apps/resources/references/modules/cart/page.mdx", "pathname": "/references/modules/cart" diff --git a/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs b/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs index 0a67fbf627..6b4196813c 100644 --- a/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs +++ b/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs @@ -62,6 +62,14 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = { "path": "https://docs.medusajs.com/resources/infrastructure-modules/cache/create", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Create Caching Provider", + "path": "https://docs.medusajs.com/resources/references/caching-module-provider", + "children": [] + }, { "loaded": true, "isPathHref": true, @@ -190,6 +198,14 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = { "path": "https://docs.medusajs.com/resources/references/cache-service", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Use Caching Module", + "path": "https://docs.medusajs.com/resources/references/caching-service", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs b/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs index f1c6febbe4..6902ddfdc2 100644 --- a/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs +++ b/www/apps/resources/generated/generated-infrastructure-modules-sidebar.mjs @@ -90,8 +90,94 @@ const generatedgeneratedInfrastructureModulesSidebarSidebar = { "loaded": true, "isPathHref": true, "type": "category", - "title": "Cache Module", + "title": "Caching Module", "initialOpen": true, + "children": [ + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/infrastructure-modules/caching", + "title": "Overview", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/infrastructure-modules/caching/concepts", + "title": "Concepts", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/infrastructure-modules/caching/migrate-cache", + "title": "Migrate from Cache Module", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "title": "Providers", + "path": "/infrastructure-modules/caching/providers", + "children": [ + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/infrastructure-modules/caching/providers/redis", + "title": "Redis", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/infrastructure-modules/caching/guides/memcached", + "title": "Memcached", + "children": [] + } + ] + }, + { + "loaded": true, + "isPathHref": true, + "type": "sub-category", + "title": "Guides", + "children": [ + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/references/caching-module-provider", + "title": "Create Caching Module Provider", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/references/caching-service", + "title": "Use Caching Module", + "children": [] + } + ] + } + ] + }, + { + "loaded": true, + "isPathHref": true, + "type": "category", + "title": "Cache Module", + "initialOpen": false, + "badge": { + "variant": "neutral", + "text": "Deprecated" + }, "children": [ { "loaded": true, diff --git a/www/apps/resources/generated/slug-changes.mjs b/www/apps/resources/generated/slug-changes.mjs index 1e8e192f67..b11986274b 100644 --- a/www/apps/resources/generated/slug-changes.mjs +++ b/www/apps/resources/generated/slug-changes.mjs @@ -164,6 +164,16 @@ export const slugChanges = [ "newSlug": "/references/cache-service", "filePath": "/www/apps/resources/references/cache/interfaces/cache.ICacheService/page.mdx" }, + { + "origSlug": "/references/caching/interfaces/caching.ICachingModuleService", + "newSlug": "/references/caching-service", + "filePath": "/www/apps/resources/references/caching/interfaces/caching.ICachingModuleService/page.mdx" + }, + { + "origSlug": "/references/caching/interfaces/caching.ICachingProviderService", + "newSlug": "/references/caching-module-provider", + "filePath": "/www/apps/resources/references/caching/interfaces/caching.ICachingProviderService/page.mdx" + }, { "origSlug": "/references/cart/ICartModuleService/methods/cart.ICartModuleService.addLineItemAdjustments", "newSlug": "/references/cart/addLineItemAdjustments", diff --git a/www/apps/resources/references/_index.mdx b/www/apps/resources/references/_index.mdx index 6d3790c610..ffb4427815 100644 --- a/www/apps/resources/references/_index.mdx +++ b/www/apps/resources/references/_index.mdx @@ -12,6 +12,7 @@ import { TypeList } from "docs-ui" - [auth-models](modules/auth_models/page.mdx) - [auth-provider](modules/auth_provider/page.mdx) - [cache](modules/cache/page.mdx) +- [caching](modules/caching/page.mdx) - [cart](modules/cart/page.mdx) - [cart-models](modules/cart_models/page.mdx) - [core-flows](modules/core_flows/page.mdx) diff --git a/www/apps/resources/references/cache/interfaces/cache.ICacheService/page.mdx b/www/apps/resources/references/cache/interfaces/cache.ICacheService/page.mdx index a9c5c0c28a..e486f8c07c 100644 --- a/www/apps/resources/references/cache/interfaces/cache.ICacheService/page.mdx +++ b/www/apps/resources/references/cache/interfaces/cache.ICacheService/page.mdx @@ -17,6 +17,13 @@ import { TypeList } from "docs-ui" In this document, you’ll learn about the different methods in the Cache Module's service and how to use them. +:::note[Deprecation Notice] + +The Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). [Use the Caching Module](#) instead. + +::: + + --- ## Resolve Cache Module's Service diff --git a/www/apps/resources/references/caching/interfaces/caching.ICachingModuleService/page.mdx b/www/apps/resources/references/caching/interfaces/caching.ICachingModuleService/page.mdx new file mode 100644 index 0000000000..f319352aa5 --- /dev/null +++ b/www/apps/resources/references/caching/interfaces/caching.ICachingModuleService/page.mdx @@ -0,0 +1,295 @@ +--- +slug: /references/caching-service +tags: + - caching + - server + - how to +sidebar_label: Use Caching Module +--- + +import { TypeList } from "docs-ui" + +# How to Use Caching Module + +In this guide, you’ll learn about the different methods in the Caching Module's service and how to use them. + +:::note + +The Caching Module and its providers are available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +::: + +:::tip + +You should use the Caching Module's service when you're caching computed data or data from external APIs. To cache database query results, enable caching in [Query](!docs!/learn/fundamentals/module-links/query#cache) or [Index Module](!docs!/learn/fundamentals/module-links/index-module#cache) instead. + +::: + +--- + +## Resolve Caching Module's Service + +In your workflow's step, you can resolve the Caching Module's service from the Medusa container: + +```ts +import { Modules } from "@medusajs/framework/utils" +import { createStep } from "@medusajs/framework/workflows-sdk" + +const step1 = createStep( + "step-1", + async ({}, { container }) => { + const cachingModuleService = container.resolve( + Modules.CACHING + ) + + // TODO use cachingModuleService + } +) +``` + +You can then use the Caching Module's service's methods in the step. The rest of this guide details these methods. + +--- + +## clear + +This method clears data from the cache. If neither `key` nor `tags` are provided, nothing is cleared. + +By default, all items matching the key or tags are cleared regardless of their options. If you provide `options.autoInvalidate: true`, +only items that were set with `options.autoInvalidate: true` are cleared. + +For example, if you set `options.autoInvalidate: true`, only items that were set with `options.autoInvalidate: true` are cleared. +Items that were set with `options.autoInvalidate: false` are only cleared when you don't provide any options. + +### Example + +To invalidate cache by key: + +```ts +await cacheModuleService.clear({ + key: "products" // this key would typically be a hash +}) +``` + +This example will clear the item with the key `products` regardless of its `options.autoInvalidate` value. + +To invalidate cache by tags: + +```ts +await cacheModuleService.clear({ + tags: ["Product:list:*"] +}) +``` + +This example will clear all items with the tag `Product:list:*` regardless of their `options.autoInvalidate` value. + +To invalidate only the cache data that were set to automatically invalidate: + +```ts +await cacheModuleService.clear({ + tags: ["Product:list:*"], + options: { autoInvalidate: true } +}) +``` + +This example will only clear items with the tag `Product:list:*` that were set with `options.autoInvalidate: true`. +Items that were set with `options.autoInvalidate: false` will not be cleared. + +:::note + +Setting `options.autoInvalidate: false` when calling the `clear` method will not clear any items. +To clear items that were set with `options.autoInvalidate: false`, you must call the `clear` method without any options. + +::: + +To invalidate cache from specific providers: + +```ts +await cacheModuleService.clear({ + key: "products", + providers: ["caching-redis", "caching-memcached"] +}) +``` + +This example will try to clear the data from both the `caching-redis` and `caching-memcached` providers. + +### Parameters + + + +### Returns + + + +___ + +## computeKey + +This method computes a cache key based on the input object. It's useful to generate consistent and unique keys for caching. + +### Example + +```ts +const key = await cacheModuleService.computeKey({ + id: "prod_123", + title: "Product 123" +}) +// key will be a hash string like "a1b2c3d4e5f6g7h8i9j0" +``` + +### Parameters + + + +### Returns + + + +___ + +## computeTags + +This method computes cache tags based on the input object. It's useful to generate consistent and relevant tags for caching. + +### Example + +```ts +const tags = await cacheModuleService.computeTags({ + products: [{ id: "prod_123" }, { id: "prod_456" }], +}, { + operation: "updated" +}) +// tags might be ["Product:prod_123", "Product:prod_456", "Product:list:*"] +``` + +### Parameters + +`","description":"Additional options to influence tag computation.","optional":true,"defaultValue":"","expandable":false,"children":[]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="computeTags"/> + +### Returns + + + +___ + +## get + +This method retrieves data from the cache. If neither `key` nor `tags` are provided, or the item is not found, `null` is returned. + +### Example + +To retrieve by key: + +```ts +const data = await cacheModuleService.get({ + key: "products", // this key would typically be a hash +}) as { id: string; title: string; } +``` + +To retrieve by tags: + +```ts +const data = await cacheModuleService.get({ + tags: ["Product:list:*"], +}) as { id: string; title: string; }[] +``` + +To retrieve by key from specific providers: + +```ts +const data = await cacheModuleService.get({ + key: "products", // this key would typically be a hash + providers: ["caching-redis", "caching-memcached"] +}) as { id: string; title: string; } +``` + +This example will try to get the data from the `caching-redis` provider first, and if not found, it will try to get it from the `caching-memcached` provider. + +### Type Parameters + + + +### Parameters + + + +### Returns + + + +___ + +## set + +This method stores data in the cache using the +[default Caching Module Provider](https://docs.medusajs.com/infrastructure-modules/caching/providers#default-caching-module-provider). + +### Example + +To store with key: + +```ts +const data = { id: "prod_123", title: "Product 123" } +const key = await cacheModuleService.computeKey(data) +await cacheModuleService.set({ + key, + data +}) +``` + +To store with tags: + +:::note + +Tags should follow [conventions](https://docs.medusajs.com/infrastructure-modules/caching/concepts#caching-tags-convention) to ensure they're automatically invalidated. + +::: + +```ts +const data = [{ id: "prod_123", title: "Product 123" }] +const key = await cacheModuleService.computeKey(data) +await cacheModuleService.set({ + key, + data, + tags: [`Product:${data[0].id}`, "Product:list:*"] +}) +``` + +To disable auto-invalidation for the item: + +```ts +const data = [{ id: "prod_123", title: "Product 123" }] +const key = await cacheModuleService.computeKey(data) +await cacheModuleService.set({ + key, + data, + options: { autoInvalidate: false } +}) +``` + +The item is now only invalidated when calling the `clear` method directly with the same key or tags. + +To store with specific providers: + +```ts +const data = { id: "prod_123", title: "Product 123" } +const key = await cacheModuleService.computeKey(data) +await cacheModuleService.set({ + key, + data, + providers: [ + "caching-redis", + { id: "caching-memcached", ttl: 120 } // custom TTL for this provider + ] +}) +``` + +This example will store the item in both the `caching-redis` and `caching-memcached` providers, with a custom TTL of `120` seconds for the `caching-memcached` provider. + +### Parameters + + + +### Returns + + diff --git a/www/apps/resources/references/caching/interfaces/caching.ICachingProviderService/page.mdx b/www/apps/resources/references/caching/interfaces/caching.ICachingProviderService/page.mdx new file mode 100644 index 0000000000..4272603890 --- /dev/null +++ b/www/apps/resources/references/caching/interfaces/caching.ICachingProviderService/page.mdx @@ -0,0 +1,333 @@ +--- +slug: /references/caching-module-provider +tags: + - caching + - server + - how to +sidebar_label: Create Caching Provider +keywords: + - caching + - provider + - integration +--- + +import { TypeList } from "docs-ui" + +# How to Create a Caching Module Provider + +In this guide, you’ll learn how to create a Caching Module Provider and the methods you must implement in its main service. + +:::note + +The Caching Module and its providers are available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +::: + +--- + +## Implementation Example + +As you implement your Caching Module Provider, it can be useful to refer to an existing provider and how it's implemeted. + +If you need to refer to an existing implementation as an example, check the [Redis Caching Module Provider in the Medusa repository](https://github.com/medusajs/medusa/tree/develop/packages/modules/providers/caching-redis). + +--- + +## 1. Create Module Provider Directory + +Start by creating a new directory for your module provider. + +If you're creating the module provider in a Medusa application, create it under the `src/modules` directory. For example, `src/modules/my-caching`. + +If you're creating the module provider in a plugin, create it under the `src/providers` directory. For example, `src/providers/my-caching`. + + + +The rest of this guide always uses the `src/modules/my-caching` directory as an example. + + + +--- + +## 2. Create the Caching Module Provider Service + +Create the file `src/modules/my-caching/service.ts` that holds the module provider's main service. It must implement the `ICachingProviderService` interface imported from `@medusajs/framework/types`: + +```ts title="src/modules/my-caching/service.ts" +import { ICachingProviderService } from "@medusajs/framework/types" + +class MyCachingProviderService implements ICachingProviderService { + // TODO implement methods +} + +export default MyCachingProviderService +``` + +### constructor + +The constructor allows you to access resources from the module's container using the first parameter, +and the module's options using the second parameter. + +If you're creating a client or establishing a connection with a third-party service, do it in a [Loader](https://docs.medusajs.com/learn/fundamentals/modules/loaders) +and store it in the Module's container. Then, you can access it in your service using the container. + +:::note[Loader Example] + +[Initialize MongoDB client in loader and access it in service](https://docs.medusajs.com/learn/fundamentals/modules/loaders#example-register-custom-mongodb-connection). + +::: + +#### Example + +```ts +import { ICachingProviderService } from "@medusajs/framework/types" +import { Logger } from "@medusajs/framework/types" + +type InjectedDependencies = { + logger: Logger + // assuming you initialized a client + // in a Loader and stored it in the container + client: Client +} + +type Options = { + url: string +} + +class MyCachingModuleProvider implements ICachingProviderService { + static identifier = "my-cache" + protected logger_: Logger + protected options_: Options + protected client + + constructor ( + { logger, client }: InjectedDependencies, + options: Options + ) { + this.logger_ = logger + this.options_ = options + // set the service's client to + // the client from the container + this.client = client + } + + // ... +} + +export default MyCachingModuleProvider +``` + +### Identifier + +Every caching module provider must have an `identifier` static property. The provider's ID +will be stored as `lp_{identifier}_{id}`, where `id` is the ID you set in your `medusa-config.ts` file. + +For example: + +```ts +class MyCachingModuleProvider implements ICachingProviderService { + static identifier = "my-cache" + // ... +} +``` + +### clear + +This method clears data from the cache. If no options are specified, all items matching the key or tags should be cleared. +Otherwise, if `options.autoInvalidate` is `true`, only items that were set with `options.autoInvalidate: true` should be cleared. + +Items with `options.autoInvalidate: false` should only be cleared when no options are provided. + +If neither `key` nor `tags` are provided, nothing should be cleared. + +#### Example + +```ts +async clear({ key, tags, options, }: { + key?: string; + tags?: string[]; + options?: { autoInvalidate?: boolean } +}): Promise { + if (!options) { + // clear all items + await this.client.invalidate({ key, tags }) + } else if (options.autoInvalidate) { + // clear only items with autoInvalidate option set to true + const keysToDelete: string[] = [] + const storedOptions = await this.client.get({ key, tags, pipeline: "options" }) + storedOptions.forEach((item) => { + if (item.autoInvalidate) { + keysToDelete.push(item.key as string) + } + }) + await this.client.invalidate({ keys: keysToDelete }) + } +} +``` + +#### Parameters + + + +#### Returns + + + +### get + +This method retrieves data from the cache either by `key` or `tags`. If neither `key` nor `tags` are provided, `null` should be returned. +If both `key` and `tags` are provided, `key` should take precedence over `tags`. + +#### Example + +```ts +class MyCachingModuleProvider implements ICachingProviderService { + // ... + async get({ key, tags }: { key?: string; tags?: string[] }): Promise { + // Assuming you're using a client to get data + if (key) { + return await this.client.get({ key }) + } + if (tags) { + return await this.client.getByTags({ tags }) + } + return null + } +} +``` + +#### Parameters + + + +#### Returns + + + +### set + +This method stores data in the cache. It should also store the options with the item, +allowing you to later to check the `autoInvalidate` option when clearing the item. + +#### Example + +```ts +class MyCachingModuleProvider implements ICachingProviderService { + // ... + async set({ key, data, ttl, tags, options }: { + key: string; + data: any; + ttl?: number; + tags?: string[]; + options?: { autoInvalidate?: boolean } + }): Promise { + // Assuming you're using a client to set data + await this.client.set({ key, data, ttl, tags }) + await this.client.set({ key, data: options, pipeline: "options" }) + } +} +``` + +#### Parameters + + + +#### Returns + + + +--- + +## 3. Create Module Definition File + +Create the file `src/modules/my-caching/index.ts` with the following content: + +```ts title="src/modules/my-caching/index.ts" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" +import MyCachingProviderService from "./service" + +export default ModuleProvider(Modules.CACHING, { + services: [MyCachingProviderService], +}) +``` + +This exports the module provider's definition, indicating that the `MyCachingProviderService` is the module provider's service. + +--- + +## 4. Use Module Provider + +To use your Caching Module Provider, add it to the `providers` array of the Caching Module in `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + // if module provider is in a plugin, use `plugin-name/providers/my-caching` + resolve: "./src/modules/my-caching", + id: "my-caching", + // set this if you want this provider to be used by default + // and you have other Caching Module Providers registered. + is_default: true, + options: { + url: "http://example.com", + // provider options... + } + }, + ] + } + } + ] +}) +``` + +--- + +## 5. Test it Out + +To test out your Caching Module Provider, create a simple API route that retrieves cached data with Query: + +```ts title="src/api/test-caching/route.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const query = req.scope.resolve("query") + + const { data } = await query.graph({ + entity: "product", + fields: ["id", "title"], + }, { + cache: { + enable: true, + providers: ["my-caching"], // use your provider id here + } + }) + + res.status(200).json({ data }) +} +``` + +Then, start your Medusa server with the following command: + +```bash npm2yarn +npm run dev +``` + +Next, send a `GET` request to `http://localhost:9000/test-caching`: + +```bash +curl http://localhost:9000/test-caching +``` + +You will receive a response with the list of products. The first time you make this request, the products will be fetched from the database and cached in memory. Subsequent requests will retrieve the products from the cache, which improves performance. + +--- + +## Useful Guides + +- [How to Use Caching Module](/references/caching-service) diff --git a/www/apps/resources/references/modules/caching/page.mdx b/www/apps/resources/references/modules/caching/page.mdx new file mode 100644 index 0000000000..7a3c8a534e --- /dev/null +++ b/www/apps/resources/references/modules/caching/page.mdx @@ -0,0 +1,8 @@ +import { TypeList } from "docs-ui" + +# caching + +## Interfaces + +- [ICachingModuleService](../../caching/interfaces/caching.ICachingModuleService/page.mdx) +- [ICachingProviderService](../../caching/interfaces/caching.ICachingProviderService/page.mdx) diff --git a/www/apps/resources/sidebars/infrastructure-modules.mjs b/www/apps/resources/sidebars/infrastructure-modules.mjs index 6051aada3c..93387d5aa3 100644 --- a/www/apps/resources/sidebars/infrastructure-modules.mjs +++ b/www/apps/resources/sidebars/infrastructure-modules.mjs @@ -59,8 +59,67 @@ export const infrastructureModulesSidebar = [ }, { type: "category", - title: "Cache Module", + title: "Caching Module", initialOpen: true, + children: [ + { + type: "link", + path: "/infrastructure-modules/caching", + title: "Overview", + }, + { + type: "link", + path: "/infrastructure-modules/caching/concepts", + title: "Concepts", + }, + { + type: "link", + path: "/infrastructure-modules/caching/migrate-cache", + title: "Migrate from Cache Module", + }, + { + type: "link", + title: "Providers", + path: "/infrastructure-modules/caching/providers", + children: [ + { + type: "link", + path: "/infrastructure-modules/caching/providers/redis", + title: "Redis", + }, + { + type: "link", + path: "/infrastructure-modules/caching/guides/memcached", + title: "Memcached", + }, + ], + }, + { + type: "sub-category", + title: "Guides", + children: [ + { + type: "link", + path: "/references/caching-module-provider", + title: "Create Caching Module Provider", + }, + { + type: "link", + path: "/references/caching-service", + title: "Use Caching Module", + }, + ], + }, + ], + }, + { + type: "category", + title: "Cache Module", + initialOpen: false, + badge: { + variant: "neutral", + text: "Deprecated", + }, children: [ { type: "link", diff --git a/www/apps/resources/utils/get-sidebar-for-path.ts b/www/apps/resources/utils/get-sidebar-for-path.ts index 1917f397eb..f232332fd9 100644 --- a/www/apps/resources/utils/get-sidebar-for-path.ts +++ b/www/apps/resources/utils/get-sidebar-for-path.ts @@ -77,6 +77,8 @@ const sidebarMappings: { "/references/cache-service", "/references/file-service", "/references/analytics", + "/references/caching-service", + "/references/caching-module-provider", ], }, { diff --git a/www/eslint.config.mjs b/www/eslint.config.mjs index ee47933a0a..30d6e317f4 100644 --- a/www/eslint.config.mjs +++ b/www/eslint.config.mjs @@ -121,9 +121,7 @@ export default [ allow: ["error", "warn"], }], - "react/prop-types": [2, { - ignore: ["className"], - }], + "react/prop-types": "off", }, }, ...compat.extends("plugin:@typescript-eslint/recommended", "plugin:react/recommended").map(config => ({ ...config, @@ -162,6 +160,8 @@ export default [ "@/space-infix-ops": "error", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": "warn", + + "react/prop-types": "off", }, settings: { next: { diff --git a/www/packages/docs-ui/src/components/TypeList/Items/index.tsx b/www/packages/docs-ui/src/components/TypeList/Items/index.tsx index 515542ef28..496f16f4b2 100644 --- a/www/packages/docs-ui/src/components/TypeList/Items/index.tsx +++ b/www/packages/docs-ui/src/components/TypeList/Items/index.tsx @@ -9,6 +9,7 @@ import { FeatureFlagNotice, InlineCode, MarkdownContent, + MDXComponents, } from "@/components" import clsx from "clsx" import { Type, CommonProps as ParentCommonProps } from ".." @@ -136,9 +137,36 @@ const TypeListItem = ({ <> {item.description && ( ) => ( + // @ts-expect-error Not recognized as a JSX element + + ), + li: (props: React.HTMLAttributes) => ( + // @ts-expect-error Not recognized as a JSX element + + ), + }} > {item.description} diff --git a/www/packages/docs-ui/src/constants.tsx b/www/packages/docs-ui/src/constants.tsx index a725cdfe7a..9fed3417a2 100644 --- a/www/packages/docs-ui/src/constants.tsx +++ b/www/packages/docs-ui/src/constants.tsx @@ -185,8 +185,8 @@ export const navDropdownItems: NavigationItem[] = [ }, { type: "link", - title: "Cache", - link: "/resources/infrastructure-modules/cache", + title: "Caching", + link: "/resources/infrastructure-modules/caching", }, { type: "link", diff --git a/www/packages/tags/src/tags/caching.ts b/www/packages/tags/src/tags/caching.ts new file mode 100644 index 0000000000..6f94041107 --- /dev/null +++ b/www/packages/tags/src/tags/caching.ts @@ -0,0 +1,10 @@ +export const caching = [ + { + "title": "Use Caching Module", + "path": "https://docs.medusajs.com/resources/references/caching-service" + }, + { + "title": "Create Caching Provider", + "path": "https://docs.medusajs.com/resources/references/caching-module-provider" + } +] \ No newline at end of file diff --git a/www/packages/tags/src/tags/how-to.ts b/www/packages/tags/src/tags/how-to.ts index 0add92c897..9112d86481 100644 --- a/www/packages/tags/src/tags/how-to.ts +++ b/www/packages/tags/src/tags/how-to.ts @@ -59,6 +59,14 @@ export const howTo = [ "title": "Use Cache Module", "path": "https://docs.medusajs.com/resources/references/cache-service" }, + { + "title": "Use Caching Module", + "path": "https://docs.medusajs.com/resources/references/caching-service" + }, + { + "title": "Create Caching Provider", + "path": "https://docs.medusajs.com/resources/references/caching-module-provider" + }, { "title": "Use Event Module", "path": "https://docs.medusajs.com/resources/references/event-service" diff --git a/www/packages/tags/src/tags/index.ts b/www/packages/tags/src/tags/index.ts index b5372633b1..57e231e482 100644 --- a/www/packages/tags/src/tags/index.ts +++ b/www/packages/tags/src/tags/index.ts @@ -3,6 +3,7 @@ export * from "./analytics.js" export * from "./api-key.js" export * from "./auth.js" export * from "./cache.js" +export * from "./caching.js" export * from "./cart.js" export * from "./checkout.js" export * from "./concept.js" diff --git a/www/packages/tags/src/tags/server.ts b/www/packages/tags/src/tags/server.ts index 1844a6ac5e..23607c4c97 100644 --- a/www/packages/tags/src/tags/server.ts +++ b/www/packages/tags/src/tags/server.ts @@ -171,6 +171,14 @@ export const server = [ "title": "Use Cache Module", "path": "https://docs.medusajs.com/resources/references/cache-service" }, + { + "title": "Use Caching Module", + "path": "https://docs.medusajs.com/resources/references/caching-service" + }, + { + "title": "Create Caching Provider", + "path": "https://docs.medusajs.com/resources/references/caching-module-provider" + }, { "title": "Use Event Module", "path": "https://docs.medusajs.com/resources/references/event-service" diff --git a/www/utils/generated/typedoc-json-output/cache.json b/www/utils/generated/typedoc-json-output/cache.json index a1108fcccf..0a993148d8 100644 --- a/www/utils/generated/typedoc-json-output/cache.json +++ b/www/utils/generated/typedoc-json-output/cache.json @@ -1,19 +1,19 @@ { - "id": 61101, + "id": 0, "name": "cache", "variant": "project", "kind": 1, "flags": {}, "children": [ { - "id": 61102, + "id": 1, "name": "ICacheService", "variant": "declaration", "kind": 256, "flags": {}, "children": [ { - "id": 61103, + "id": 2, "name": "get", "variant": "declaration", "kind": 2048, @@ -23,12 +23,12 @@ "fileName": "service.ts", "line": 11, "character": 2, - "url": "https://github.com/medusajs/medusa/blob/a503bbe5963f95710f599c466dfb2c3956914e06/packages/core/types/src/cache/service.ts#L11" + "url": "https://github.com/medusajs/medusa/blob/54cf8811af626df63b1b093561bd0337d1f786eb/packages/core/types/src/cache/service.ts#L11" } ], "signatures": [ { - "id": 61104, + "id": 3, "name": "get", "variant": "signature", "kind": 4096, @@ -66,12 +66,12 @@ "fileName": "service.ts", "line": 11, "character": 2, - "url": "https://github.com/medusajs/medusa/blob/a503bbe5963f95710f599c466dfb2c3956914e06/packages/core/types/src/cache/service.ts#L11" + "url": "https://github.com/medusajs/medusa/blob/54cf8811af626df63b1b093561bd0337d1f786eb/packages/core/types/src/cache/service.ts#L11" } ], "typeParameters": [ { - "id": 61105, + "id": 4, "name": "T", "variant": "typeParam", "kind": 131072, @@ -80,7 +80,7 @@ ], "parameters": [ { - "id": 61106, + "id": 5, "name": "key", "variant": "param", "kind": 32768, @@ -115,7 +115,7 @@ }, { "type": "reference", - "target": 61105, + "target": 4, "name": "T", "package": "@medusajs/types", "refersToTypeParameter": true @@ -130,7 +130,7 @@ ] }, { - "id": 61107, + "id": 6, "name": "set", "variant": "declaration", "kind": 2048, @@ -140,12 +140,12 @@ "fileName": "service.ts", "line": 22, "character": 2, - "url": "https://github.com/medusajs/medusa/blob/a503bbe5963f95710f599c466dfb2c3956914e06/packages/core/types/src/cache/service.ts#L22" + "url": "https://github.com/medusajs/medusa/blob/54cf8811af626df63b1b093561bd0337d1f786eb/packages/core/types/src/cache/service.ts#L22" } ], "signatures": [ { - "id": 61108, + "id": 7, "name": "set", "variant": "signature", "kind": 4096, @@ -174,12 +174,12 @@ "fileName": "service.ts", "line": 22, "character": 2, - "url": "https://github.com/medusajs/medusa/blob/a503bbe5963f95710f599c466dfb2c3956914e06/packages/core/types/src/cache/service.ts#L22" + "url": "https://github.com/medusajs/medusa/blob/54cf8811af626df63b1b093561bd0337d1f786eb/packages/core/types/src/cache/service.ts#L22" } ], "parameters": [ { - "id": 61109, + "id": 8, "name": "key", "variant": "param", "kind": 32768, @@ -198,7 +198,7 @@ } }, { - "id": 61110, + "id": 9, "name": "data", "variant": "param", "kind": 32768, @@ -217,7 +217,7 @@ } }, { - "id": 61111, + "id": 10, "name": "ttl", "variant": "param", "kind": 32768, @@ -257,7 +257,7 @@ ] }, { - "id": 61112, + "id": 11, "name": "invalidate", "variant": "declaration", "kind": 2048, @@ -267,12 +267,12 @@ "fileName": "service.ts", "line": 31, "character": 2, - "url": "https://github.com/medusajs/medusa/blob/a503bbe5963f95710f599c466dfb2c3956914e06/packages/core/types/src/cache/service.ts#L31" + "url": "https://github.com/medusajs/medusa/blob/54cf8811af626df63b1b093561bd0337d1f786eb/packages/core/types/src/cache/service.ts#L31" } ], "signatures": [ { - "id": 61113, + "id": 12, "name": "invalidate", "variant": "signature", "kind": 4096, @@ -301,12 +301,12 @@ "fileName": "service.ts", "line": 31, "character": 2, - "url": "https://github.com/medusajs/medusa/blob/a503bbe5963f95710f599c466dfb2c3956914e06/packages/core/types/src/cache/service.ts#L31" + "url": "https://github.com/medusajs/medusa/blob/54cf8811af626df63b1b093561bd0337d1f786eb/packages/core/types/src/cache/service.ts#L31" } ], "parameters": [ { - "id": 61114, + "id": 13, "name": "key", "variant": "param", "kind": 32768, @@ -348,9 +348,9 @@ { "title": "Methods", "children": [ - 61103, - 61107, - 61112 + 2, + 6, + 11 ] } ], @@ -359,7 +359,7 @@ "fileName": "service.ts", "line": 1, "character": 17, - "url": "https://github.com/medusajs/medusa/blob/a503bbe5963f95710f599c466dfb2c3956914e06/packages/core/types/src/cache/service.ts#L1" + "url": "https://github.com/medusajs/medusa/blob/54cf8811af626df63b1b093561bd0337d1f786eb/packages/core/types/src/cache/service.ts#L1" } ] } @@ -368,65 +368,65 @@ { "title": "Interfaces", "children": [ - 61102 + 1 ] } ], "packageName": "@medusajs/types", "symbolIdMap": { - "61101": { + "0": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "" }, - "61102": { + "1": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ICacheService" }, - "61103": { + "2": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ICacheService.get" }, - "61104": { + "3": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ICacheService.get" }, - "61105": { + "4": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "T" }, - "61106": { + "5": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "key" }, - "61107": { + "6": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ICacheService.set" }, - "61108": { + "7": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ICacheService.set" }, - "61109": { + "8": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "key" }, - "61110": { + "9": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "data" }, - "61111": { + "10": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ttl" }, - "61112": { + "11": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ICacheService.invalidate" }, - "61113": { + "12": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "ICacheService.invalidate" }, - "61114": { + "13": { "sourceFileName": "../../../../packages/core/types/src/cache/service.ts", "qualifiedName": "key" } @@ -436,7 +436,7 @@ "1": "../../../../packages/core/types/src/cache/service.ts" }, "reflections": { - "1": 61101 + "1": 0 } } } diff --git a/www/utils/generated/typedoc-json-output/caching.json b/www/utils/generated/typedoc-json-output/caching.json new file mode 100644 index 0000000000..52af429dd4 --- /dev/null +++ b/www/utils/generated/typedoc-json-output/caching.json @@ -0,0 +1,2942 @@ +{ + "id": 0, + "name": "caching", + "variant": "project", + "kind": 1, + "flags": {}, + "children": [ + { + "id": 1, + "name": "ICachingModuleService", + "variant": "declaration", + "kind": 256, + "flags": {}, + "comment": { + "summary": [], + "blockTags": [ + { + "tag": "@since", + "content": [ + { + "kind": "text", + "text": "v2.11.0" + } + ] + } + ] + }, + "children": [ + { + "id": 2, + "name": "get", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 74, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L74" + } + ], + "signatures": [ + { + "id": 3, + "name": "get", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method retrieves data from the cache. If neither " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " nor " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": " are provided, or the item is not found, " + }, + { + "kind": "code", + "text": "`null`" + }, + { + "kind": "text", + "text": " is returned." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "The item(s) that was stored in the cache. If no item was found, or neither " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " nor " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": " were provided, " + }, + { + "kind": "code", + "text": "`null`" + }, + { + "kind": "text", + "text": " is returned." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "text", + "text": "To retrieve by key:\n\n" + }, + { + "kind": "code", + "text": "```ts\nconst data = await cacheModuleService.get({\n key: \"products\", // this key would typically be a hash\n}) as { id: string; title: string; }\n```" + }, + { + "kind": "text", + "text": "\n\nTo retrieve by tags:\n\n" + }, + { + "kind": "code", + "text": "```ts\nconst data = await cacheModuleService.get({\n tags: [\"Product:list:*\"],\n}) as { id: string; title: string; }[]\n```" + }, + { + "kind": "text", + "text": "\n\nTo retrieve by key from specific providers:\n\n" + }, + { + "kind": "code", + "text": "```ts\nconst data = await cacheModuleService.get({\n key: \"products\", // this key would typically be a hash\n providers: [\"caching-redis\", \"caching-memcached\"]\n}) as { id: string; title: string; }\n```" + }, + { + "kind": "text", + "text": "\n\nThis example will try to get the data from the " + }, + { + "kind": "code", + "text": "`caching-redis`" + }, + { + "kind": "text", + "text": " provider first, and if not found, it will try to get it from the " + }, + { + "kind": "code", + "text": "`caching-memcached`" + }, + { + "kind": "text", + "text": " provider." + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 74, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L74" + } + ], + "typeParameters": [ + { + "id": 4, + "name": "T", + "variant": "typeParam", + "kind": 131072, + "flags": {} + } + ], + "parameters": [ + { + "id": 5, + "name": "param0", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The options for retrieving the item." + } + ] + }, + "type": { + "type": "reflection", + "declaration": { + "id": 6, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 7, + "name": "key", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The key of the item to retrieve.\nIf both " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " and " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": " are provided, " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " takes precedence over " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": "." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 83, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L83" + } + ], + "type": { + "type": "intrinsic", + "name": "string" + } + }, + { + "id": 8, + "name": "tags", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The tags of the items to retrieve. Tags\nare useful to retrieve multiple related items at once." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 88, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L88" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + }, + { + "id": 9, + "name": "providers", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The providers to retrieve the item(s) from. You can specify an array of provider IDs.\nThey're checked in the order they're provided in, so make sure to order them based on your priority.\nIf not provided, the [default provider](https://docs.medusajs.com/infrastructure-modules/caching/providers#default-caching-module-provider) is used." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 94, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L94" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 7, + 8, + 9 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 78, + "character": 5, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L78" + } + ] + } + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "union", + "types": [ + { + "type": "literal", + "value": null + }, + { + "type": "reference", + "target": 4, + "name": "T", + "package": "@medusajs/types", + "refersToTypeParameter": true + } + ] + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + }, + { + "id": 10, + "name": "set", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 166, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L166" + } + ], + "signatures": [ + { + "id": 11, + "name": "set", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method stores data in the cache using the \n[default Caching Module Provider](https://docs.medusajs.com/infrastructure-modules/caching/providers#default-caching-module-provider)." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "A promise that resolves when the item has been stored." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "text", + "text": "To store with key:\n\n" + }, + { + "kind": "code", + "text": "```ts\nconst data = { id: \"prod_123\", title: \"Product 123\" }\nconst key = await cacheModuleService.computeKey(data)\nawait cacheModuleService.set({\n key,\n data\n})\n```" + }, + { + "kind": "text", + "text": "\n\nTo store with tags:\n\n:::note\n\nTags should follow [conventions](https://docs.medusajs.com/infrastructure-modules/caching/concepts#caching-tags-convention) to ensure they're automatically invalidated.\n\n:::\n\n" + }, + { + "kind": "code", + "text": "```ts\nconst data = [{ id: \"prod_123\", title: \"Product 123\" }]\nconst key = await cacheModuleService.computeKey(data)\nawait cacheModuleService.set({\n key,\n data,\n tags: [`Product:${data[0].id}`, \"Product:list:*\"]\n})\n```" + }, + { + "kind": "text", + "text": "\n\nTo disable auto-invalidation for the item:\n\n" + }, + { + "kind": "code", + "text": "```ts\nconst data = [{ id: \"prod_123\", title: \"Product 123\" }]\nconst key = await cacheModuleService.computeKey(data)\nawait cacheModuleService.set({\n key,\n data,\n options: { autoInvalidate: false }\n})\n```" + }, + { + "kind": "text", + "text": "\n\nThe item is now only invalidated when calling the " + }, + { + "kind": "code", + "text": "`clear`" + }, + { + "kind": "text", + "text": " method directly with the same key or tags.\n\nTo store with specific providers:\n\n" + }, + { + "kind": "code", + "text": "```ts\nconst data = { id: \"prod_123\", title: \"Product 123\" }\nconst key = await cacheModuleService.computeKey(data)\nawait cacheModuleService.set({\n key,\n data,\n providers: [\n \"caching-redis\",\n { id: \"caching-memcached\", ttl: 120 } // custom TTL for this provider\n ]\n})\n```" + }, + { + "kind": "text", + "text": "\n\nThis example will store the item in both the " + }, + { + "kind": "code", + "text": "`caching-redis`" + }, + { + "kind": "text", + "text": " and " + }, + { + "kind": "code", + "text": "`caching-memcached`" + }, + { + "kind": "text", + "text": " providers, with a custom TTL of " + }, + { + "kind": "code", + "text": "`120`" + }, + { + "kind": "text", + "text": " seconds for the " + }, + { + "kind": "code", + "text": "`caching-memcached`" + }, + { + "kind": "text", + "text": " provider." + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 166, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L166" + } + ], + "parameters": [ + { + "id": 12, + "name": "param0", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The options for storing the item." + } + ] + }, + "type": { + "type": "reflection", + "declaration": { + "id": 13, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 14, + "name": "key", + "variant": "declaration", + "kind": 1024, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The key of the item to store." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 177, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L177" + } + ], + "type": { + "type": "intrinsic", + "name": "string" + } + }, + { + "id": 15, + "name": "data", + "variant": "declaration", + "kind": 1024, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The data to store in the cache." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 181, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L181" + } + ], + "type": { + "type": "intrinsic", + "name": "object" + } + }, + { + "id": 16, + "name": "ttl", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The time-to-live (TTL in seconds) value in seconds.\nIf not provided, the default TTL value configured in the provider should be used." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 186, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L186" + } + ], + "type": { + "type": "intrinsic", + "name": "number" + } + }, + { + "id": 17, + "name": "tags", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The tags of the items to store. Tags are useful to group related items \ntogether for retrieval or invalidation." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 191, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L191" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + }, + { + "id": 18, + "name": "options", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Options for storing the item. The options are stored with the item, allowing you to later match against them when clearing the item.\nFor example, if you set " + }, + { + "kind": "code", + "text": "`autoInvalidate: false`" + }, + { + "kind": "text", + "text": ", the item will only be invalidated when calling the " + }, + { + "kind": "code", + "text": "`clear`" + }, + { + "kind": "text", + "text": " method directly with the same key or tags." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 196, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L196" + } + ], + "type": { + "type": "reflection", + "declaration": { + "id": 19, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 20, + "name": "autoInvalidate", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Whether to automatically invalidate the item when related data changes." + } + ], + "blockTags": [ + { + "tag": "@defaultValue", + "content": [ + { + "kind": "code", + "text": "```ts\ntrue\n```" + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 202, + "character": 6, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L202" + } + ], + "type": { + "type": "intrinsic", + "name": "boolean" + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 20 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 196, + "character": 14, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L196" + } + ] + } + } + }, + { + "id": 21, + "name": "providers", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The providers to store the item(s) in. You can specify an array of provider IDs or an array of objects with provider ID and TTL.\nIf not provided, the [default provider](https://docs.medusajs.com/infrastructure-modules/caching/providers#default-caching-module-provider) is used." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 208, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L208" + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "Providers" + }, + "name": "Providers", + "package": "@medusajs/types" + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 14, + 15, + 16, + 17, + 18, + 21 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 173, + "character": 5, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L173" + } + ] + } + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "intrinsic", + "name": "void" + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + }, + { + "id": 22, + "name": "clear", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 274, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L274" + } + ], + "signatures": [ + { + "id": 23, + "name": "clear", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method clears data from the cache. If neither " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " nor " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": " are provided, nothing is cleared.\n\nBy default, all items matching the key or tags are cleared regardless of their options. If you provide " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: true`" + }, + { + "kind": "text", + "text": ",\nonly items that were set with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: true`" + }, + { + "kind": "text", + "text": " are cleared.\n\nFor example, if you set " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: true`" + }, + { + "kind": "text", + "text": ", only items that were set with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: true`" + }, + { + "kind": "text", + "text": " are cleared.\nItems that were set with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: false`" + }, + { + "kind": "text", + "text": " are only cleared when you don't provide any options." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "A promise that resolves when the item(s) have been cleared." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "text", + "text": "To invalidate cache by key:\n\n" + }, + { + "kind": "code", + "text": "```ts\nawait cacheModuleService.clear({\n key: \"products\" // this key would typically be a hash\n})\n```" + }, + { + "kind": "text", + "text": "\n\nThis example will clear the item with the key " + }, + { + "kind": "code", + "text": "`products`" + }, + { + "kind": "text", + "text": " regardless of its " + }, + { + "kind": "code", + "text": "`options.autoInvalidate`" + }, + { + "kind": "text", + "text": " value.\n\nTo invalidate cache by tags:\n\n" + }, + { + "kind": "code", + "text": "```ts\nawait cacheModuleService.clear({\n tags: [\"Product:list:*\"]\n})\n```" + }, + { + "kind": "text", + "text": "\n\nThis example will clear all items with the tag " + }, + { + "kind": "code", + "text": "`Product:list:*`" + }, + { + "kind": "text", + "text": " regardless of their " + }, + { + "kind": "code", + "text": "`options.autoInvalidate`" + }, + { + "kind": "text", + "text": " value.\n\nTo invalidate only the cache data that were set to automatically invalidate:\n\n" + }, + { + "kind": "code", + "text": "```ts\nawait cacheModuleService.clear({\n tags: [\"Product:list:*\"],\n options: { autoInvalidate: true }\n})\n```" + }, + { + "kind": "text", + "text": "\n\nThis example will only clear items with the tag " + }, + { + "kind": "code", + "text": "`Product:list:*`" + }, + { + "kind": "text", + "text": " that were set with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: true`" + }, + { + "kind": "text", + "text": ".\nItems that were set with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: false`" + }, + { + "kind": "text", + "text": " will not be cleared.\n\n:::note\n\nSetting " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: false`" + }, + { + "kind": "text", + "text": " when calling the " + }, + { + "kind": "code", + "text": "`clear`" + }, + { + "kind": "text", + "text": " method will not clear any items.\nTo clear items that were set with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: false`" + }, + { + "kind": "text", + "text": ", you must call the " + }, + { + "kind": "code", + "text": "`clear`" + }, + { + "kind": "text", + "text": " method without any options.\n\n:::\n\nTo invalidate cache from specific providers:\n\n" + }, + { + "kind": "code", + "text": "```ts\nawait cacheModuleService.clear({\n key: \"products\",\n providers: [\"caching-redis\", \"caching-memcached\"]\n})\n```" + }, + { + "kind": "text", + "text": "\n\nThis example will try to clear the data from both the " + }, + { + "kind": "code", + "text": "`caching-redis`" + }, + { + "kind": "text", + "text": " and " + }, + { + "kind": "code", + "text": "`caching-memcached`" + }, + { + "kind": "text", + "text": " providers." + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 274, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L274" + } + ], + "parameters": [ + { + "id": 24, + "name": "param0", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The options for clearing the item(s)." + } + ] + }, + "type": { + "type": "reflection", + "declaration": { + "id": 25, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 26, + "name": "key", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The key of the item to clear." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 283, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L283" + } + ], + "type": { + "type": "intrinsic", + "name": "string" + } + }, + { + "id": 27, + "name": "tags", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The tags of the items to clear. Tags\nare useful to clear multiple related items at once." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 288, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L288" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + }, + { + "id": 28, + "name": "options", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Options for clearing the item(s). The options are matched against the stored options when the item was set.\nFor example, if the item was set with " + }, + { + "kind": "code", + "text": "`autoInvalidate: true`" + }, + { + "kind": "text", + "text": ", it will only be cleared if the " + }, + { + "kind": "code", + "text": "`autoInvalidate`" + }, + { + "kind": "text", + "text": " option is also set to " + }, + { + "kind": "code", + "text": "`true`" + }, + { + "kind": "text", + "text": ".\nIf not provided, all items matching the key or tags are cleared regardless of their options." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 294, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L294" + } + ], + "type": { + "type": "reflection", + "declaration": { + "id": 29, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 30, + "name": "autoInvalidate", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Whether to clear item(s) that were set to automatically invalidate." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 298, + "character": 6, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L298" + } + ], + "type": { + "type": "intrinsic", + "name": "boolean" + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 30 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 294, + "character": 14, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L294" + } + ] + } + } + }, + { + "id": 31, + "name": "providers", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The providers from which to clear the item(s). You can specify an array of provider IDs.\nIf not provided, the [default provider](https://docs.medusajs.com/infrastructure-modules/caching/providers#default-caching-module-provider) is used." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 304, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L304" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 26, + 27, + 28, + 31 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 279, + "character": 5, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L279" + } + ] + } + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "intrinsic", + "name": "void" + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + }, + { + "id": 32, + "name": "computeKey", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 320, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L320" + } + ], + "signatures": [ + { + "id": 33, + "name": "computeKey", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method computes a cache key based on the input object. It's useful to generate consistent and unique keys for caching." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "The computed cache key." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "code", + "text": "```ts\nconst key = await cacheModuleService.computeKey({\n id: \"prod_123\",\n title: \"Product 123\"\n})\n// key will be a hash string like \"a1b2c3d4e5f6g7h8i9j0\"\n```" + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 320, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L320" + } + ], + "parameters": [ + { + "id": 34, + "name": "input", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The input object to compute the key from." + } + ] + }, + "type": { + "type": "intrinsic", + "name": "object" + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "intrinsic", + "name": "string" + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + }, + { + "id": 35, + "name": "computeTags", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 337, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L337" + } + ], + "signatures": [ + { + "id": 36, + "name": "computeTags", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method computes cache tags based on the input object. It's useful to generate consistent and relevant tags for caching." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "An array of computed cache tags." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "code", + "text": "```ts\nconst tags = await cacheModuleService.computeTags({\n products: [{ id: \"prod_123\" }, { id: \"prod_456\" }],\n}, {\n operation: \"updated\"\n})\n// tags might be [\"Product:prod_123\", \"Product:prod_456\", \"Product:list:*\"]\n```" + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 337, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L337" + } + ], + "parameters": [ + { + "id": 37, + "name": "input", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The input object to compute the tags from." + } + ] + }, + "type": { + "type": "intrinsic", + "name": "object" + } + }, + { + "id": 38, + "name": "options", + "variant": "param", + "kind": 32768, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Additional options to influence tag computation." + } + ] + }, + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Record" + }, + "typeArguments": [ + { + "type": "intrinsic", + "name": "string" + }, + { + "type": "intrinsic", + "name": "any" + } + ], + "name": "Record", + "package": "typescript" + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + } + ], + "groups": [ + { + "title": "Methods", + "children": [ + 2, + 10, + 22, + 32, + 35 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 18, + "character": 17, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L18" + } + ], + "extendedTypes": [ + { + "type": "reference", + "target": { + "sourceFileName": "../../../../packages/core/types/src/modules-sdk/index.ts", + "qualifiedName": "IModuleService" + }, + "name": "IModuleService", + "package": "@medusajs/types" + } + ] + }, + { + "id": 52, + "name": "ICachingProviderService", + "variant": "declaration", + "kind": 256, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "### constructor\n\nThe constructor allows you to access resources from the module's container using the first parameter,\nand the module's options using the second parameter.\n\nIf you're creating a client or establishing a connection with a third-party service, do it in a [Loader](https://docs.medusajs.com/learn/fundamentals/modules/loaders)\nand store it in the Module's container. Then, you can access it in your service using the container.\n\n:::note[Loader Example]\n\n[Initialize MongoDB client in loader and access it in service](https://docs.medusajs.com/learn/fundamentals/modules/loaders#example-register-custom-mongodb-connection).\n\n:::\n\n#### Example\n\n" + }, + { + "kind": "code", + "text": "```ts\nimport { ICachingProviderService } from \"@medusajs/framework/types\"\nimport { Logger } from \"@medusajs/framework/types\"\n\ntype InjectedDependencies = {\n logger: Logger\n // assuming you initialized a client\n // in a Loader and stored it in the container\n client: Client\n}\n\ntype Options = {\n url: string\n}\n\nclass MyCachingModuleProvider implements ICachingProviderService {\n static identifier = \"my-cache\"\n protected logger_: Logger\n protected options_: Options\n protected client\n\n constructor (\n { logger, client }: InjectedDependencies,\n options: Options\n ) {\n this.logger_ = logger\n this.options_ = options\n // set the service's client to\n // the client from the container\n this.client = client\n }\n\n // ...\n}\n\nexport default MyCachingModuleProvider\n```" + }, + { + "kind": "text", + "text": "\n\n### Identifier\n\nEvery caching module provider must have an " + }, + { + "kind": "code", + "text": "`identifier`" + }, + { + "kind": "text", + "text": " static property. The provider's ID\nwill be stored as " + }, + { + "kind": "code", + "text": "`lp_{identifier}_{id}`" + }, + { + "kind": "text", + "text": ", where " + }, + { + "kind": "code", + "text": "`id`" + }, + { + "kind": "text", + "text": " is the ID you set in your " + }, + { + "kind": "code", + "text": "`medusa-config.ts`" + }, + { + "kind": "text", + "text": " file.\n\nFor example:\n\n" + }, + { + "kind": "code", + "text": "```ts\nclass MyCachingModuleProvider implements ICachingProviderService {\n static identifier = \"my-cache\"\n // ...\n}\n```" + } + ] + }, + "children": [ + { + "id": 53, + "name": "get", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 432, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L432" + } + ], + "signatures": [ + { + "id": 54, + "name": "get", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method retrieves data from the cache either by " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " or " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": ". If neither " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " nor " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": " are provided, " + }, + { + "kind": "code", + "text": "`null`" + }, + { + "kind": "text", + "text": " should be returned.\nIf both " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " and " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": " are provided, " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " should take precedence over " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": "." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "The item(s) that was stored in the cache, or " + }, + { + "kind": "code", + "text": "`null`" + }, + { + "kind": "text", + "text": " if not found." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "code", + "text": "```ts\nclass MyCachingModuleProvider implements ICachingProviderService {\n // ...\n async get({ key, tags }: { key?: string; tags?: string[] }): Promise {\n // Assuming you're using a client to get data\n if (key) {\n return await this.client.get({ key })\n }\n if (tags) {\n return await this.client.getByTags({ tags })\n }\n return null\n }\n}\n```" + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 432, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L432" + } + ], + "parameters": [ + { + "id": 55, + "name": "param0", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The parameters for retrieving the item." + } + ] + }, + "type": { + "type": "reflection", + "declaration": { + "id": 56, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 57, + "name": "key", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The key of the item to retrieve. If both are provided, " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " should take precedence over " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": "." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 436, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L436" + } + ], + "type": { + "type": "intrinsic", + "name": "string" + } + }, + { + "id": 58, + "name": "tags", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The tags of the items to retrieve. All items with any of the provided tags should be retrieved." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 440, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L440" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 57, + 58 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 432, + "character": 21, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L432" + } + ] + } + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "intrinsic", + "name": "any" + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + }, + { + "id": 59, + "name": "set", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 465, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L465" + } + ], + "signatures": [ + { + "id": 60, + "name": "set", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method stores data in the cache. It should also store the options with the item, \nallowing you to later to check the " + }, + { + "kind": "code", + "text": "`autoInvalidate`" + }, + { + "kind": "text", + "text": " option when clearing the item." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "A promise that resolves when the item has been stored." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "code", + "text": "```ts\nclass MyCachingModuleProvider implements ICachingProviderService {\n // ...\n async set({ key, data, ttl, tags, options }: { \n key: string; \n data: any; \n ttl?: number; \n tags?: string[]; \n options?: { autoInvalidate?: boolean } \n }): Promise {\n // Assuming you're using a client to set data\n await this.client.set({ key, data, ttl, tags })\n await this.client.set({ key, data: options, pipeline: \"options\" })\n }\n}\n```" + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 465, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L465" + } + ], + "parameters": [ + { + "id": 61, + "name": "param0", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The parameters for storing the item." + } + ] + }, + "type": { + "type": "reflection", + "declaration": { + "id": 62, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 63, + "name": "key", + "variant": "declaration", + "kind": 1024, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The key of the item to store." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 475, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L475" + } + ], + "type": { + "type": "intrinsic", + "name": "string" + } + }, + { + "id": 64, + "name": "data", + "variant": "declaration", + "kind": 1024, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The data to store in the cache." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 479, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L479" + } + ], + "type": { + "type": "intrinsic", + "name": "object" + } + }, + { + "id": 65, + "name": "ttl", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The time-to-live (TTL in seconds) value in seconds.\nIf not provided, the default TTL value configured in the provider should be used." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 484, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L484" + } + ], + "type": { + "type": "intrinsic", + "name": "number" + } + }, + { + "id": 66, + "name": "tags", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The tags of the items to store. Items should be grouped together using tags for retrieval or invalidation." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 488, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L488" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + }, + { + "id": 67, + "name": "options", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Options for storing the item. The options should be stored with the item, allowing you to later match against them when clearing the item.\nFor example, if you set " + }, + { + "kind": "code", + "text": "`autoInvalidate: false`" + }, + { + "kind": "text", + "text": ", the item will only be invalidated when calling the " + }, + { + "kind": "code", + "text": "`clear`" + }, + { + "kind": "text", + "text": " method directly with the same key or tags." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 493, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L493" + } + ], + "type": { + "type": "reflection", + "declaration": { + "id": 68, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 69, + "name": "autoInvalidate", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Whether to automatically invalidate the item when related data changes." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 497, + "character": 6, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L497" + } + ], + "type": { + "type": "intrinsic", + "name": "boolean" + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 69 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 493, + "character": 14, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L493" + } + ] + } + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 63, + 64, + 65, + 66, + 67 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 471, + "character": 5, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L471" + } + ] + } + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "intrinsic", + "name": "void" + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + }, + { + "id": 70, + "name": "clear", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 533, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L533" + } + ], + "signatures": [ + { + "id": 71, + "name": "clear", + "variant": "signature", + "kind": 4096, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This method clears data from the cache. If no options are specified, all items matching the key or tags should be cleared.\nOtherwise, if " + }, + { + "kind": "code", + "text": "`options.autoInvalidate`" + }, + { + "kind": "text", + "text": " is " + }, + { + "kind": "code", + "text": "`true`" + }, + { + "kind": "text", + "text": ", only items that were set with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: true`" + }, + { + "kind": "text", + "text": " should be cleared.\n\nItems with " + }, + { + "kind": "code", + "text": "`options.autoInvalidate: false`" + }, + { + "kind": "text", + "text": " should only be cleared when no options are provided.\n\nIf neither " + }, + { + "kind": "code", + "text": "`key`" + }, + { + "kind": "text", + "text": " nor " + }, + { + "kind": "code", + "text": "`tags`" + }, + { + "kind": "text", + "text": " are provided, nothing should be cleared." + } + ], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "A promise that resolves when the item(s) have been cleared." + } + ] + }, + { + "tag": "@example", + "content": [ + { + "kind": "code", + "text": "```ts\nasync clear({ key, tags, options, }: { \n key?: string; \n tags?: string[]; \n options?: { autoInvalidate?: boolean }\n}): Promise {\n if (!options) {\n // clear all items\n await this.client.invalidate({ key, tags })\n } else if (options.autoInvalidate) {\n // clear only items with autoInvalidate option set to true\n const keysToDelete: string[] = []\n const storedOptions = await this.client.get({ key, tags, pipeline: \"options\" })\n storedOptions.forEach((item) => {\n if (item.autoInvalidate) {\n keysToDelete.push(item.key as string)\n }\n })\n await this.client.invalidate({ keys: keysToDelete })\n }\n}\n```" + } + ] + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 533, + "character": 2, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L533" + } + ], + "parameters": [ + { + "id": 72, + "name": "param0", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The parameters for clearing the item(s)." + } + ] + }, + "type": { + "type": "reflection", + "declaration": { + "id": 73, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 74, + "name": "key", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The key of the item to clear." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 541, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L541" + } + ], + "type": { + "type": "intrinsic", + "name": "string" + } + }, + { + "id": 75, + "name": "tags", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The tags of the items to clear. All items with any of the provided tags should be cleared." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 545, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L545" + } + ], + "type": { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } + } + }, + { + "id": 76, + "name": "options", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Options for clearing the item(s). The options should be matched against the stored options when the item was set.\nFor example, if the item was set with " + }, + { + "kind": "code", + "text": "`autoInvalidate: true`" + }, + { + "kind": "text", + "text": ", it will only be cleared if the " + }, + { + "kind": "code", + "text": "`autoInvalidate`" + }, + { + "kind": "text", + "text": " option is also set to " + }, + { + "kind": "code", + "text": "`true`" + }, + { + "kind": "text", + "text": ".\nIf not provided, all items matching the key or tags should be cleared regardless of their options." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 551, + "character": 4, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L551" + } + ], + "type": { + "type": "reflection", + "declaration": { + "id": 77, + "name": "__type", + "variant": "declaration", + "kind": 65536, + "flags": {}, + "children": [ + { + "id": 78, + "name": "autoInvalidate", + "variant": "declaration", + "kind": 1024, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Whether to clear item(s) that were set to automatically invalidate." + } + ] + }, + "sources": [ + { + "fileName": "caching/index.ts", + "line": 555, + "character": 6, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L555" + } + ], + "type": { + "type": "intrinsic", + "name": "boolean" + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 78 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 551, + "character": 14, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L551" + } + ] + } + } + } + ], + "groups": [ + { + "title": "Properties", + "children": [ + 74, + 75, + 76 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 537, + "character": 5, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L537" + } + ] + } + } + } + ], + "type": { + "type": "reference", + "target": { + "sourceFileName": "../../node_modules/typescript/lib/lib.es5.d.ts", + "qualifiedName": "Promise" + }, + "typeArguments": [ + { + "type": "intrinsic", + "name": "void" + } + ], + "name": "Promise", + "package": "typescript" + } + } + ] + } + ], + "groups": [ + { + "title": "Methods", + "children": [ + 53, + 59, + 70 + ] + } + ], + "sources": [ + { + "fileName": "caching/index.ts", + "line": 409, + "character": 17, + "url": "https://github.com/medusajs/medusa/blob/e6c43174f056a39daef3cab4e170981a2e4ccbf9/packages/core/types/src/caching/index.ts#L409" + } + ] + } + ], + "groups": [ + { + "title": "Interfaces", + "children": [ + 1, + 52 + ] + } + ], + "packageName": "@medusajs/types", + "symbolIdMap": { + "0": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "" + }, + "1": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService" + }, + "2": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.get" + }, + "3": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.get" + }, + "4": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "T" + }, + "5": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__0" + }, + "6": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "7": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.key" + }, + "8": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.tags" + }, + "9": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.providers" + }, + "10": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.set" + }, + "11": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.set" + }, + "12": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__0" + }, + "13": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "14": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.key" + }, + "15": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.data" + }, + "16": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.ttl" + }, + "17": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.tags" + }, + "18": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.options" + }, + "19": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "20": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.autoInvalidate" + }, + "21": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.providers" + }, + "22": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.clear" + }, + "23": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.clear" + }, + "24": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__0" + }, + "25": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "26": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.key" + }, + "27": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.tags" + }, + "28": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.options" + }, + "29": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "30": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.autoInvalidate" + }, + "31": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.providers" + }, + "32": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.computeKey" + }, + "33": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.computeKey" + }, + "34": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "input" + }, + "35": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.computeTags" + }, + "36": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingModuleService.computeTags" + }, + "37": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "input" + }, + "38": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "options" + }, + "52": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingProviderService" + }, + "53": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingProviderService.get" + }, + "54": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingProviderService.get" + }, + "55": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__0" + }, + "56": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "57": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.key" + }, + "58": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.tags" + }, + "59": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingProviderService.set" + }, + "60": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingProviderService.set" + }, + "61": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__0" + }, + "62": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "63": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.key" + }, + "64": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.data" + }, + "65": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.ttl" + }, + "66": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.tags" + }, + "67": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.options" + }, + "68": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "69": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.autoInvalidate" + }, + "70": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingProviderService.clear" + }, + "71": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "ICachingProviderService.clear" + }, + "72": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__0" + }, + "73": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "74": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.key" + }, + "75": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.tags" + }, + "76": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.options" + }, + "77": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type" + }, + "78": { + "sourceFileName": "../../../../packages/core/types/src/caching/index.ts", + "qualifiedName": "__type.autoInvalidate" + } + }, + "files": { + "entries": { + "1": "../../../../packages/core/types/src/caching/index.ts" + }, + "reflections": { + "1": 0 + } + } +} diff --git a/www/utils/packages/typedoc-generate-references/src/constants/custom-options.ts b/www/utils/packages/typedoc-generate-references/src/constants/custom-options.ts index bc26fa1cc1..29a83ad4e6 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/custom-options.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/custom-options.ts @@ -22,6 +22,11 @@ const customOptions: Record> = { tsConfigName: "types.json", name: "cache", }), + caching: getOptions({ + entryPointPath: "packages/core/types/src/caching/index.ts", + tsConfigName: "types.json", + name: "caching", + }), "core-flows": getOptions({ entryPointPath: "packages/core/core-flows/src/index.ts", tsConfigName: "core-flows.json", diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/cache.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/cache.ts index 45cffad3df..b5a30d7eea 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/cache.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/cache.ts @@ -5,7 +5,14 @@ const cacheOptions: FormattingOptionsType = { reflectionGroups: { Constructors: false, }, - reflectionDescription: `In this document, you’ll learn about the different methods in the Cache Module's service and how to use them.`, + reflectionDescription: `In this document, you’ll learn about the different methods in the Cache Module's service and how to use them. + +:::note[Deprecation Notice] + +The Cache Module is deprecated starting from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). [Use the Caching Module](#) instead. + +::: + `, frontmatterData: { slug: "/references/cache-service", tags: ["cache", "server", "how to"], diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/caching.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/caching.ts new file mode 100644 index 0000000000..9ad349a2ed --- /dev/null +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/caching.ts @@ -0,0 +1,215 @@ +import { FormattingOptionsType } from "types" +import baseSectionsOptions from "../base-section-options.js" + +const cachingOptions: FormattingOptionsType = { + "^caching/.*ICachingProviderService": { + reflectionGroups: { + Constructors: false, + }, + reflectionDescription: `In this guide, you’ll learn how to create a Caching Module Provider and the methods you must implement in its main service. + +:::note + +The Caching Module and its providers are available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +:::`, + frontmatterData: { + slug: "/references/caching-module-provider", + tags: ["caching", "server", "how to"], + sidebar_label: "Create Caching Provider", + keywords: ["caching", "provider", "integration"], + }, + reflectionTitle: { + fullReplacement: "How to Create a Caching Module Provider", + }, + shouldIncrementAfterStartSections: true, + expandMembers: true, + expandProperties: true, + sortMembers: true, + sections: { + ...baseSectionsOptions, + member_declaration_title: false, + reflection_typeParameters: false, + }, + startSections: [ + // TODO add link to memcached guide when available + `## Implementation Example + +As you implement your Caching Module Provider, it can be useful to refer to an existing provider and how it's implemeted. + +If you need to refer to an existing implementation as an example, check the [Redis Caching Module Provider in the Medusa repository](https://github.com/medusajs/medusa/tree/develop/packages/modules/providers/caching-redis).`, + `## 1. Create Module Provider Directory + +Start by creating a new directory for your module provider. + +If you're creating the module provider in a Medusa application, create it under the \`src/modules\` directory. For example, \`src/modules/my-caching\`. + +If you're creating the module provider in a plugin, create it under the \`src/providers\` directory. For example, \`src/providers/my-caching\`. + + + +The rest of this guide always uses the \`src/modules/my-caching\` directory as an example. + +`, + `## 2. Create the Caching Module Provider Service + +Create the file \`src/modules/my-caching/service.ts\` that holds the module provider's main service. It must implement the \`ICachingProviderService\` interface imported from \`@medusajs/framework/types\`: + +\`\`\`ts title="src/modules/my-caching/service.ts" +import { ICachingProviderService } from "@medusajs/framework/types" + +class MyCachingProviderService implements ICachingProviderService { + // TODO implement methods +} + +export default MyCachingProviderService +\`\`\``, + ], + endSections: [ + `## 3. Create Module Definition File + +Create the file \`src/modules/my-caching/index.ts\` with the following content: + +\`\`\`ts title="src/modules/my-caching/index.ts" +import { ModuleProvider, Modules } from "@medusajs/framework/utils" +import MyCachingProviderService from "./service" + +export default ModuleProvider(Modules.CACHING, { + services: [MyCachingProviderService], +}) +\`\`\` + +This exports the module provider's definition, indicating that the \`MyCachingProviderService\` is the module provider's service.`, + `## 4. Use Module Provider + +To use your Caching Module Provider, add it to the \`providers\` array of the Caching Module in \`medusa-config.ts\`: + +\`\`\`ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/caching", + options: { + providers: [ + { + // if module provider is in a plugin, use \`plugin-name/providers/my-caching\` + resolve: "./src/modules/my-caching", + id: "my-caching", + // set this if you want this provider to be used by default + // and you have other Caching Module Providers registered. + is_default: true, + options: { + url: "http://example.com", + // provider options... + } + }, + ] + } + } + ] +}) +\`\`\` +`, + `## 5. Test it Out + +To test out your Caching Module Provider, create a simple API route that retrieves cached data with Query: + +\`\`\`ts title="src/api/test-caching/route.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const query = req.scope.resolve("query") + + const { data } = await query.graph({ + entity: "product", + fields: ["id", "title"], + }, { + cache: { + enable: true, + providers: ["my-caching"], // use your provider id here + } + }) + + res.status(200).json({ data }) +} +\`\`\` + +Then, start your Medusa server with the following command: + +\`\`\`bash npm2yarn +npm run dev +\`\`\` + +Next, send a \`GET\` request to \`http://localhost:9000/test-caching\`: + +\`\`\`bash +curl http://localhost:9000/test-caching +\`\`\` + +You will receive a response with the list of products. The first time you make this request, the products will be fetched from the database and cached in memory. Subsequent requests will retrieve the products from the cache, which improves performance. +`, + `## Useful Guides + +- [How to Use Caching Module](/references/caching-service) +`, + ], + }, + "^caching/.*ICachingModuleService": { + reflectionGroups: { + Constructors: false, + }, + reflectionDescription: `In this guide, you’ll learn about the different methods in the Caching Module's service and how to use them. + +:::note + +The Caching Module and its providers are available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +::: + +:::tip + +You should use the Caching Module's service when you're caching computed data or data from external APIs. To cache database query results, enable caching in [Query](!docs!/learn/fundamentals/module-links/query#cache) or [Index Module](!docs!/learn/fundamentals/module-links/index-module#cache) instead. + +::: +`, + frontmatterData: { + slug: "/references/caching-service", + tags: ["caching", "server", "how to"], + sidebar_label: "Use Caching Module", + }, + reflectionTitle: { + fullReplacement: "How to Use Caching Module", + }, + expandMembers: true, + sortMembers: true, + startSections: [ + `## Resolve Caching Module's Service + +In your workflow's step, you can resolve the Caching Module's service from the Medusa container: + +\`\`\`ts +import { Modules } from "@medusajs/framework/utils" +import { createStep } from "@medusajs/framework/workflows-sdk" + +const step1 = createStep( + "step-1", + async ({}, { container }) => { + const cachingModuleService = container.resolve( + Modules.CACHING + ) + + // TODO use cachingModuleService + } +) +\`\`\` + +You can then use the Caching Module's service's methods in the step. The rest of this guide details these methods. + +--- +`, + ], + }, +} + +export default cachingOptions diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/index.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/index.ts index 174bdb151d..3343d4da9c 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/index.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/index.ts @@ -1,5 +1,6 @@ import { FormattingOptionsType } from "types" import authProviderOptions from "./auth-provider.js" +import cachingOptions from "./caching.js" import fileOptions from "./file.js" import fulfillmentProviderOptions from "./fulfillment-provider.js" import helperStepsOptions from "./helper-steps.js" @@ -25,6 +26,7 @@ const mergerCustomOptions: FormattingOptionsType = { ...analyticsProviderOptions, ...authProviderOptions, ...cacheOptions, + ...cachingOptions, ...coreFlowsOptions, ...dmlOptions, ...eventOptions, diff --git a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/locking.ts b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/locking.ts index 395115688f..f8b72ec510 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/locking.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/merger-custom-options/locking.ts @@ -6,7 +6,7 @@ const lockingOptions: FormattingOptionsType = { reflectionGroups: { Constructors: false, }, - reflectionDescription: `In this document, you’ll learn how to create a Locking Module Provider and the methods you must implement in its main service.`, + reflectionDescription: `In this guide, you’ll learn how to create a Locking Module Provider and the methods you must implement in its main service.`, frontmatterData: { slug: "/references/locking-module-provider", tags: ["locking", "server", "how to"], @@ -86,7 +86,7 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/medusa/payment", + resolve: "@medusajs/medusa/locking", options: { providers: [ { @@ -130,7 +130,7 @@ The Locking Module will now use your provider to handle all locking operations. reflectionGroups: { Constructors: false, }, - reflectionDescription: `In this document, you’ll learn about the different methods in the Locking Module's service and how to use them.`, + reflectionDescription: `In this guide, you’ll learn about the different methods in the Locking Module's service and how to use them.`, frontmatterData: { slug: "/references/locking-service", tags: ["locking", "server", "how to"], diff --git a/www/utils/packages/typedoc-generate-references/src/constants/references.ts b/www/utils/packages/typedoc-generate-references/src/constants/references.ts index e2ec512292..e7cbc1147e 100644 --- a/www/utils/packages/typedoc-generate-references/src/constants/references.ts +++ b/www/utils/packages/typedoc-generate-references/src/constants/references.ts @@ -38,6 +38,7 @@ const allReferences = [ "workflows", "locking", "cache", + "caching", "event", "file-service", "notification-service",