docs: add documentation for Locking Module (#11824)
* add locking docs * fix main navbar * added implementation example links * generate refs * update architecture * fix vale error
This commit is contained in:
1 parent
5cf0bf4d93
commit
28b0d08591
94 files changed
+15932
-10857
No files matched your search
+50
-10
@@ -1,22 +1,68 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Cache Modules`,
|
||||
title: `Cache Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn what a Cache Module is and how to use it in your Medusa application.
|
||||
|
||||
## What is a Cache Module?
|
||||
|
||||
A Cache Module is used to cache the results of computations such as price selection or various tax calculations.
|
||||
|
||||
The underlying database, third-party service, or caching logic is flexible since it's implemented in a module. You can choose from Medusa’s cache modules or create your own to support something more suitable for your architecture.
|
||||
|
||||
### Default Cache Module
|
||||
|
||||
By default, Medusa uses the [In-Memory Cache Module](./in-memory/page.mdx). This module uses a plain JavaScript Map object to store the cache data. While this is suitable for development, it's recommended to use other Cache Modules, such as the [Redis Cache Module](./redis/page.mdx), for production. You can also [Create a Cache Module](./create/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Cache Module?
|
||||
|
||||
You can use the registered Cache Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
In a step of your workflow, you can resolve the Cache Module's service and use its methods to cache data, retrieve cached data, or clear the cache.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const cacheModuleService = container.resolve(
|
||||
Modules.CACHE
|
||||
)
|
||||
|
||||
await cacheModuleService.set("key", "value")
|
||||
}
|
||||
)
|
||||
|
||||
export const workflow = createWorkflow(
|
||||
"workflow-1",
|
||||
() => {
|
||||
step1()
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, you create a workflow that has a step. In the step, you resolve the service of the Cache Module from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
Then, you use the `set` method of the Cache Module to cache the value `"value"` with the key `"key"`.
|
||||
|
||||
---
|
||||
|
||||
## List of Cache Modules
|
||||
|
||||
By default, Medusa uses the In-Memory Cache Module. This module uses a plain JavaScript Map object to store the cache data.
|
||||
|
||||
This is useful for development. However, for production, it's highly recommended to use other Cache Modules, such as the Redis Cache Module.
|
||||
Medusa provides the following Cache Modules. You can use one of them, or [Create a Cache Module](./create/page.mdx).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -38,9 +84,3 @@ This is useful for development. However, for production, it's highly recommended
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Create a Cache Module
|
||||
|
||||
To create a cache module, refer to [this guide](./create/page.mdx).
|
||||
@@ -1,10 +1,10 @@
|
||||
export const metadata = {
|
||||
title: `Local Event Bus Module`,
|
||||
title: `Local Event Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Local Event Bus Module uses Node EventEmitter to implement Medusa's pub/sub events system. The Node EventEmitter is limited to a single process environment.
|
||||
The Local Event Module uses Node EventEmitter to implement Medusa's pub/sub events system. The Node EventEmitter is limited to a single process environment.
|
||||
|
||||
This module is useful for development and testing, but it’s not recommended to be used in production.
|
||||
|
||||
@@ -12,11 +12,11 @@ For production, it’s recommended to use modules like [Redis Event Bus Module](
|
||||
|
||||
---
|
||||
|
||||
## Register the Local Event Bus Module
|
||||
## Register the Local Event Module
|
||||
|
||||
<Note>
|
||||
|
||||
The Local Event Bus Module is registered by default in your application.
|
||||
The Local Event Module is registered by default in your application.
|
||||
|
||||
</Note>
|
||||
|
||||
|
||||
@@ -1,22 +1,80 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Event Modules`,
|
||||
title: `Event Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn what an Event Module is and how to use it in your Medusa application.
|
||||
|
||||
## What is an Event Module?
|
||||
|
||||
An Event Module implements the underlying publish/subscribe system that handles queueing events, emitting them, and executing their subscribers.
|
||||
|
||||
This makes the event architecture customizable, as you can either choose one of Medusa’s event modules or create your own.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about Medusa's event systems in the [Events and Subscribers documentation](!docs!/learn/fundamentals/events-and-subscribers).
|
||||
|
||||
</Note>
|
||||
|
||||
### Default Event Module
|
||||
|
||||
By default, Medusa uses the [Local Event Module](./local/page.mdx). This module uses Node’s EventEmitter to implement the publish/subscribe system. While this is suitable for development, it's recommended to use other Event Modules, such as the [Redis Event Module](./redis/page.mdx), for production. You can also [Create an Event Module](./create/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Event Module?
|
||||
|
||||
You can use the registered Event Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
Medusa provides the helper step [emitEventStep](/references/helper-steps/emitEventStep) that you can use in your workflow. You can also resolve the Event Module's service in a step of your workflow and use its methods to emit events.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const eventModuleService = container.resolve(
|
||||
Modules.EVENT
|
||||
)
|
||||
|
||||
await eventModuleService.emit({
|
||||
name: "custom.event",
|
||||
data: {
|
||||
id: "123",
|
||||
// other data payload
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export const workflow = createWorkflow(
|
||||
"workflow-1",
|
||||
() => {
|
||||
step1()
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, you create a workflow that has a step. In the step, you resolve the service of the Event Module from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
Then, you use the `emit` method of the Event Module to emit an event with the name `"custom.event"` and the data payload `{ id: "123" }`.
|
||||
|
||||
---
|
||||
|
||||
## List of Event Modules
|
||||
|
||||
By default, Medusa uses the Local Event Module. This module uses Node’s EventEmitter to implement the publish/subscribe system.
|
||||
|
||||
This is useful for development. However, for production, it’s highly recommended to use other Event Modules, Redis Event Module.
|
||||
Medusa provides the following Event Modules. You can use one of them, or [Create an Event Module](./create/page.mdx).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -38,9 +96,3 @@ This is useful for development. However, for production, it’s highly recommend
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Create a Event Module
|
||||
|
||||
To create an event module, refer to [this guide](./create/page.mdx).
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Table, Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Redis Event Bus Module`,
|
||||
title: `Redis Event Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Redis Event Bus Module uses Redis to implement Medusa's pub/sub events system.
|
||||
The Redis Event Module uses Redis to implement Medusa's pub/sub events system.
|
||||
|
||||
It's powered by BullMQ and `io-redis`. BullMQ is responsible for the message queue and worker, and `io-redis` is the underlying Redis client that BullMQ connects to for events storage.
|
||||
|
||||
@@ -14,7 +14,7 @@ In production, it's recommended to use this module.
|
||||
|
||||
---
|
||||
|
||||
## Register the Redis Events Bus Module
|
||||
## Register the Redis Event Module
|
||||
|
||||
<Prerequisites items={[
|
||||
{
|
||||
@@ -55,7 +55,7 @@ Make sure to add the following environment variables:
|
||||
EVENTS_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
### Redis Event Bus Module Options
|
||||
### Redis Event Module Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
|
||||
@@ -1,18 +1,78 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `File Module Providers`,
|
||||
title: `File Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## What is a File Module Provider?
|
||||
In this document, you'll learn about the File Module and its providers.
|
||||
|
||||
The File Module exposes the functionalities to upload assets, such as product images, to the Medusa application. A file module provider implements the underlying logic of handling uploads and downloads of assets, such as integrating third-party services. The File Module must have one file module provider configured.
|
||||
## What is the File Module?
|
||||
|
||||
By default, Medusa uses the Local File Module. This module uploads files to the `uploads` directory of your Medusa application.
|
||||
The File Module exposes the functionalities to upload assets, such as product images, to the Medusa application. Medusa uses the File Module in its core commerce features for all file operations, and you can use it in your custom features as well.
|
||||
|
||||
This is useful for development. However, for production, it’s highly recommended to use other File Modules, such as the S3 Module.
|
||||
---
|
||||
|
||||
## How to Use the File Module?
|
||||
|
||||
You can use the File Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
In a step of your workflow, you can resolve the File Module's service and use its methods to upload files, retrieve files, or delete files.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
StepResponse,
|
||||
WorkflowResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const fileModuleService = container.resolve(
|
||||
Modules.FILE
|
||||
)
|
||||
|
||||
const { url } = await fileModuleService.retrieveFile("image.png")
|
||||
|
||||
return new StepResponse(url)
|
||||
}
|
||||
)
|
||||
|
||||
export const workflow = createWorkflow(
|
||||
"workflow-1",
|
||||
() => {
|
||||
const url = step1()
|
||||
|
||||
return new WorkflowResponse(url)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, you create a workflow that has a step. In the step, you resolve the service of the File Module from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
Then, you use the `retrieveFile` method of the File Module to retrieve the URL of the file with the name `"image.png"`. The URL is then returned as a response from the step and the workflow.
|
||||
|
||||
---
|
||||
|
||||
### 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.
|
||||
|
||||
<Note>
|
||||
|
||||
Only one File Module Provider can be registered at a time. If you register multiple providers, the File Module will throw an error.
|
||||
|
||||
</Note>
|
||||
|
||||
By default, Medusa uses the [Local File Module](./local/page.mdx). This module uploads files to the `uploads` directory of your Medusa application.
|
||||
|
||||
This is useful for development. However, for production, it’s highly recommended to use other File Module Providers, such as the S3 File Module Provider. You can also [Create a File Provider](/references/file-provider-module).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -34,9 +94,3 @@ This is useful for development. However, for production, it’s highly recommend
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Create a File Module Provider
|
||||
|
||||
To create a file module provider, refer to [this guide](/references/file-provider-module).
|
||||
@@ -0,0 +1,160 @@
|
||||
import { Table, CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Locking Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the Locking Module and its providers.
|
||||
|
||||
## What is the Locking Module?
|
||||
|
||||
The Locking Module manages access to shared resources by multiple processes or threads. It prevents conflicts between processes that are trying to access the same resource at the same time, and ensures data consistency.
|
||||
|
||||
Medusa uses the Locking Module to control concurrency, avoid race conditions, and protect parts of code that should not be executed by more than one process at a time. This is especially essential in distributed or multi-threaded environments.
|
||||
|
||||
For example, Medusa uses the Locking Module in inventory management to ensure that only one transaction can update the stock levels at a time. By using the Locking Module in this scenario, Medusa prevents overselling an inventory item and keeps its quantity amounts accurate, even during high traffic periods or when receiving concurrent requests.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Locking Module?
|
||||
|
||||
You can use the Locking Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
In a step of your workflow, you can resolve the Locking Module's service and use its methods to execute an asynchronous job, acquire a lock, or release locks.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const lockingModuleService = container.resolve(
|
||||
Modules.LOCKING
|
||||
)
|
||||
const productModuleService = container.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
await lockingModuleService.execute("prod_123", async () => {
|
||||
await productModuleService.deleteProduct("prod_123")
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export const workflow = createWorkflow(
|
||||
"workflow-1",
|
||||
() => {
|
||||
step1()
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, you create a workflow that has a step. In the step, you resolve the services of the Locking and Product modules from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
Then, you use the `execute` method of the Locking Module to acquire a lock for the product with the ID `prod_123` and execute an asynchronous function, which deletes the product.
|
||||
|
||||
---
|
||||
|
||||
## When to Use the Locking Module?
|
||||
|
||||
You should use the Locking Module when you need to ensure that only one process can access a shared resource at a time. As mentioned in the inventory example previously, you don't want customers to order quantities of inventory that are not available, or to update the stock levels of an item concurrently.
|
||||
|
||||
In those scenarios, you can use the Locking Module to acquire a lock for a resource and execute a critical section of code that should not be accessed by multiple processes simultaneously.
|
||||
|
||||
---
|
||||
|
||||
## What is a Locking Module Provider?
|
||||
|
||||
A Locking Module Provider implements the underlying logic of the Locking Module. It manages the locking mechanisms and ensures that only one process can access a shared resource at a time.
|
||||
|
||||
Medusa provides [multiple Locking Module Providers](#list-of-locking-module-providers) that are suitable for development and production. You can also create a [custom Locking Module Provider](/references/locking-module-provider) to implement custom locking mechanisms or integrate with third-party services.
|
||||
|
||||
### Default Locking Module Provider
|
||||
|
||||
By default, Medusa uses the In-Memory Locking Module Provider. This provider uses a plain JavaScript map to store the locks. While this is useful for development, it is not recommended for production environments as it is only intended for use in a single-instance environment.
|
||||
|
||||
To add more providers, you can register them in the `medusa-config.ts` file. For example:
|
||||
|
||||
```ts
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking",
|
||||
options: {
|
||||
providers: [
|
||||
// add providers here...
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
When you register other providers in `medusa-config.ts`, Medusa will set the default provider based on the following scenarios:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Scenario</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default Provider</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
One provider is registered.
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The registered provider.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
Multiple providers are registered and none of them has an `is_default` flag.
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
In-Memory Locking Module Provider.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
Multiple providers and one of them has an `is_default` flag.
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
The provider with the `is_default` flag.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
## List of Locking Module Providers
|
||||
|
||||
Medusa provides the following Locking Module Providers. You can use one of them, or [Create a Locking Module Provider](./references/locking-module-provider).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/architectural-modules/locking/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "Recommended"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "PostgreSQL",
|
||||
href: "/architectural-modules/locking/postgres",
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,105 @@
|
||||
export const metadata = {
|
||||
title: `PostgreSQL Locking Module Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The PostgreSQL Locking Module Provider uses PostgreSQL's advisory locks to control and manage locks across multiple instances of Medusa. Advisory locks are lightweight locks that do not interfere with other database transactions. By using PostgreSQL's advisory locks, Medusa can create distributed locks directly through the database.
|
||||
|
||||
The provider uses the existing PostgreSQL database in your application to manage locks, so you don't need to set up a separate database or service to manage locks.
|
||||
|
||||
<Note>
|
||||
|
||||
While this provider is suitable for production environments, it's recommended to use the [Redis Locking Module Provider](../redis/page.mdx) if possible.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Register the PostgreSQL Locking Module Provider
|
||||
|
||||
To register the PostgreSQL Locking Module Provider, add it to the list of providers of the Locking Module in `medusa-config.ts`:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking-postgres",
|
||||
id: "postgres-lock",
|
||||
// set this if you want this provider to be used by default
|
||||
// and you have other Locking Module Providers registered.
|
||||
is_default: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### Run Migrations
|
||||
|
||||
The PostgreSQL Locking Module Provider requires a new `locking` table in the database to store the locks. So, you must run the migrations after registering the provider:
|
||||
|
||||
```bash
|
||||
npx medusa db:migrate
|
||||
```
|
||||
|
||||
This will run the migration in the PostgreSQL Locking Module Provider and create the necessary table in the database.
|
||||
|
||||
---
|
||||
|
||||
## Use Provider with Locking Module
|
||||
|
||||
The PostgreSQL Locking Module Provider will be the default provider if you don't register any other providers, or if you set the `is_default` flag to `true`:
|
||||
|
||||
export const defaultHighlights = [
|
||||
["11", "is_default"]
|
||||
]
|
||||
|
||||
```ts title="medusa-config.ts" highlights={defaultHighlights}
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking-postgres",
|
||||
id: "postgres-lock",
|
||||
is_default: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
If you use the Locking Module in your customizations, the PostgreSQL Locking Module Provider will be used by default in this case. You can also explicitly use this provider by passing its identifier `lp_locking-postgres` to the Locking Module's service methods.
|
||||
|
||||
For example, when using the `acquire` method in a [workflow step](!docs!/learn/fundamentals/workflows):
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const lockingModuleService = container.resolve(
|
||||
Modules.LOCKING
|
||||
)
|
||||
|
||||
await lockingModuleService.acquire("prod_123", {
|
||||
provider: "lp_locking-postgres",
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,289 @@
|
||||
import { Table, Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Redis Locking Module Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Redis Locking Module Provider uses Redis to manage locks across multiple instances of Medusa. Redis ensures that locks are globally available, which is ideal for distributed environments.
|
||||
|
||||
This provider is recommended for production environments where Medusa is running in a multi-instance setup.
|
||||
|
||||
---
|
||||
|
||||
## Register the Redis Locking Module Provider
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "A redis server set up locally or a database in your deployed application.",
|
||||
link: "https://redis.io/download",
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
To register the Redis Locking Module Provider, add it to the list of providers of the Locking Module in `medusa-config.ts`:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking-redis",
|
||||
id: "redis-lock",
|
||||
// set this if you want this provider to be used by default
|
||||
// and you have other Locking Module Providers registered.
|
||||
is_default: true,
|
||||
options: {
|
||||
redisUrl: process.env.LOCKING_REDIS_URL,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variable:
|
||||
|
||||
```bash
|
||||
LOCKING_REDIS_URL=<YOUR_LOCKING_REDIS_URL>
|
||||
```
|
||||
|
||||
Where `<YOUR_LOCKING_REDIS_URL>` is the URL of your Redis server, either locally or in the deployed environment.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The default Redis URL in a local environment is `redis://localhost:6379`.
|
||||
|
||||
</Note>
|
||||
|
||||
### Redis Locking Module Provider Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`redisUrl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Redis connection URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`redisOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of Redis options. Refer to the [Redis API Reference](https://redis.github.io/ioredis/index.html#RedisOptions) for details on accepted properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`namespace`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string used to prefix all locked keys with `{namespace}`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`medusa_lock:`. So, all locked keys are prefixed with `medusa_lock:`.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`waitLockingTimeout`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the default timeout (in seconds) to wait while acquiring a lock. This timeout is used when no timeout is specified when executing an asynchronous job or acquiring a lock.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`5`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`defaultRetryInterval`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the time (in milliseconds) to wait before retrying to acquire a lock.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`5`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`maximumRetryInterval`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the maximum time (in milliseconds) to wait before retrying to acquire a lock.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`200`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
## Test out the Module
|
||||
|
||||
To test out the Redis Locking Module Provider, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message logged in the terminal:
|
||||
|
||||
```bash
|
||||
info: Connection to Redis in "locking-redis" provider established
|
||||
```
|
||||
|
||||
This message indicates that the Redis Locking Module Provider has successfully connected to the Redis server.
|
||||
|
||||
If you set the `is_default` flag to `true` in the provider options or you only registered the Redis Locking Module Provider, the Locking Module will use it by default for all locking operations.
|
||||
|
||||
---
|
||||
|
||||
## Use Provider with Locking Module
|
||||
|
||||
The Redis Locking Module Provider will be the default provider if you don't register any other providers, or if you set the `is_default` flag to `true`:
|
||||
|
||||
export const defaultHighlights = [
|
||||
["11", "is_default"]
|
||||
]
|
||||
|
||||
```ts title="medusa-config.ts" highlights={defaultHighlights}
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/locking-redis",
|
||||
id: "redis-lock",
|
||||
is_default: true,
|
||||
options: {
|
||||
// ...
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
If you use the Locking Module in your customizations, the Redis Locking Module Provider will be used by default in this case. You can also explicitly use this provider by passing its identifier `lp_locking-redis` to the Locking Module's service methods.
|
||||
|
||||
For example, when using the `acquire` method in a [workflow step](!docs!/learn/fundamentals/workflows):
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const lockingModuleService = container.resolve(
|
||||
Modules.LOCKING
|
||||
)
|
||||
|
||||
await lockingModuleService.acquire("prod_123", {
|
||||
provider: "lp_locking-redis",
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -1,22 +1,73 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Notification Module Providers`,
|
||||
title: `Notification Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the Notification Module and its providers.
|
||||
|
||||
## What is the Notification Module?
|
||||
|
||||
The Notification Module exposes the functionalities to send a notification to a customer or user. For example, sending an order confirmation email. Medusa uses the Notification Module in its core commerce features for notification operations, and you an use it in your custom features as well.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Notification Module?
|
||||
|
||||
You can use the Notification Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
In a step of your workflow, you can resolve the Notification Module's service and use its methods to send notifications.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const notificationModuleService = container.resolve(
|
||||
Modules.NOTIFICATION
|
||||
)
|
||||
|
||||
await notificationModuleService.createNotifications({
|
||||
to: "customer@gmail.com",
|
||||
channel: "email",
|
||||
template: "product-created",
|
||||
data,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export const workflow = createWorkflow(
|
||||
"workflow-1",
|
||||
() => {
|
||||
step1()
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, you create a workflow that has a step. In the step, you resolve the service of the Notification Module from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
Then, you use the `createNotifications` method of the Notification Module to send an email notification.
|
||||
|
||||
Find a full example of sending a notification in the [Send Notification guide](./send-notification/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## What is a Notification Module Provider?
|
||||
|
||||
The Notification Module exposes the functionalities to send a notification to a customer or user. For example, sending an order confirmation email.
|
||||
A Notification Module Provider implements the underlying logic of sending notification. It either integrates a third-party service or uses custom logic to send the notification.
|
||||
|
||||
You can resolve the Notification Module and send notifications in API routes, subscribers, or other resources.
|
||||
By default, Medusa uses the [Local Notification Module](./local/page.mdx) which only simulates sending the notification by logging a message in the terminal.
|
||||
|
||||
A notification module provider implements the underlying logic of sending notification. It either integrates a third-party service or uses custom logic to send the notification.
|
||||
|
||||
By default, Medusa uses the Local Notification Module which only simulates sending the notification by logging a message in the terminal.
|
||||
|
||||
Medusa provides other Notification Modules that actually send notifications, such as the SendGrid Notification Module Provider.
|
||||
Medusa provides other Notification Modules that actually send notifications, such as the [SendGrid Notification Module Provider](./send-notification/page.mdx). You can also [Create a Notification Module Provider](/references/notification-provider-module).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -43,7 +94,9 @@ Medusa provides other Notification Modules that actually send notifications, suc
|
||||
|
||||
## Notification Module Provider Channels
|
||||
|
||||
When you send a notification, you specify the channel to send it through, such as `email` or `sms`. Each provider defined in the Notification Module's `providers` option has a `channels` option specifying which channels it can be used in. Only one provider can be setup for each channel.
|
||||
When you send a notification, you specify the channel to send it through, such as `email` or `sms`.
|
||||
|
||||
You register providers of the Notification Module in `medusa-config.ts`. For each provider, you pass a `channels` option specifying which channels the provider can be used in. Only one provider can be setup for each channel.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -76,9 +129,3 @@ module.exports = {
|
||||
```
|
||||
|
||||
The `channels` option is an array of strings indicating the channels this provider is used for.
|
||||
|
||||
---
|
||||
|
||||
## Create a Notification Module Provider
|
||||
|
||||
To create a notification module provider, refer to [this guide](/references/notification-provider-module).
|
||||
@@ -6,17 +6,17 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
Medusa's architectural functionalities, such as emitting and subscribing to events or caching data, are all implemented in architectural modules. An architectural module is a package that can be installed and used in any Medusa application. These modules allow you to choose and integrate custom services for architectural purposes.
|
||||
Medusa's architectural functionalities, such as emitting and subscribing to events or caching data, are all implemented in Architectural Modules. An Architectural Module is a package that can be installed and used in any Medusa application. These modules allow you to choose and integrate custom services for architectural purposes.
|
||||
|
||||
For example, you can use our [Redis Event Module](./event/redis/page.mdx) to handle event functionalities, or create a custom module that implements these functionalities with Memcached. Learn more about Medusa's architecture in [this documentation](!docs!/learn/introduction/architecture).
|
||||
For example, you can use our [Redis Event Module](./event/redis/page.mdx) to handle event functionalities, or create a custom module that implements these functionalities with Memcached. Learn more in [the Architecture documentation](!docs!/learn/introduction/architecture).
|
||||
|
||||
This section of the documentation showcases official architectural modules and how to configure them in your application.
|
||||
This section of the documentation showcases Medusa's Architectural Modules, how they work, and how to use them in your Medusa application.
|
||||
|
||||
## Cache Modules
|
||||
## Cache 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 following modules are provided by Medusa. You can also create your own cache module as explained in [this guide](./cache/create/page.mdx).
|
||||
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).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -41,11 +41,11 @@ The following modules are provided by Medusa. You can also create your own cache
|
||||
|
||||
---
|
||||
|
||||
## Event Modules
|
||||
## Event Module
|
||||
|
||||
An Event Module implements the underlying publish/subscribe system that handles queueing events, emitting them, and executing their subscribers. Learn more in [this documentation](./event/page.mdx).
|
||||
|
||||
The following modules are provided by Medusa. You can also create your own event module as explained in [this guide](./event/create/page.mdx).
|
||||
The following Event modules are provided by Medusa. You can also create your own event module as explained in [this guide](./event/create/page.mdx).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -70,11 +70,11 @@ The following modules are provided by Medusa. You can also create your own event
|
||||
|
||||
---
|
||||
|
||||
## File Module Providers
|
||||
## File Module
|
||||
|
||||
A file module provider implements the logic of handling uploads and downloads of assets, such as integrating third-party services. Learn more in [this documentation](./file/page.mdx).
|
||||
The File Module handles file upload and storage of assets, such as product images. Refer to the [File Module documentation](./file/page.mdx) to learn more about it.
|
||||
|
||||
The following modules are provided by Medusa. You can also create your own file module as explained in [this guide](/references/file-provider-module).
|
||||
The File Module has module providers that implement the underlying logic of handling uploads and downloads of assets, such as integrating third-party services. The following File Module Providers are provided by Medusa. You can also create a custom provider as explained in the [Create File Module Provider guide](/references/file-provider-module).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -99,11 +99,36 @@ The following modules are provided by Medusa. You can also create your own file
|
||||
|
||||
---
|
||||
|
||||
## Notification Module Providers
|
||||
## Locking Module
|
||||
|
||||
A notification module provider implements the logic of sending notifications, typically through integrating a third-party service. Learn more in [this documentation](./notification/page.mdx).
|
||||
The Locking Module manages access to shared resources by multiple processes or threads. It prevents conflicts between processes and ensures data consistency. Refer to the [Locking Module documentation](./locking/page.mdx) to learn more about it.
|
||||
|
||||
The following modules are provided by Medusa. You can also create your own file module as explained in [this guide](/references/notification-provider-module).
|
||||
The Locking Module uses module providers that implement the underlying logic of the locking mechanism. The following Locking Module Providers are provided by Medusa. You can also create a custom provider as explained in the [Create Locking Module Provider guide](/references/locking-provider-module).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/architectural-modules/locking/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "Recommended"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "PostgreSQL",
|
||||
href: "/architectural-modules/locking/postgres",
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Notification Module
|
||||
|
||||
The Notification Module handles sending notifications to users or customers, such as reset password instructions or newsletters. Refer to the [Notifcation Module documentation](./notification/page.mdx) to learn more about it.
|
||||
|
||||
The Notification Module has module providers that implement the underlying logic of sending notifications, typically through integrating a third-party service. The following modules are provided by Medusa. You can also create a custom provider as explained in the [Create Notification Module Provider guide](/references/notification-provider-module).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
@@ -151,11 +176,11 @@ The following modules are provided by Medusa. You can also create your own file
|
||||
|
||||
---
|
||||
|
||||
## Workflow Engine Modules
|
||||
## Workflow Engine Module
|
||||
|
||||
Workflow engine modules handle tracking and recording the transactions and statuses of workflows and their steps. Learn more in [this documentation](./workflow-engine/page.mdx).
|
||||
A Workflow Engine Module handles tracking and recording the transactions and statuses of workflows and their steps. Learn more about it in the [Worklow Engine Module documentation](./workflow-engine/page.mdx).
|
||||
|
||||
The following modules are provided by Medusa.
|
||||
The following Workflow Engine modules are provided by Medusa.
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
|
||||
@@ -1,14 +1,74 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Workflow Engine Modules`,
|
||||
title: `Workflow Engine Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
Workflow engine modules handle tracking and recording the transactions and statuses of workflows and their steps.
|
||||
In this document, you'll learn what a Workflow Engine Module is and how to use it in your Medusa application.
|
||||
|
||||
Medusa uses the In-Memory Workflow Engine Module by default. For production purposes, it's recommended to use the Redis Engine Module instead.
|
||||
## What is a Workflow Engine Module?
|
||||
|
||||
A Workflow Engine Module handles tracking and recording the transactions and statuses of workflows and their steps. It can use custom mechanism or integrate a third-party service.
|
||||
|
||||
### Default Workflow Engine Module
|
||||
|
||||
Medusa uses the [In-Memory Workflow Engine Module](./in-memory/page.mdx) by default. For production purposes, it's recommended to use the [Redis Workflow Engine Module](./redis/page.mdx) instead.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Workflow Engine Module?
|
||||
|
||||
You can use the registered Workflow Engine Module as part of the [workflows](!docs!/learn/fundamentals/workflows) you build for your custom features. A workflow is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
In a step of your workflow, you can resolve the Workflow Engine Module's service and use its methods to track and record the transactions and statuses of workflows and their steps.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createStep,
|
||||
createWorkflow,
|
||||
StepResponse,
|
||||
WorkflowResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
const step1 = createStep(
|
||||
"step-1",
|
||||
async ({}, { container }) => {
|
||||
const workflowEngineService = container.resolve(
|
||||
Modules.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
const [workflowExecution] = await workflowEngineService.listWorkflowExecutions({
|
||||
transaction_id: transaction_id,
|
||||
})
|
||||
|
||||
return new StepResponse(workflowExecution)
|
||||
}
|
||||
)
|
||||
|
||||
export const workflow = createWorkflow(
|
||||
"workflow-1",
|
||||
() => {
|
||||
const workflowExecution = step1()
|
||||
|
||||
return new WorkflowResponse(workflowExecution)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, you create a workflow that has a step. In the step, you resolve the service of the Workflow Engine Module from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
|
||||
Then, you use the `listWorkflowExecutions` method of the Workflow Engine Module to list the workflow executions with the transaction ID `transaction_id`. The workflow execution is then returned as a response from the step and the workflow.
|
||||
|
||||
---
|
||||
|
||||
## List of Workflow Engine Modules
|
||||
|
||||
Medusa provides the following Workflow Engine Modules.
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChildDocs } from "docs-ui"
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Commerce Modules`,
|
||||
@@ -14,7 +14,82 @@ A commerce module also defines data models, representing tables in the database.
|
||||
|
||||
## Commerce Modules List
|
||||
|
||||
<ChildDocs onlyTopLevel={true} defaultItemsPerRow={2} hideItems={["Overview"]} />
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "API Key Module",
|
||||
href: "/commerce-modules/api-key"
|
||||
},
|
||||
{
|
||||
title: "Auth Module",
|
||||
href: "/commerce-modules/auth"
|
||||
},
|
||||
{
|
||||
title: "Cart Module",
|
||||
href: "/commerce-modules/cart"
|
||||
},
|
||||
{
|
||||
title: "Currency Module",
|
||||
href: "/commerce-modules/currency"
|
||||
},
|
||||
{
|
||||
title: "Customer Module",
|
||||
href: "/commerce-modules/customer"
|
||||
},
|
||||
{
|
||||
title: "Fulfillment Module",
|
||||
href: "/commerce-modules/fulfillment"
|
||||
},
|
||||
{
|
||||
title: "Inventory Module",
|
||||
href: "/commerce-modules/inventory"
|
||||
},
|
||||
{
|
||||
title: "Order Module",
|
||||
href: "/commerce-modules/order"
|
||||
},
|
||||
{
|
||||
title: "Payment Module",
|
||||
href: "/commerce-modules/payment"
|
||||
},
|
||||
{
|
||||
title: "Pricing Module",
|
||||
href: "/commerce-modules/pricing"
|
||||
},
|
||||
{
|
||||
title: "Product Module",
|
||||
href: "/commerce-modules/product"
|
||||
},
|
||||
{
|
||||
title: "Promotion Module",
|
||||
href: "/commerce-modules/promotion"
|
||||
},
|
||||
{
|
||||
title: "Region Module",
|
||||
href: "/commerce-modules/region"
|
||||
},
|
||||
{
|
||||
title: "Sales Channel Module",
|
||||
href: "/commerce-modules/sales-channel"
|
||||
},
|
||||
{
|
||||
title: "Stock Location Module",
|
||||
href: "/commerce-modules/stock-location"
|
||||
},
|
||||
{
|
||||
title: "Store Module",
|
||||
href: "/commerce-modules/store"
|
||||
},
|
||||
{
|
||||
title: "Tax Module",
|
||||
href: "/commerce-modules/tax"
|
||||
},
|
||||
{
|
||||
title: "User Module",
|
||||
href: "/commerce-modules/user"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user