docs: rename Architectural Modules to Infrastructure Modules (#12212)
* docs: rename Architectural Modules to Infrastructure Modules * generate again
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
---
|
||||
sidebar_label: "Create Cache Module"
|
||||
tags:
|
||||
- cache
|
||||
- how to
|
||||
- server
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
title: `How to Create a Cache Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll learn how to create a Cache Module.
|
||||
|
||||
## 1. Create Module Directory
|
||||
|
||||
Start by creating a new directory for your module. For example, `src/modules/my-cache`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create the Cache Service
|
||||
|
||||
Create the file `src/modules/my-cache/service.ts` that holds the implementation of the cache service.
|
||||
|
||||
The Cache Module's main service must implement the `ICacheService` interface imported from `@medusajs/framework/types`:
|
||||
|
||||
```ts title="src/modules/my-cache/service.ts"
|
||||
import { ICacheService } from "@medusajs/framework/types"
|
||||
|
||||
class MyCacheService implements ICacheService {
|
||||
get<T>(key: string): Promise<T> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
set(key: string, data: unknown, ttl?: number): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
invalidate(key: string): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default MyCacheService
|
||||
```
|
||||
|
||||
The service implements the required methods based on the desired caching mechanism.
|
||||
|
||||
### Implement get Method
|
||||
|
||||
The `get` method retrieves the value of a cached item based on its key.
|
||||
|
||||
The method accepts a string as a first parameter, which is the key in the cache. It either returns the cached item or `null` if it doesn’t exist.
|
||||
|
||||
For example, to implement this method using Memcached:
|
||||
|
||||
```ts title="src/modules/my-cache/service.ts"
|
||||
class MyCacheService implements ICacheService {
|
||||
// ...
|
||||
async get<T>(cacheKey: string): Promise<T | null> {
|
||||
return new Promise((res, rej) => {
|
||||
this.memcached.get(cacheKey, (err, data) => {
|
||||
if (err) {
|
||||
res(null)
|
||||
} else {
|
||||
if (data) {
|
||||
res(JSON.parse(data))
|
||||
} else {
|
||||
res(null)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implement set Method
|
||||
|
||||
The `set` method is used to set an item in the cache. It accepts three parameters:
|
||||
|
||||
1. The first parameter is a string indicating the key of the data being added to the cache. This key can be used later to get or invalidate the cached item.
|
||||
2. The second parameter is the data to be added to the cache. The data can be of any type.
|
||||
3. The third parameter is optional. It’s a number indicating how long (in seconds) the data should be kept in the cache.
|
||||
|
||||
For example, to implement this method using Memcached:
|
||||
|
||||
```ts title="src/modules/my-cache/service.ts"
|
||||
class MyCacheService implements ICacheService {
|
||||
protected TTL = 60
|
||||
// ...
|
||||
async set(
|
||||
key: string,
|
||||
data: Record<string, unknown>,
|
||||
ttl: number = this.TTL // or any value
|
||||
): Promise<void> {
|
||||
return new Promise((res, rej) =>
|
||||
this.memcached.set(
|
||||
key, JSON.stringify(data), ttl, (err) => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implement invalidate Method
|
||||
|
||||
The `invalidate` method removes an item from the cache using its key.
|
||||
|
||||
By default, items are removed from the cache when their time-to-live (ttl) expires. The `invalidate` method can be used to remove the item beforehand.
|
||||
|
||||
The method accepts a string as a first parameter, which is the key of the item to invalidate and remove from the cache.
|
||||
|
||||
For example, to implement this method using Memcached:
|
||||
|
||||
```ts title="src/modules/my-cache/service.ts"
|
||||
class MyCacheService implements ICacheService {
|
||||
// ...
|
||||
async invalidate(key: string): Promise<void> {
|
||||
return new Promise((res, rej) => {
|
||||
this.memcached.del(key, (err) => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Create Module Definition File
|
||||
|
||||
Create the file `src/modules/my-cache/index.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/my-cache/index.ts"
|
||||
import MyCacheService from "./service"
|
||||
import { Module } from "@medusajs/framework/utils"
|
||||
|
||||
export default Module("my-cache", {
|
||||
service: MyCacheService,
|
||||
})
|
||||
```
|
||||
|
||||
This exports the module's definition, indicating that the `MyCacheService` is the main service of the module.
|
||||
|
||||
---
|
||||
|
||||
## 4. Use Module
|
||||
|
||||
To use your Cache Module, add it to the `modules` object exported as part of the configurations in `medusa-config.ts`. A Cache Module is added under the `cacheService` key.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "./src/modules/my-cache",
|
||||
options: {
|
||||
// any options
|
||||
ttl: 30,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `In-Memory Cache Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The In-Memory Cache Module uses a plain JavaScript Map object to store the cached data. This module is used by default in your Medusa application.
|
||||
|
||||
This module is helpful for development or when you’re testing out Medusa, but it’s not recommended to be used in production.
|
||||
|
||||
For production, it’s recommended to use modules like [Redis Cache Module](../redis/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Register the In-Memory Cache Module
|
||||
|
||||
<Note>
|
||||
|
||||
The In-Memory Cache Module is registered by default in your application.
|
||||
|
||||
</Note>
|
||||
|
||||
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/cache-inmemory",
|
||||
options: {
|
||||
// optional options
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### In-Memory Cache Module Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`ttl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The number of seconds an item can live in the cache before it’s removed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`30` seconds
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
@@ -0,0 +1,86 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
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
|
||||
|
||||
Medusa provides the following Cache Modules. You can use one of them, or [Create a Cache Module](./create/page.mdx).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "In-Memory",
|
||||
href: "/infrastructure-modules/cache/in-memory",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/infrastructure-modules/cache/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,171 @@
|
||||
import { Table, Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Redis Cache Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Redis Cache Module uses Redis to cache data in your store. In production, it's recommended to use this module.
|
||||
|
||||
---
|
||||
|
||||
## Register the Redis Cache Module
|
||||
|
||||
<Prerequisites items={[
|
||||
{
|
||||
text: "Redis installed and Redis server running",
|
||||
link: "https://redis.io/docs/getting-started/installation/"
|
||||
}
|
||||
]} />
|
||||
|
||||
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
|
||||
|
||||
export const highlights = [
|
||||
["11", "redisUrl", "The Redis connection URL."]
|
||||
]
|
||||
|
||||
```ts title="medusa-config.ts" highlights={highlights}
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/cache-redis",
|
||||
options: {
|
||||
redisUrl: process.env.CACHE_REDIS_URL,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables:
|
||||
|
||||
```bash
|
||||
CACHE_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
### Redis Cache Module 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>
|
||||
|
||||
`ttl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The number of seconds an item can live in the cache before it’s removed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`30` seconds
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`namespace`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string used to prefix all cached keys with `{namespace}:`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`medusa`. So, all cached keys are prefixed with `medusa:`.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
## Test the Module
|
||||
|
||||
To test the module, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message in the terminal's logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Connection to Redis in module 'cache-redis' established
|
||||
```
|
||||
@@ -0,0 +1,262 @@
|
||||
---
|
||||
sidebar_label: "Create Event Module"
|
||||
tags:
|
||||
- event
|
||||
- how to
|
||||
- server
|
||||
---
|
||||
|
||||
import { TypeList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `How to Create an Event Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll learn how to create an Event Module.
|
||||
|
||||
## 1. Create Module Directory
|
||||
|
||||
Start by creating a new directory for your module. For example, `src/modules/my-event`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create the Event Service
|
||||
|
||||
Create the file `src/modules/my-event/service.ts` that holds the implementation of the event service.
|
||||
|
||||
The Event Module's main service must extend the `AbstractEventBusModuleService` class from the Medusa Framework:
|
||||
|
||||
```ts title="src/modules/my-event/service.ts"
|
||||
import { AbstractEventBusModuleService } from "@medusajs/framework/utils"
|
||||
import { Message } from "@medusajs/types"
|
||||
|
||||
class MyEventService extends AbstractEventBusModuleService {
|
||||
async emit<T>(data: Message<T> | Message<T>[], options: Record<string, unknown>): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async releaseGroupedEvents(eventGroupId: string): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async clearGroupedEvents(eventGroupId: string): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
export default MyEventService
|
||||
```
|
||||
|
||||
The service implements the required methods based on the desired publish/subscribe logic.
|
||||
|
||||
### eventToSubscribersMap_ Property
|
||||
|
||||
The `AbstractEventBusModuleService` has a field `eventToSubscribersMap_`, which is a JavaScript Map. The map's keys are the event names, whereas the value of each key is an array of subscribed handler functions.
|
||||
|
||||
In your custom implementation, you can use this property to manage the subscribed handler functions:
|
||||
|
||||
```ts
|
||||
const eventSubscribers =
|
||||
this.eventToSubscribersMap_.get(eventName) || []
|
||||
```
|
||||
|
||||
### emit Method
|
||||
|
||||
The `emit` method is used to push an event from the Medusa application into your messaging system. The subscribers to that event would then pick up the message and execute their asynchronous tasks.
|
||||
|
||||
An example implementation:
|
||||
|
||||
```ts title="src/modules/my-event/service.ts"
|
||||
class MyEventService extends AbstractEventBusModuleService {
|
||||
async emit<T>(data: Message<T> | Message<T>[], options: Record<string, unknown>): Promise<void> {
|
||||
const events = Array.isArray(data) ? data : [data]
|
||||
|
||||
for (const event of events) {
|
||||
console.log(`Received the event ${event.name} with data ${event.data}`)
|
||||
|
||||
// TODO push the event somewhere
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `emit` method receives the following parameters:
|
||||
|
||||
<TypeList
|
||||
types={[
|
||||
{
|
||||
name: "data",
|
||||
type: "`object or array of objects`",
|
||||
description: "The emitted event(s).",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "name",
|
||||
type: "`string`",
|
||||
description: "The name of the emitted event.",
|
||||
optional: false
|
||||
},
|
||||
{
|
||||
name: "data",
|
||||
type: "`object`",
|
||||
description: "The data payload of the event.",
|
||||
optional: false
|
||||
},
|
||||
{
|
||||
name: "metadata",
|
||||
type: "`object`",
|
||||
description: "Additional details of the emitted event.",
|
||||
optional: false,
|
||||
children: [
|
||||
{
|
||||
name: "eventGroupId",
|
||||
type: "string",
|
||||
description: "A group ID that the event belongs to.",
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "options",
|
||||
type: "`object`",
|
||||
description: "Additional options relevant for the event service.",
|
||||
optional: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
### releaseGroupedEvents Method
|
||||
|
||||
Grouped events are useful when you have distributed transactions where you need to explicitly group, release, and clear events upon lifecycle transaction events.
|
||||
|
||||
If your Event Module supports grouped events, this method is used to emit all events in a group, then clear that group.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/modules/my-event/service.ts"
|
||||
class MyEventService extends AbstractEventBusModuleService {
|
||||
protected groupedEventsMap_: Map<string, Message[]>
|
||||
|
||||
constructor() {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
|
||||
this.groupedEventsMap_ = new Map()
|
||||
}
|
||||
|
||||
async releaseGroupedEvents(eventGroupId: string): Promise<void> {
|
||||
const groupedEvents = this.groupedEventsMap_.get(eventGroupId) || []
|
||||
|
||||
for (const event of groupedEvents) {
|
||||
const { options, ...eventBody } = event
|
||||
|
||||
// TODO emit event
|
||||
}
|
||||
|
||||
await this.clearGroupedEvents(eventGroupId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `releaseGroupedEvents` receives the group ID as a parameter.
|
||||
|
||||
In the example above, you add a `groupedEventsMap_` property to store grouped events. Then, in the method, you emit the events in the group, then clear the grouped events using the `clearGroupedEvents` which you'll learn about next.
|
||||
|
||||
To add events to the grouped events map, you can do it in the `emit` method:
|
||||
|
||||
```ts title="src/modules/my-event/service.ts"
|
||||
class MyEventService extends AbstractEventBusModuleService {
|
||||
// ...
|
||||
async emit<T>(data: Message<T> | Message<T>[], options: Record<string, unknown>): Promise<void> {
|
||||
const events = Array.isArray(data) ? data : [data]
|
||||
|
||||
for (const event of events) {
|
||||
console.log(`Received the event ${event.name} with data ${event.data}`)
|
||||
|
||||
if (event.metadata.eventGroupId) {
|
||||
const groupedEvents = this.groupedEventsMap_.get(
|
||||
event.metadata.eventGroupId
|
||||
) || []
|
||||
|
||||
groupedEvents.push(event)
|
||||
|
||||
this.groupedEventsMap_.set(event.metadata.eventGroupId, groupedEvents)
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO push the event somewhere
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### clearGroupedEvents Method
|
||||
|
||||
If your Event Module supports grouped events, this method is used to remove the events of a group.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/modules/my-event/service.ts"
|
||||
class MyEventService extends AbstractEventBusModuleService {
|
||||
// from previous section
|
||||
protected groupedEventsMap_: Map<string, Message[]>
|
||||
|
||||
async clearGroupedEvents(eventGroupId: string): Promise<void> {
|
||||
this.groupedEventsMap_.delete(eventGroupId)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The method accepts the group's name as a parameter.
|
||||
|
||||
In the method, you delete the group from the `groupedEventsMap_` property (added in the previous section), deleting the stored events of it as well.
|
||||
|
||||
---
|
||||
|
||||
## 3. Create Module Definition File
|
||||
|
||||
Create the file `src/modules/my-event/index.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/my-event/index.ts"
|
||||
import MyEventService from "./service"
|
||||
import { Module } from "@medusajs/framework/utils"
|
||||
|
||||
export default Module("my-event", {
|
||||
service: MyEventService,
|
||||
})
|
||||
```
|
||||
|
||||
This exports the module's definition, indicating that the `MyEventService` is the main service of the module.
|
||||
|
||||
---
|
||||
|
||||
## 4. Use Module
|
||||
|
||||
To use your Event Module, add it to the `modules` object exported as part of the configurations in `medusa-config.ts`. An Event Module is added under the `eventBus` key.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "./src/modules/my-event",
|
||||
options: {
|
||||
// any options
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
export const metadata = {
|
||||
title: `Local Event Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
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.
|
||||
|
||||
For production, it’s recommended to use modules like [Redis Event Bus Module](../redis/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Register the Local Event Module
|
||||
|
||||
<Note>
|
||||
|
||||
The Local Event Module is registered by default in your application.
|
||||
|
||||
</Note>
|
||||
|
||||
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/event-bus-local",
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Module
|
||||
|
||||
To test the module, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message in the terminal's logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Local Event Bus installed. This is not recommended for production.
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
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
|
||||
|
||||
Medusa provides the following Event Modules. You can use one of them, or [Create an Event Module](./create/page.mdx).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "Local",
|
||||
href: "/infrastructure-modules/event/local",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/infrastructure-modules/event/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,217 @@
|
||||
import { Table, Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Redis Event Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
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.
|
||||
|
||||
In production, it's recommended to use this module.
|
||||
|
||||
---
|
||||
|
||||
## Register the Redis Event Module
|
||||
|
||||
<Prerequisites items={[
|
||||
{
|
||||
text: "Redis installed and Redis server running",
|
||||
link: "https://redis.io/docs/getting-started/installation/"
|
||||
}
|
||||
]} />
|
||||
|
||||
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
|
||||
|
||||
export const highlights = [
|
||||
["11", "redisUrl", "The Redis connection URL."]
|
||||
]
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/event-bus-redis",
|
||||
options: {
|
||||
redisUrl: process.env.EVENTS_REDIS_URL,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables:
|
||||
|
||||
```bash
|
||||
EVENTS_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
### Redis Event Module 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>
|
||||
|
||||
`queueName`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating BullMQ's queue name.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`events-queue`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`queueOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of options to pass to the BullMQ constructor. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/interfaces/v3.QueueOptions.html) for allowed properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`workerOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of options to pass to the BullMQ Worker constructor. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/interfaces/v3.WorkerOptions.html) for allowed properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`jobOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of options to pass to jobs added to the BullMQ queue. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/modules/v3.html#BulkJobOptions) for allowed properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
## Test the Module
|
||||
|
||||
To test the module, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message in the terminal's logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Connection to Redis in module 'event-redis' established
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Local File Module Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Local File Module Provider stores files uploaded to your Medusa application in the `/uploads` directory.
|
||||
|
||||
<Note type="warning">
|
||||
|
||||
The Local File Module Provider is only for development purposes. Use the [S3 File Module Provider](../s3/page.mdx) in production instead.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Register the Local File Module
|
||||
|
||||
<Note>
|
||||
|
||||
The Local File Module Provider is registered by default in your application.
|
||||
|
||||
</Note>
|
||||
|
||||
Add the module into the `providers` array of the File Module:
|
||||
|
||||
<Note>
|
||||
|
||||
The File Module accepts one provider only.
|
||||
|
||||
</Note>
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/file",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/file-local",
|
||||
id: "local",
|
||||
options: {
|
||||
// provider options...
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Local File Module Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`upload_dir`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The directory to upload files to. Medusa exposes the content of the `static` directory publically. If you change the directory, it must be served and publically accessible.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`static`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`backend_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The URL that serves the files.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`http://localhost:9000/static`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
@@ -0,0 +1,96 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `File Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the File Module and its providers.
|
||||
|
||||
## What is the File Module?
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 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={[
|
||||
{
|
||||
title: "Local",
|
||||
href: "/infrastructure-modules/file/local",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "AWS S3 (and Compatible APIs)",
|
||||
href: "/infrastructure-modules/file/s3",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,427 @@
|
||||
import S3BucketAcl from "../../../troubleshooting/_sections/s3/aws-bucket-acl.mdx"
|
||||
import CloudflareChecksum from "../../../troubleshooting/_sections/s3/cloudflare-checksum.mdx"
|
||||
|
||||
import { Table, Tabs, TabsList, TabsContent, TabsContentWrapper, TabsTrigger, DetailsList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `S3 File Module Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The S3 File Module Provider integrates Amazon S3 and services following a compatible API (such as MinIO or DigitalOcean Spaces) to store files uploaded to your Medusa application.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<Tabs defaultValue="aws">
|
||||
<TabsList>
|
||||
<TabsTrigger value="aws">AWS S3</TabsTrigger>
|
||||
<TabsTrigger value="minio">MinIO</TabsTrigger>
|
||||
<TabsTrigger value="spaces">DigitalOcean Spaces</TabsTrigger>
|
||||
<TabsTrigger value="supabase">Supabase S3 Storage</TabsTrigger>
|
||||
<TabsTrigger value="cloudflare">Cloudflare R2</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContentWrapper>
|
||||
<TabsContent value="aws">
|
||||
|
||||
- [AWS account](https://console.aws.amazon.com/console/home?nc2=h_ct&src=header-signin).
|
||||
- Create [AWS user with AmazonS3FullAccess permissions](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-and-attach-iam-policy.html).
|
||||
- Create [AWS user access key ID and secret access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey).
|
||||
- Create [S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html) with the "Public Access setting" enabled:
|
||||
1. On your bucket's dashboard, click on the Permissions tab.
|
||||
2. Click on the Edit button of the Block public access (bucket settings) section.
|
||||
3. In the form that opens, don't toggle any checkboxes and click the "Save changes" button.
|
||||
4. Confirm saving the changes by entering `confirm` in the pop-up that shows.
|
||||
5. Back on the Permissions page, scroll to the Object Ownership section and click the Edit button.
|
||||
6. In the form that opens:
|
||||
- Choose the "ACLs enabled" card.
|
||||
- Click on the "Save changes" button.
|
||||
7. Back on the Permissions page, scroll to the "Access Control List (ACL)" section and click on the Edit button.
|
||||
8. In the form that opens, enable the Read permission for "Everyone (public access)".
|
||||
9. Check the "I understand the effects of these changes on my objects and buckets." checkbox.
|
||||
10. Click on the "Save changes" button.
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="minio">
|
||||
|
||||
- [Install MinIO](https://min.io/docs/minio/linux/index.html).
|
||||
- Change port to `9001` using the [console address](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#minio.server.-console-address) and [address](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#minio.server.-address) CLI options.
|
||||
- Create MinIO access and secret access key:
|
||||
- Go to User -> Access Keys
|
||||
- Click on the Create Access Keys button.
|
||||
- Click on the Create button.
|
||||
- Copy the keys of the pop-up. Make sure to copy the secret key as it won't be shown again.
|
||||
- Create a [MinIO bucket](https://min.io/docs/minio/linux/administration/console/managing-objects.html#creating-buckets) with public access policy. To add the policy:
|
||||
- Go to the bucket's dashboard from Administrator -> Buckets.
|
||||
- Under the Summary section, click on the pencil icon next to Access Policy.
|
||||
- In the pop-up, choose Custom from the dropdown.
|
||||
- In the editor, enter the following:
|
||||
|
||||
```json highlights={[["15", "{bucketname}", "Replace with the bucket's name."]]}
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:GetObject"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Resource": [
|
||||
"arn:aws:s3:::{bucketname}/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `{bucket_name}` with the name of the bucket you created. You can edit the access policy under the Summary section of your bucket's dashboard.
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="spaces">
|
||||
|
||||
- Create [DigitalOcean account](https://cloud.digitalocean.com/registrations/new).
|
||||
- Create [DigitalOcean Spaces bucket](https://docs.digitalocean.com/products/spaces/how-to/create/).
|
||||
- Create [DigitalOcean Spaces access and secret access keys](https://docs.digitalocean.com/products/spaces/how-to/manage-access/#access-keys).
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="supabase">
|
||||
|
||||
- [Supabase account](https://supabase.com/dashboard/sign-in) with a project.
|
||||
- Create [New Public S3 bucket](https://supabase.com/docs/guides/storage/buckets/creating-buckets?queryGroups=language&language=dashboard).
|
||||
- Create [New S3 Access Keys](https://supabase.com/docs/guides/storage/s3/authentication?queryGroups=language&language=javascript#s3-access-keys).
|
||||
- Create [Storage Policy](https://supabase.com/docs/guides/storage/security/access-control).
|
||||
1. On your bucket's dashboard, click on Policies under the Configuration sidebar.
|
||||
2. Click on New Policy under "Other policies under storage.objects".
|
||||
3. Click get started quickly, choose "insert access for authenticated users only", and click the "Use this template" button.
|
||||
4. Click ALL for "Allowed Operations", click the "Review" button, then the "Save policy" button.
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="cloudflare">
|
||||
1. Create a [Cloudflare account](https://dash.cloudflare.com/sign-up).
|
||||
2. Set up your R2 bucket:
|
||||
- Navigate to R2 Object Storage in your dashboard. You may need to provide your credit-card information.
|
||||
- Click "Create bucket"
|
||||
- Enter a unique bucket name
|
||||
- Select "Automatic" for location
|
||||
- Choose "Standard" for storage class
|
||||
- Confirm by clicking "Create bucket"
|
||||
3. Configure public access:
|
||||
- On your bucket's dashboard, click on the Settings tab.
|
||||
- Scroll down to the Public Access section, and click on "Allow Access" in the "R2.dev subdomain" card.
|
||||
- Type 'allow' to confirm
|
||||
- Copy the Public R2.dev Bucket URL for your `S3_FILE_URL`
|
||||
4. Retrieve credentials:
|
||||
- [Go to API tokens page](https://dash.cloudflare.com/?to=/:account/r2/api-tokens):
|
||||
- Select "Create API token"
|
||||
- Edit the "R2 Token" name
|
||||
- Under Permissions, select Object Read & Write permission types
|
||||
- You can optionally specify the buckets that this API token has access to under the "Specify bucket(s)" section.
|
||||
- Once done, click the "Create API Token" button.
|
||||
- You'll receive an access key ID and a secret access key. Save them to use them later for the `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY` environment variables.
|
||||
|
||||
</TabsContent>
|
||||
</TabsContentWrapper>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Register the S3 File Module
|
||||
|
||||
Add the module into the `providers` array of the File Module:
|
||||
|
||||
<Note>
|
||||
|
||||
The File Module accepts one provider only.
|
||||
|
||||
</Note>
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/medusa/file",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/file-s3",
|
||||
id: "s3",
|
||||
options: {
|
||||
file_url: process.env.S3_FILE_URL,
|
||||
access_key_id: process.env.S3_ACCESS_KEY_ID,
|
||||
secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
|
||||
region: process.env.S3_REGION,
|
||||
bucket: process.env.S3_BUCKET,
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Additional Configuration for MinIO and Supabase
|
||||
|
||||
If you're using MinIO or Supabase, set `forcePathStyle` to `true` in the `additional_client_config` object.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/file",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/file-s3",
|
||||
id: "s3",
|
||||
options: {
|
||||
// ...
|
||||
additional_client_config: {
|
||||
forcePathStyle: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### S3 File Module Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`file_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The base URL to upload files to.
|
||||
|
||||
- For AWS S3, the endpoint is of the format `https://{bucket}.s3.{region}.amazonaws.com`
|
||||
- For MinIO, it's the URL to the MinIO server with the bucket's name. For example, `https://{server-domain}/{bucket}`. Locally, it may be something like `http://192.168.0.123:9001/{bucket}`.
|
||||
- For DigitalOcean Spaces, it's either the Origin Endpoint or the CDN endpoint of your Spaces Object Storage bucket.
|
||||
- for Supabase, it's `https://{uniqueID}.supabase.co/storage/v1/object/public/{bucket}`. You can retrieve the `uniqueID` from [Storage Settings](https://supabase.com/docs/guides/storage/s3/authentication?queryGroups=language&language=javascript#s3-access-keys) page in the Endpoint URL.
|
||||
- For Cloudflare R2, it's `Public R2.dev Bucket URL`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`access_key_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The AWS or (S3 compatible) user's access key ID.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`secret_access_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The AWS or (S3 compatible) user's secret access key.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`region`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The bucket's region code.
|
||||
|
||||
For MinIO, use `us-east-1`.
|
||||
|
||||
For Cloudflare, use `auto`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`bucket`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The bucket's name.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`endpoint`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The URL to the AWS S3 (or compatible S3 API) server.
|
||||
|
||||
- For AWS S3, the endpoint is of the format `https://s3.{region}.amazonaws.com`
|
||||
- For MinIO, it's the URL to the MinIO server. For example, locally, it may be something like `http://192.168.0.123:9001`.
|
||||
- For DigitalOcean Spaces, it's the Spaces Origin Endpoint of the format `https://{region}.digitaloceanspaces.com`.
|
||||
- For Supabase, it's the Endpoint URL in the [Storage Settings](https://supabase.com/docs/guides/storage/s3/authentication?queryGroups=language&language=javascript#s3-access-keys).
|
||||
- For Cloudflare, it's `https://{your-account-id}.r2.cloudflarestorage.com`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`prefix`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string to prefix each uploaded file's name.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`cache_control`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating how long objects remain in the AWS S3 (or compatible S3 API) cache.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`public, max-age=31536000`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`download_file_duration`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the expiry time of presigned URLs in seconds.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`3600` (An hour)
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`additional_client_config`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Any additional configurations to pass to the S3 client.
|
||||
|
||||
Refer to [this AWS API reference](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html) for a full list of accepted configuration.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: "AWS: The bucket does not allow ACLs (Enabling public access to bucket)",
|
||||
content: <S3BucketAcl />
|
||||
},
|
||||
{
|
||||
title: "Cloudflare: Checksum error",
|
||||
content: <CloudflareChecksum />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -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: "/infrastructure-modules/locking/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "Recommended"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "PostgreSQL",
|
||||
href: "/infrastructure-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: "locking-postgres",
|
||||
// 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: "locking-postgres",
|
||||
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: "locking-redis",
|
||||
// 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: "locking-redis",
|
||||
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",
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Local Notification Module Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Local Notification Module Provider simulates sending a notification, but only logs the notification's details in the terminal. This is useful for development.
|
||||
|
||||
---
|
||||
|
||||
## Register the Local Notification Module
|
||||
|
||||
<Note>
|
||||
|
||||
The Local Notification Module Provider is registered by default in your application. It's configured to run on the `feed` channel.
|
||||
|
||||
</Note>
|
||||
|
||||
Add the module into the `providers` array of the Notification Module:
|
||||
|
||||
<Note>
|
||||
|
||||
Only one provider can be defined for a channel.
|
||||
|
||||
</Note>
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification",
|
||||
options: {
|
||||
providers: [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification-local",
|
||||
id: "local",
|
||||
options: {
|
||||
channels: ["email"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Local Notification Module Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`channels`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The channels this notification module is used to send notifications for. While the local notification module doesn't actually send the notification,
|
||||
it's important to specify its channels to make sure it's used when a notification for that channel is created.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
@@ -0,0 +1,131 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
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?
|
||||
|
||||
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](./local/page.mdx) 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](./send-notification/page.mdx). You can also [Create a Notification Module Provider](/references/notification-provider-module).
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "Local",
|
||||
href: "/infrastructure-modules/notification/local",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "SendGrid",
|
||||
href: "/infrastructure-modules/notification/sendgrid",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Notification Module Provider Channels
|
||||
|
||||
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:
|
||||
|
||||
```ts title="medusa-config.ts" highlights={[["19"]]}
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification",
|
||||
options: {
|
||||
providers: [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification-local",
|
||||
id: "notification",
|
||||
options: {
|
||||
channels: ["email"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
The `channels` option is an array of strings indicating the channels this provider is used for.
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
---
|
||||
sidebar_label: "Send Notification"
|
||||
tags:
|
||||
- notification
|
||||
- how to
|
||||
- server
|
||||
---
|
||||
|
||||
import { TypeList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Send Notification with the Notification Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you'll learn how to send a notification using the Notification Module.
|
||||
|
||||
## Use the Create Method
|
||||
|
||||
In your resource, such as a subscriber, resolve the Notification Module's main service and use its `create` method:
|
||||
|
||||
export const highlights = [
|
||||
["12", "notificationModuleService", "Resolve the Notification Module."],
|
||||
["15", "create", "Create the notification to be sent."],
|
||||
[
|
||||
"17",
|
||||
'"email"',
|
||||
"Use the module provider defined for the `email` channel to send an email.",
|
||||
],
|
||||
[
|
||||
"18",
|
||||
'"product-created"',
|
||||
"The ID of the template defined in the third-party service, such as SendGrid.",
|
||||
],
|
||||
[
|
||||
"19",
|
||||
"data",
|
||||
"The data to pass to the template defined in the third-party service.",
|
||||
],
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/product-created.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { INotificationModuleService } from "@medusajs/framework/types"
|
||||
|
||||
export default async function productCreateHandler({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const notificationModuleService: INotificationModuleService =
|
||||
container.resolve(Modules.NOTIFICATION)
|
||||
|
||||
await notificationModuleService.createNotifications({
|
||||
to: "shahednasser@gmail.com",
|
||||
channel: "email",
|
||||
template: "product-created",
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "product.created",
|
||||
}
|
||||
```
|
||||
|
||||
The `create` method accepts an object or an array of objects having the following properties:
|
||||
|
||||
<TypeList
|
||||
types={[
|
||||
{
|
||||
name: "to",
|
||||
type: "`string`",
|
||||
description:
|
||||
"The destination to send the notification to. When sending an email, it'll be the email address. When sending an SMS, it'll be the phone number.",
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
name: "channel",
|
||||
type: "`string`",
|
||||
description:
|
||||
"The channel to send the notification through. For example, `email` or `sms`. The module provider defined for that channel will be used to send the notification.",
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
name: "template",
|
||||
type: "`string`",
|
||||
description:
|
||||
"The ID of the template used for the notification. This is useful for providers like SendGrid, where you define templates within SendGrid and use their IDs here.",
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
name: "data",
|
||||
type: "`Record<string, unknown>`",
|
||||
description: "The data to pass along to the template, if necessary.",
|
||||
},
|
||||
]}
|
||||
sectionTitle="Use the Create Method"
|
||||
/>
|
||||
|
||||
For a full list of properties accepted, refer to [this guide](/references/notification-provider-module#create).
|
||||
@@ -0,0 +1,176 @@
|
||||
import { Table, Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `SendGrid Notification Module Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The SendGrid Notification Module Provider integrates [SendGrid](https://sendgrid.com) to send emails to users and customers.
|
||||
|
||||
---
|
||||
|
||||
## Register the SendGrid Notification Module
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "SendGrid account",
|
||||
link: "https://signup.sendgrid.com",
|
||||
},
|
||||
{
|
||||
text: "Setup SendGrid single sender",
|
||||
link: "https://docs.sendgrid.com/ui/sending-email/sender-verification",
|
||||
},
|
||||
{
|
||||
text: "SendGrid API Key",
|
||||
link: "https://docs.sendgrid.com/ui/account-and-settings/api-keys",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
Add the module into the `providers` array of the Notification Module:
|
||||
|
||||
<Note>
|
||||
|
||||
Only one provider can be defined for a channel.
|
||||
|
||||
</Note>
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification",
|
||||
options: {
|
||||
providers: [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/medusa/notification-sendgrid",
|
||||
id: "sendgrid",
|
||||
options: {
|
||||
channels: ["email"],
|
||||
api_key: process.env.SENDGRID_API_KEY,
|
||||
from: process.env.SENDGRID_FROM,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables:
|
||||
|
||||
```bash
|
||||
SENDGRID_API_KEY=<YOUR_SENDGRID_API_KEY>
|
||||
SENDGRID_FROM=<YOUR_SENDGRID_FROM>
|
||||
```
|
||||
|
||||
### SendGrid Notification Module Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>`channels`</Table.Cell>
|
||||
<Table.Cell>
|
||||
The channels this notification module is used to send notifications for.
|
||||
Only one provider can be defined for a channel.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>`api_key`</Table.Cell>
|
||||
<Table.Cell>The SendGrid API key.</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>`from`</Table.Cell>
|
||||
<Table.Cell>The SendGrid from email.</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
## SendGrid Templates
|
||||
|
||||
When you send a notification, you must specify the ID of the template to use in SendGrid.
|
||||
|
||||
Refer to [this SendGrid documentation guide](https://docs.sendgrid.com/ui/sending-email/how-to-send-an-email-with-dynamic-templates) on how to create templates for your different email types.
|
||||
|
||||
---
|
||||
|
||||
## Test out the Module
|
||||
|
||||
To test the module out, create a simple subscriber at `src/subscribers/product-created.ts` with the following content:
|
||||
|
||||
export const highlights = [
|
||||
["11", "notificationModuleService", "Resolve the Notification Module."],
|
||||
["13", "createNotifications", "Create the notification to be sent."],
|
||||
[
|
||||
"15",
|
||||
'"email"',
|
||||
"By specifying the `email` channel, SendGrid will be used to send the notification.",
|
||||
],
|
||||
["16", '"product-created"', "The ID of the template defined in SendGrid."],
|
||||
["17", "data", "The data to pass to the template defined in SendGrid."],
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/product-created.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export default async function productCreateHandler({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const notificationModuleService = container.resolve(Modules.NOTIFICATION)
|
||||
const productModuleService = container.resolve(Modules.PRODUCT)
|
||||
|
||||
const product = await productModuleService.retrieveProduct(data.id)
|
||||
|
||||
await notificationModuleService.createNotifications({
|
||||
to: "test@gmail.com",
|
||||
channel: "email",
|
||||
template: "product-created",
|
||||
data: {
|
||||
product_title: product.title,
|
||||
product_image: product.images[0]?.url,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "product.created",
|
||||
}
|
||||
```
|
||||
|
||||
In this subscriber, you:
|
||||
|
||||
- Resolve the Notification and Product Modules' main services from the [Medusa container](!docs!/learn/fundamentals/medusa-container).
|
||||
- Retrieve the product's details to pass them to the template in SendGrid.
|
||||
- Use the `createNotifications` method of the Notification Module's main service to create a notification to be sent to the specified email. By specifying the `email` channel, the SendGrid Notification Module Provider is used to send the notification.
|
||||
- The `template` property of the `createNotifications` method's parameter specifies the ID of the template defined in SendGrid.
|
||||
- The `data` property allows you to pass data to the template in SendGrid. For example, the product's title and image.
|
||||
|
||||
Then, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
And create a product either using the [API route](!api!/admin#products_postproducts) or the [Medusa Admin](!user-guide!/products/create). This runs the subscriber and sends an email using SendGrid.
|
||||
@@ -0,0 +1,204 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Infrastructure Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
Medusa's architectural functionalities, such as emitting and subscribing to events or caching data, are all implemented in Infrastructure Modules. An Infrastructure 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 in [the Architecture documentation](!docs!/learn/introduction/architecture).
|
||||
|
||||
This section of the documentation showcases Medusa's Infrastructure Modules, how they work, and how to use them in your Medusa application.
|
||||
|
||||
## 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 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={[
|
||||
{
|
||||
title: "In-Memory",
|
||||
href: "/infrastructure-modules/cache/in-memory",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/infrastructure-modules/cache/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## 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 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={[
|
||||
{
|
||||
title: "Local",
|
||||
href: "/infrastructure-modules/event/local",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/infrastructure-modules/event/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## File Module
|
||||
|
||||
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 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={[
|
||||
{
|
||||
title: "Local",
|
||||
href: "/infrastructure-modules/file/local",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "AWS S3 (and Compatible APIs)",
|
||||
href: "/infrastructure-modules/file/s3",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Locking Module
|
||||
|
||||
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 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: "/infrastructure-modules/locking/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "Recommended"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "PostgreSQL",
|
||||
href: "/infrastructure-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={[
|
||||
{
|
||||
title: "Local",
|
||||
href: "/infrastructure-modules/notification/local",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "SendGrid",
|
||||
href: "/infrastructure-modules/notification/sendgrid",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
### Notification Module Provider Guides
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "Send Notification",
|
||||
href: "/infrastructure-modules/notification/send-notification"
|
||||
},
|
||||
{
|
||||
title: "Create Notification Provider",
|
||||
href: "/references/notification-provider-module"
|
||||
},
|
||||
{
|
||||
title: "Resend",
|
||||
href: "/integrations/guides/resend",
|
||||
badge: {
|
||||
variant: "blue",
|
||||
children: "Integration"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Workflow Engine Module
|
||||
|
||||
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 Workflow Engine modules are provided by Medusa.
|
||||
|
||||
<CardList
|
||||
items={[
|
||||
{
|
||||
title: "In-Memory",
|
||||
href: "/infrastructure-modules/workflow-engine/in-memory",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/infrastructure-modules/workflow-engine/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,392 @@
|
||||
---
|
||||
sidebar_label: "Use Workflow Engine Module"
|
||||
tags:
|
||||
- workflow engine
|
||||
- server
|
||||
- how to
|
||||
---
|
||||
|
||||
import { TypeList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `How to Use the Workflow Engine Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the different methods in the Workflow Engine Module's service and how to use them.
|
||||
|
||||
---
|
||||
|
||||
## Resolve Workflow Engine Module's Service
|
||||
|
||||
In your workflow's step, you can resolve the Workflow Engine 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 workflowEngineModuleService = container.resolve(
|
||||
Modules.WORKFLOW_ENGINE
|
||||
)
|
||||
|
||||
// TODO use workflowEngineModuleService
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This will resolve the service of the configured Workflow Engine Module, which is the [In-Memory Workflow Engine Module](../in-memory/page.mdx) by default.
|
||||
|
||||
You can then use the Workflow Engine Module's service's methods in the step. The rest of this guide details these methods.
|
||||
|
||||
---
|
||||
|
||||
## setStepSuccess
|
||||
|
||||
This method sets an async step in a currently-executing [long-running workflow](!docs!/learn/fundamentals/workflows/long-running-workflow) as successful. The workflow will then continue to the next step.
|
||||
|
||||
### Example
|
||||
|
||||
```ts
|
||||
// other imports...
|
||||
import {
|
||||
TransactionHandlerType,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
await workflowEngineModuleService.setStepSuccess({
|
||||
idempotencyKey: {
|
||||
action: TransactionHandlerType.INVOKE,
|
||||
transactionId,
|
||||
stepId: "step-2",
|
||||
workflowId: "hello-world",
|
||||
},
|
||||
stepResponse: new StepResponse("Done!"),
|
||||
options: {
|
||||
container,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
<TypeList types={[
|
||||
{
|
||||
"name": "idempotencyKey",
|
||||
"type": "`object`",
|
||||
"description": "The details of the step to set as successful.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "action",
|
||||
"type": "`invoke` | `compensate`",
|
||||
"description": "If the step's compensation function is running, use `compensate`. Otherwise, use `invoke`.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "transactionId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow execution's transaction.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "stepId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the step to change its status. This is the first parameter passed to `createStep` when creating the step.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "workflowId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow. This is the first parameter passed to `createWorkflow` when creating the workflow.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stepResponse",
|
||||
"type": "`StepResponse`",
|
||||
"description": "Set the response of the step. This is similar to the response you return in a step's definition, but since the async step doesn't have a response, you set its response when changing its status.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"type": "`object`",
|
||||
"description": "Options to pass to the step.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "container",
|
||||
"type": "`Container`",
|
||||
"description": "An instance of the Medusa container.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="setStepSuccess"/>
|
||||
|
||||
---
|
||||
|
||||
## setStepFailure
|
||||
|
||||
This method sets an async step in a currently-executing [long-running workflow](!docs!/learn/fundamentals/workflows/long-running-workflow) as failed. The workflow will then stop executing and the compensation functions of the workflow's steps will be executed.
|
||||
|
||||
### Example
|
||||
|
||||
```ts
|
||||
// other imports...
|
||||
import {
|
||||
TransactionHandlerType,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
await workflowEngineModuleService.setStepFailure({
|
||||
idempotencyKey: {
|
||||
action: TransactionHandlerType.INVOKE,
|
||||
transactionId,
|
||||
stepId: "step-2",
|
||||
workflowId: "hello-world",
|
||||
},
|
||||
stepResponse: new StepResponse("Failed!"),
|
||||
options: {
|
||||
container,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
<TypeList types={[
|
||||
{
|
||||
"name": "idempotencyKey",
|
||||
"type": "`object`",
|
||||
"description": "The details of the step to set as failed.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "action",
|
||||
"type": "`invoke` | `compensate`",
|
||||
"description": "If the step's compensation function is running, use `compensate`. Otherwise, use `invoke`.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "transactionId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow execution's transaction.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "stepId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the step to change its status. This is the first parameter passed to `createStep` when creating the step.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "workflowId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow. This is the first parameter passed to `createWorkflow` when creating the workflow.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stepResponse",
|
||||
"type": "`StepResponse`",
|
||||
"description": "Set the response of the step. This is similar to the response you return in a step's definition, but since the async step doesn't have a response, you set its response when changing its status.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"type": "`object`",
|
||||
"description": "Options to pass to the step.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "container",
|
||||
"type": "`Container`",
|
||||
"description": "An instance of the Medusa container.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="setStepFailure"/>
|
||||
|
||||
---
|
||||
|
||||
## subscribe
|
||||
|
||||
This method subscribes to a workflow's events. You can use this method to listen to a [long-running workflow](!docs!/learn/fundamentals/workflows/long-running-workflow)'s events and retrieve its result once it's done executing.
|
||||
|
||||
Refer to the [Long-Running Workflows](!docs!/learn/fundamentals/workflows/long-running-workflow#access-long-running-workflow-status-and-result) documentation to learn more.
|
||||
|
||||
### Example
|
||||
|
||||
```ts
|
||||
const { transaction } = await helloWorldWorkflow(container).run()
|
||||
|
||||
const subscriptionOptions = {
|
||||
workflowId: "hello-world",
|
||||
transactionId: transaction.transactionId,
|
||||
subscriberId: "hello-world-subscriber",
|
||||
}
|
||||
|
||||
await workflowEngineModuleService.subscribe({
|
||||
...subscriptionOptions,
|
||||
subscriber: async (data) => {
|
||||
if (data.eventType === "onFinish") {
|
||||
console.log("Finished execution", data.result)
|
||||
// unsubscribe
|
||||
await workflowEngineModuleService.unsubscribe({
|
||||
...subscriptionOptions,
|
||||
subscriberOrId: subscriptionOptions.subscriberId,
|
||||
})
|
||||
} else if (data.eventType === "onStepFailure") {
|
||||
console.log("Workflow failed", data.step)
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
<TypeList types={[
|
||||
{
|
||||
"name": "subscriptionOptions",
|
||||
"type": "`object`",
|
||||
"description": "The options for the subscription.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "workflowId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow to subscribe to. This is the first parameter passed to `createWorkflow` when creating the workflow.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "transactionId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow execution's transaction. This is returned when you execute a workflow.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "subscriberId",
|
||||
"type": "`string`",
|
||||
"description": "A unique ID for the subscriber. It's used to unsubscribe from the workflow's events.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "subscriber",
|
||||
"type": "`(data: WorkflowEvent) => void`",
|
||||
"description": "The subscriber function that will be called when the workflow emits an event.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="subscribe"/>
|
||||
|
||||
---
|
||||
|
||||
## unsubscribe
|
||||
|
||||
This method unsubscribes from a workflow's events. You can use this method to stop listening to a [long-running workflow](!docs!/learn/fundamentals/workflows/long-running-workflow)'s events after you've received the result.
|
||||
|
||||
### Example
|
||||
|
||||
```ts
|
||||
await workflowEngineModuleService.unsubscribe({
|
||||
workflowId: "hello-world",
|
||||
transactionId: "transaction-id",
|
||||
subscriberOrId: "hello-world-subscriber",
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
<TypeList types={[
|
||||
{
|
||||
"name": "workflowId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow to unsubscribe from. This is the first parameter passed to `createWorkflow` when creating the workflow.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "transactionId",
|
||||
"type": "`string`",
|
||||
"description": "The ID of the workflow execution's transaction. This is returned when you execute a workflow.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "subscriberOrId",
|
||||
"type": "`string`",
|
||||
"description": "The subscriber ID or the subscriber function to unsubscribe from the workflow's events.",
|
||||
"optional": false,
|
||||
"defaultValue": "",
|
||||
"expandable": false,
|
||||
"children": []
|
||||
}
|
||||
]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="unsubscribe"/>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `In-Memory Workflow Engine Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The In-Memory Workflow Engine Module uses a plain JavaScript Map object to store the workflow executions.
|
||||
|
||||
This module is helpful for development or when you’re testing out Medusa, but it’s not recommended to be used in production.
|
||||
|
||||
For production, it’s recommended to use modules like [Redis Workflow Engine Module](../redis/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Register the In-Memory Workflow Engine Module
|
||||
|
||||
<Note>
|
||||
|
||||
The In-Memory Workflow Engine Module is registered by default in your application.
|
||||
|
||||
</Note>
|
||||
|
||||
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
|
||||
|
||||
```ts title="medusa-config.ts"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/workflow-engine-inmemory",
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,92 @@
|
||||
import { CardList } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Workflow Engine Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn what a Workflow Engine Module is and how to use it in your Medusa application.
|
||||
|
||||
## 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={[
|
||||
{
|
||||
title: "In-Memory",
|
||||
href: "/infrastructure-modules/workflow-engine/in-memory",
|
||||
badge: {
|
||||
variant: "neutral",
|
||||
children: "For Development"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Redis",
|
||||
href: "/infrastructure-modules/workflow-engine/redis",
|
||||
badge: {
|
||||
variant: "green",
|
||||
children: "For Production"
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,174 @@
|
||||
import { Table, Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Redis Workflow Engine Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Redis Workflow Engine Module uses Redis to track workflow executions and handle their subscribers. In production, it's recommended to use this module.
|
||||
|
||||
---
|
||||
|
||||
## Register the Redis Workflow Engine Module
|
||||
|
||||
<Prerequisites items={[
|
||||
{
|
||||
text: "Redis installed and Redis server running",
|
||||
link: "https://redis.io/docs/getting-started/installation/"
|
||||
}
|
||||
]} />
|
||||
|
||||
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
|
||||
|
||||
export const highlights = [
|
||||
["12", "url", "The Redis connection URL."]
|
||||
]
|
||||
|
||||
```ts title="medusa-config.ts" highlights={highlights}
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: [
|
||||
{
|
||||
resolve: "@medusajs/medusa/workflow-engine-redis",
|
||||
options: {
|
||||
redis: {
|
||||
url: process.env.WE_REDIS_URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables:
|
||||
|
||||
```bash
|
||||
WE_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
### Redis Workflow Engine Module 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>
|
||||
|
||||
`url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Redis connection URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No. If not provided, you must provide the `pubsub` option.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`options`
|
||||
|
||||
</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>
|
||||
|
||||
`queueName`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The name of the queue used to keep track of retries and timeouts.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`medusa-workflows`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`pubsub`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A connection object having the following properties:
|
||||
|
||||
- `url`: A required string indicating the Redis connection URL.
|
||||
- `options`: An optional 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. If not provided, you must provide the `url` option.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
## Test the Module
|
||||
|
||||
To test the module, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message in the terminal's logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Connection to Redis in module 'workflow-engine-redis' established
|
||||
```
|
||||
Reference in New Issue
Block a user