docs: documentation for v1.18 (#5652)

* docs: documentation for v.17.5

* fix links

* updated version number
This commit is contained in:
Shahed Nasser
2023-11-21 08:57:11 +00:00
committed by GitHub
parent adc60e519c
commit 9c7f95c3d5
36 changed files with 1499 additions and 1336 deletions
@@ -135,9 +135,9 @@ export const GET = async (
You can learn more about API Route [here](../api-routes/overview.mdx).
### Services and Subscribers
### Services
As custom repositories are registered in the [dependency container](../fundamentals/dependency-injection.md#dependency-container-and-injection), they can be accessed through dependency injection in the constructor of a service or a subscriber.
As custom repositories are registered in the [dependency container](../fundamentals/dependency-injection.md#dependency-container-and-injection), they can be accessed through dependency injection in the constructor of a service.
For example:
@@ -167,6 +167,32 @@ class PostService extends TransactionBaseService {
You can learn more about services [here](../services/overview.mdx).
### Subscribers
A subscriber handler function can resolve a repository using the `container` property of its parameter. The `container` has a method `resolve` which accepts the registration name of the repository as a parameter.
For example:
```ts title=src/subscribers/post-handler.ts
import {
type SubscriberArgs,
} from "@medusajs/medusa"
import { PostRepository } from "../repositories/post"
export default async function postHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs) {
const postRepository: PostRepository = container.resolve(
"postRepository"
)
// ...
}
// ...
```
### Other Resources
Resources that have access to the dependency container can access repositories just like any other resources. You can learn more about the dependency container and dependency injection in [this documentation](../fundamentals/dependency-injection.md).
@@ -62,9 +62,9 @@ In the class you must implement the `emit` method. You can optionally implement
### Note About the eventToSubscribersMap Property
The `AbstractEventBusModuleService` implements two methods for handling subscription: `subscribe` and `unsubscribe`. In these methods, the subscribed handler methods are managed within a class property `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 methods.
The `AbstractEventBusModuleService` implements two methods for handling subscription: `subscribe` and `unsubscribe`. In these methods, the subscribed handler functions are managed within a class property `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 methods. For example, you can get the subscribers of a method using the `get` method of the map:
In your custom implementation, you can use this property to manage the subscribed handler functions. For example, you can get the subscribers of a method using the `get` method of the map:
```ts
const eventSubscribers =
@@ -144,13 +144,13 @@ class CustomEventBus extends AbstractEventBusModuleService {
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom subscribe logic if necessary.
The `subscribe` method attaches a handler method to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class.
The `subscribe` method attaches a handler function to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class.
The `subscribe` method accepts three parameters:
1. The first parameter `eventName` is a required string. It indicates which event the handler method is subscribing to.
1. The first parameter `eventName` is a required string. It indicates which event the handler function is subscribing to.
2. The second parameter `subscriber` is a required function that performs an action when the event is triggered.
3. The third parameter `context` is an optional object that has the property `subscriberId`. Subscriber IDs are useful to differentiate between handler methods when retrying a failed method. It’s also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the `subscribe` method.
3. The third parameter `context` is an optional object that has the property `subscriberId`. Subscriber IDs are useful to differentiate between handler functions when retrying a failed method. It’s also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the `subscribe` method.
The implementation of this method depends on the service you’re using for the event bus:
@@ -170,11 +170,11 @@ class CustomEventBus extends AbstractEventBusModuleService {
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom unsubscribe logic if necessary.
The `unsubscribe` method is used to unsubscribe a handler method from an event.
The `unsubscribe` method is used to unsubscribe a handler function from an event.
The `unsubscribe` method accepts three parameters:
1. The first parameter `eventName` is a required string. It indicates which event the handler method is unsubscribing from.
1. The first parameter `eventName` is a required string. It indicates which event the handler function is unsubscribing from.
2. The second parameter `subscriber` is a required function that was initially subscribed to the event.
3. The third parameter `context` is an optional object that has the property `subscriberId`. It can be used to specify the ID of the subscriber to unsubscribe.
@@ -0,0 +1,140 @@
---
description: 'Learn how to create a subscriber in Medusa. You can use subscribers to implement functionalities like sending an order confirmation email.'
addHowToData: true
---
# (Deprecated) How to Create a Subscriber
In this document, you’ll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action.
:::note
Following v1.18 of `@medusajs/medusa`, the approach in this guide is deprecated. It's recommended to follow [this guide](./create-subscriber.md) instead.
:::
## Implementation
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convention, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`.
After creating the file under `src/subscribers`, in the constructor of your subscriber, listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscriber’s constructor.
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
For example, here is the `OrderNotifierSubscriber` class created in `src/subscribers/order-notifier.ts`:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder)
}
handleOrder = async (data) => {
console.log("New Order: " + data.id)
}
}
export default OrderNotifierSubscriber
```
This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need.
:::tip
For the `order.placed` event, the `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
:::
### Subscriber ID
The `subscribe` method of the `eventBusService` accepts a third optional parameter which is a context object. This object has a property `subscriberId` with its value being a string. This ID is useful when there is more than one handler method attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler methods when retrying a failed one.
If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID.
An example of using the subscribe method with the third parameter:
```ts
eventBusService.subscribe("order.placed", this.handleOrder, {
subscriberId: "my-unique-subscriber",
})
```
---
## Retrieve Medusa Configurations
Within your subscriber, you may need to access the Medusa configuration exported from `medusa-config.js`. To do that, you can access `configModule` using dependency injection.
For example:
```ts
import { ConfigModule, EventBusService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
configModule: ConfigModule
}
class OrderNotifierSubscriber {
protected readonly configModule_: ConfigModule
constructor({
eventBusService,
configModule,
}: InjectedDependencies) {
this.configModule_ = configModule
eventBusService.subscribe("order.placed", this.handleOrder)
}
// ...
}
export default OrderNotifierSubscriber
```
---
## Using Services in Subscribers
You can access any service through the dependencies injected to your subscriber’s constructor.
For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ productService, eventBusService }) {
this.productService = productService
eventBusService.subscribe(
"order.placed",
this.handleOrder
)
}
// ...
}
```
You can then use `this.productService` anywhere in your subscriber’s methods. For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
// ...
handleOrder = async (data) => {
// ...
const product = this.productService.list()
}
}
```
:::note
When using attributes defined in the subscriber, such as the `productService` in the example above, you must use an arrow function to declare the method. Otherwise, the attribute will be undefined when used.
:::
---
## See Also
- [Example: send order confirmation email](../../modules/orders/backend/send-order-confirmation.md)
- [Example: send registration confirmation email](../../modules/customers/backend/send-confirmation.md)
- [Create a Plugin](../plugins/create.mdx)
@@ -7,51 +7,98 @@ addHowToData: true
In this document, you’ll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action.
## Implementation
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convention, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`.
After creating the file under `src/subscribers`, in the constructor of your subscriber, listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscriber’s constructor.
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
For example, here is the `OrderNotifierSubscriber` class created in `src/subscribers/order-notifier.ts`:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder)
}
handleOrder = async (data) => {
console.log("New Order: " + data.id)
}
}
export default OrderNotifierSubscriber
```
This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need.
:::tip
For the `order.placed` event, the `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
v1.18 of `@medusajs/medusa` introduced a new approach to create a subscriber. If you're looking for the old guide, you can find it [here](./create-subscriber-deprecated.md). However, it's highly recommended you follow this new approach, as the old one is deprecated.
:::
### Subscriber ID
## Implementation
The `subscribe` method of the `eventBusService` accepts a third optional parameter which is a context object. This object has a property `subscriberId` with its value being a string. This ID is useful when there is more than one handler method attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler methods when retrying a failed one.
If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID.
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. It can be created under subdirectories of `src/subscribers` as well. For example, you can place all subscribers to product events under the `src/subscribers/products` directory.
An example of using the subscribe method with the third parameter:
The subscriber file exports a default handler function, and the subscriber's configurations.
```ts
eventBusService.subscribe("order.placed", this.handleOrder, {
subscriberId: "my-unique-subscriber",
})
For example:
```ts title=src/subscribers/product-update-handler.ts
import {
ProductService,
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
export default async function productUpdateHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, any>>) {
const productService: ProductService = container.resolve(
"productService"
)
const { id } = data
const product = await productService.retrieve(id)
// do something with the product...
}
export const config: SubscriberConfig = {
event: ProductService.Events.UPDATED,
context: {
subscriberId: "product-update-handler",
},
}
```
### Subscriber Configuration
The exported configuration object of type `SubscriberConfig` must include the following properties:
- `event`: A string or an array of strings, each being the name of the event that the subscriber handler function listens to.
- `context`: An object that defines the context of the subscriber. It can accept any properties along with the `subscriberId` property. Learn more about the subscriber ID and the context object in [this section](#context-with-subscriber-id).
### Subscriber Handler Function
The default-export of the subscriber file is a handler function that is executed when the events specified in the exported configuration is triggerd.
The function accepts a parameter of type `SubscriberArgs`, which has the following properties:
- `data`: The data payload of the emitted event. Its type is different for each event. So, make sure to check the [events reference](./events-list.md) for the expected payload of the events your subscriber listens to. You can then pass the expected payload type as a type parameter to `SubscriberArgs`, for example, `Record<string, string>`.
- `eventName`: A string indicating the name of the event. This is useful if your subscriber listens to more than one event and you want to differentiate between them.
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
- `pluginOptions`: When the subscriber is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../backend/configurations.md).
---
## Context with Subscriber ID
The `context` property of the subscriber configuration object is passed to the `eventBusService`. You can pass the `subscriberId` and any custom data in it.
:::note
The subscriber ID is useful when there is more than one handler function attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler functions when retrying a failed one, avoiding retrying all subscribers which can lead to data inconsistencies or general unwanted behavior in your system.
:::
### Inferred Subscriber ID
If you don't pass a subscriber ID to the subscriber configurations, the name of the subscriber function is used as the subscriber ID. If the subscriber function is an anonymous function, the name of the subscriber file is used instead.
---
## Caveats for Local Event Bus
If you use the `event-bus-local` as your event bus sevice, note the following:
- The `subscriberId` passed in the context is overwritten to a random ID when using `event-bus-local`. So, setting the subscriber ID in the context won't have any effect in this case.
- The `eventName` passed to the handler function will be `undefined` when using `event-bus-local` as it doesn't pass the event name properly.
:::note
While the local event bus is a good option for development, it's highly recommended to use the [Redis Event Module](./modules/redis.md) in production.
:::
---
## Retrieve Medusa Configurations
@@ -60,71 +107,32 @@ Within your subscriber, you may need to access the Medusa configuration exported
For example:
```ts
import { ConfigModule, EventBusService } from "@medusajs/medusa"
```ts title=src/subscribers/product-update-handler.ts
import {
ProductService,
type SubscriberConfig,
type SubscriberArgs,
type ConfigModule,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
configModule: ConfigModule
}
class OrderNotifierSubscriber {
protected readonly configModule_: ConfigModule
export default async function productUpdateHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs) {
const configModule: ConfigModule = container.resolve(
"configModule"
)
constructor({
eventBusService,
configModule,
}: InjectedDependencies) {
this.configModule_ = configModule
eventBusService.subscribe("order.placed", this.handleOrder)
}
// ...
}
export default OrderNotifierSubscriber
```
---
## Using Services in Subscribers
You can access any service through the dependencies injected to your subscriber’s constructor.
For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ productService, eventBusService }) {
this.productService = productService
eventBusService.subscribe(
"order.placed",
this.handleOrder
)
}
// ...
export const config: SubscriberConfig = {
event: ProductService.Events.UPDATED,
context: {
subscriberId: "product-update-handler",
},
}
```
You can then use `this.productService` anywhere in your subscriber’s methods. For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
// ...
handleOrder = async (data) => {
// ...
const product = this.productService.list()
}
}
```
:::note
When using attributes defined in the subscriber, such as the `productService` in the example above, you must use an arrow function to declare the method. Otherwise, the attribute will be undefined when used.
:::
---
## See Also
@@ -4,7 +4,7 @@ description: 'Learn about the available events and their data payloads in Medusa
# Events Reference
This document details all events in Medusa, when they are triggered, and what data your handler method will receive when the event is triggered.
This document details all events in Medusa, when they are triggered, and what data your handler function will receive when the event is triggered.
## Prerequisites
@@ -15,9 +15,9 @@ Subscribers register handlers for an events and allows you to perform an action
Natively in Medusa there are subscribers to handle different events. However, you can also create your own custom subscribers.
Custom subscribers are TypeScript or JavaScript files in your project's `src/subscribers` directory. Files here should export classes, which will be treated as subscribers by Medusa. By convention, the class name should end with `Subscriber` and the file name should be the camel-case version of the class name without `Subscriber`. For example, the `WelcomeSubscriber` class is in the file `src/subscribers/welcome.ts`.
Custom subscribers are TypeScript or JavaScript files in your project's `src/subscribers` directory. Subscriber files must default export a handler function and export a configuration object.
Whenever an event is emitted, the subscriber’s registered handler method is executed. The handler method receives as a parameter an object that holds data related to the event. For example, if an order is placed the `order.placed` event will be emitted and all the handlers will receive the order id in the parameter object.
Whenever an event is emitted, the subscriber’s handler function is executed. The handler function receives as a parameter an object that includes the data payload, among other parameters. For example, if an order is placed, the `order.placed` event is emitted and all the handlers receive the order ID in the `data` object.
### Example Use Cases
@@ -20,7 +20,7 @@ The backend doesn't have any tightly-coupled frontend. Instead, it exposes [API
Medusa also uses an [Events Architecture](../events/index.mdx) to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as [Redis](https://redis.io/).
Events can be handled using [Subscribers](../events/subscribers.mdx). Subscribers are TypeScript or JavaScript classes that add their methods as handlers for specific events. These handler methods are only executed when an event is triggered.
Events can be handled using [Subscribers](../events/subscribers.mdx). Subscribers are TypeScript or JavaScript files that export a handler function that's executed asynchronously when an event is triggered, and a configuration object defining what events the subscriber listens to.
You can create any of the resources in the backend’s architecture, such as entities, API Routes, services, and more, as part of your custom development without directly modifying the backend itself. The Medusa backend uses [loaders](../loaders/overview.mdx) to load the backend’s resources, as well as your custom resources and resources in [Plugins](../plugins/overview.mdx).
@@ -704,14 +704,42 @@ To resolve resources, such as services, in API Routes, use the `MedusaRequest` o
For example:
```ts
const logger = req.scope.resolve("logger")
const productService: ProductService = req.scope.resolve(
"productService"
)
```
Please note that in API Routes some resources, such as repositories, aren't available. Refer to the [repositories](../entities/repositories.md#api-routes) documentation to learn how you can load them.
### In Subscribers
To resolve resources, such as services, in subscriber handler functions, use the `container` property of the handler function's parameter. The `container` has a method `resolve` which accepts the registration name of a resource as a parameter.
For example:
```ts
import {
ProductService,
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
export default async function productUpdateHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const productService: ProductService = container.resolve(
"productService"
)
// ...
}
// ...
```
### In Classes
In classes such as services, strategies, or subscribers, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.
In classes such as services or strategies, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.
For example:
@@ -23,14 +23,14 @@ The loader file must export a function.
When the loader is defined in the Medusa backend or a plugin, the function receives the following parameters:
1. `container`: the first parameter, which is a `AwilixContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
1. `container`: the first parameter, which is a `MedusaContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
2. `config`: the second parameter, which is an object that holds the loader’s plugin options. If the loader is not created inside a plugin, the config object will be empty.
### Parameters of Loaders in Modules
When the loader is defined in a module, it receives the following parameters:
1. `container`: the first parameter, which is a `AwilixContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
1. `container`: the first parameter, which is a `MedusaContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
2. `logger`: the second parameter, which is a `Logger` object. The logger can be used to log messages in the console.
3. `config`: the third parameter, which is an object that holds the loader’s module options.
@@ -39,11 +39,14 @@ When the loader is defined in a module, it receives the following parameters:
For example, this loader function resolves the `ProductService` and logs in the console the count of products in the Medusa backend:
```ts title=src/loaders/my-loader.ts
import { ProductService, ConfigModule } from "@medusajs/medusa"
import { AwilixContainer } from "awilix"
import {
ProductService,
ConfigModule,
MedusaContainer,
} from "@medusajs/medusa"
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
console.info("Starting loader...")
@@ -21,11 +21,11 @@ import {
ProductService,
ConfigModule,
Logger,
MedusaContainer,
} from "@medusajs/medusa"
import { AwilixContainer } from "awilix"
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
const logger = container.resolve<Logger>("logger")
@@ -90,11 +90,11 @@ import {
ProductService,
ConfigModule,
Logger,
MedusaContainer,
} from "@medusajs/medusa"
import { AwilixContainer } from "awilix"
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
const logger = container.resolve<Logger>("logger")
@@ -77,7 +77,7 @@ Notification Provider Services must have a static property `identifier`.
The `NotificationProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the Service class is used when the Notification Provider is created in the database.
The value of this property is also used later when you want to subscribe the Notification Provider to events in a Subscriber.
The value of this property is also used later when you want to subscribe the Notification Provider to events in a Loader.
For example, in the class you created in the previous code snippet you can add the following property:
@@ -257,39 +257,33 @@ The `to` and `data` properties are used in the `NotificationService` in Medusa
---
## Create a Subscriber
## Subscribe with Loaders
After creating your Notification Provider Service, you must create a Subscriber that registers this Service as a notification handler of events.
After creating your Notification Provider Service, you must create a [Loader](../loaders/overview.mdx) that registers this Service as a notification handler of events.
:::note
Following the previous example, to make sure the `email-sender` Notification Provider handles the `order.placed` event, create the file `src/loaders/notification.ts` with the following content:
This section will not cover the basics of Subscribers. You can read the [Subscribers](../events/create-subscriber.md) documentation to learn more about them and how to create them.
```ts title=src/loaders/notification.ts
import {
MedusaContainer,
NotificationService,
} from "@medusajs/medusa"
:::
export default async (
container: MedusaContainer
): Promise<void> => {
const notificationService = container.resolve<
NotificationService
>("notificationService")
Following the previous example, to make sure the `email-sender` Notification Provider handles the `order.placed` event, create the file `src/subscribers/notification.js` with the following content:
```ts title=src/subscribers/notification.js
class NotificationSubscriber {
constructor({ notificationService }) {
notificationService.subscribe(
"order.placed",
"email-sender"
)
}
// ...
notificationService.subscribe(
"order.placed",
"email-sender"
)
}
export default NotificationSubscriber
```
This subscriber accesses the `notificationService` using dependency injection. The `notificationService` contains a `subscribe` method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that is subscribing to that event.
:::tip
Notice that the value of the `identifier` static property defined in the `EmailSenderService` is used to register the Notification Provider to handle the `order.placed` event.
:::
This loader accesses the `notificationService` through the [MedusaContainer](../fundamentals/dependency-injection.md). The `notificationService` has a `subscribe` method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that's subscribing to that event.
---
@@ -0,0 +1,186 @@
---
description: 'Learn how to create a scheduled job in Medusa. The scheduled job in this example will simply change the status of draft products to published.'
addHowToData: true
---
# (Deprecated) How to Create a Scheduled Job
In this document, you’ll learn how to create a scheduled job in Medusa.
:::note
Following v1.18 of `@medusajs/medusa`, the approach in this guide is deprecated. It's recommended to follow [this guide](./create.md) instead.
:::
## Overview
Medusa allows you to create scheduled jobs that run at specific times during your backend's lifetime. For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day.
This guide explains how to create a scheduled job on your Medusa backend. The scheduled job in this example will simply change the status of draft products to `published`.
---
## Prerequisites
### Medusa Components
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
---
## 1. Create a File
Each scheduled job should reside in a [loader](../loaders/overview.mdx), which is a TypeScript or JavaScript file located under the `src/loaders` directory.
Start by creating the `src/loaders` directory. Then, inside that directory, create the JavaScript or TypeScript file that you’ll add the scheduled job in. You can use any name for the file.
For the example in this tutorial, you can create the file `src/loaders/publish.ts`.
---
## 2. Create Scheduled Job
To create a scheduled job, add the following code in the file you created, which is `src/loaders/publish.ts` in this example:
```ts title=src/loaders/publish.ts
import { MedusaContainer } from "@medusajs/medusa"
const publishJob = async (
container: MedusaContainer,
options: Record<string, any>
) => {
const jobSchedulerService =
container.resolve("jobSchedulerService")
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// job to execute
const productService = container.resolve("productService")
const draftProducts = await productService.list({
status: "draft",
})
for (const product of draftProducts) {
await productService.update(product.id, {
status: "published",
})
}
}
)
}
export default publishJob
```
:::info
The service taking care of background jobs was renamed in v1.7.1. If you are running a previous version, use `eventBusService` instead of `jobSchedulerService`.
:::
This file should export a function that accepts a `container` and `options` parameters. `container` is the dependency container that you can use to resolve services, such as the JobSchedulerService. `options` are the plugin’s options if this scheduled job is created in a plugin.
You then resolve the `JobSchedulerService` and use the `jobSchedulerService.create` method to create the scheduled job. This method accepts four parameters:
- The first parameter is a unique name to give to the scheduled job. In the example above, you use the name `publish-products`.
- The second parameter is an object which can be used to [pass data to the job](#pass-data-to-the-scheduled-job).
- The third parameter is the scheduled job expression pattern. In this example, it will execute the scheduled job once a day at 12 AM.
- The fourth parameter is the function to execute. This is where you add the code to execute once the scheduled job runs. In this example, you retrieve the draft products using the [ProductService](../../references/services/classes/ProductService.mdx) and update the status of each of these products to `published`.
:::tip
You can see examples of scheduled job expression patterns on [crontab guru](https://crontab.guru/examples.html).
:::
### Scheduled Job Name
As mentioned earlier, the first parameter of the `create` method is the name of the scheduled job. By default, if another scheduled job has the same name, your custom scheduled job will replace it.
If you want to ensure both scheduled jobs are registered and used, you can pass as a fifth parameter an options object with a `keepExisting` property set to `true`. For example:
```ts
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// ...
},
{
keepExisting: true,
}
)
```
### Pass Data to the Scheduled Job
To pass data to your scheduled job, add the data to the object passed as a second parameter under the `data` property. This is helpful if you use one function to handle multiple scheduled jobs.
For example:
```ts
jobSchedulerService.create("publish-products", {
data: {
productId,
},
}, "0 0 * * *", async (job) => {
console.log(job.data) // {productId: 'prod_124...'}
// ...
})
```
---
## 3. Run Medusa Backend
:::info
Scheduled Jobs only run while the Medusa backend is running.
:::
Before you run the Medusa backend, make sure to build your code that's under the `src` directory into the `dist` directory with the following command:
```bash npm2yarn
npm run build
```
Then, run the following command to start your Medusa backend:
```bash npm2yarn
npx medusa develop
```
If the scheduled job was registered successfully, you should see a message similar to this logged on your Medusa backend:
```bash
Registering publish-products
```
Where `publish-products` is the unique name you provided to the scheduled job.
Once it is time to run your scheduled job based on the scheduled job expression pattern, the scheduled job will run and you can see it logged on your Medusa backend.
For example, the above scheduled job will run at 12 AM and, when it runs, you can see the following logged on your Medusa backend:
```bash noReport
info: Processing scheduled job: publish-products
```
If you log anything in the scheduled job, for example using `console.log`, or if any errors are thrown, it’ll also be logged on your Medusa backend.
:::tip
To test the previous example out instantly, you can change the scheduled job expression pattern passed as the third parameter to `jobSchedulerService.create` to `* * * * *`. This will run the scheduled job every minute.
:::
---
## See Also
- [Create a Plugin](../plugins/create.mdx)
@@ -7,6 +7,12 @@ addHowToData: true
In this document, you’ll learn how to create a scheduled job in Medusa.
:::tip
v1.18 of `@medusajs/medusa` introduced a new approach to create a scheduled job. If you're looking for the old guide, you can find it [here](./create-deprecated.md). However, it's highly recommended you follow this new approach, as the old one is deprecated.
:::
## Overview
Medusa allows you to create scheduled jobs that run at specific times during your backend's lifetime. For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day.
@@ -17,72 +23,59 @@ This guide explains how to create a scheduled job on your Medusa backend. The sc
## Prerequisites
### Medusa Components
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
To use scheduled jobs, you must configure Redis in your Medusa backend. Learn more in the [Configurations documentation](../backend/configurations.md#redis_url).
---
## 1. Create a File
## Create Scheduled Job
Each scheduled job should reside in a [loader](../loaders/overview.mdx), which is a TypeScript or JavaScript file located under the `src/loaders` directory.
Scheduled jobs are TypeScript or JavaScript guides placed under the `src/jobs` directory. It can be created under subdirectories of `src/job` as well.
Start by creating the `src/loaders` directory. Then, inside that directory, create the JavaScript or TypeScript file that you’ll add the scheduled job in. You can use any name for the file.
The scheduled job file exports a default handler function, and the scheduled job's configurations.
For the example in this tutorial, you can create the file `src/loaders/publish.ts`.
---
## 2. Create Scheduled Job
To create a scheduled job, add the following code in the file you created, which is `src/loaders/publish.ts` in this example:
For example:
```ts title=src/loaders/publish.ts
import { AwilixContainer } from "awilix"
import {
type ProductService,
type ScheduledJobConfig,
type ScheduledJobArgs,
ProductStatus,
} from "@medusajs/medusa"
const publishJob = async (
container: AwilixContainer,
options: Record<string, any>
) => {
const jobSchedulerService =
container.resolve("jobSchedulerService")
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// job to execute
const productService = container.resolve("productService")
const draftProducts = await productService.list({
status: "draft",
})
for (const product of draftProducts) {
await productService.update(product.id, {
status: "published",
})
}
}
export default async function handler({
container,
data,
pluginOptions,
}: ScheduledJobArgs) {
const productService: ProductService = container.resolve(
"productService"
)
const draftProducts = await productService.list({
status: ProductStatus.DRAFT,
})
for (const product of draftProducts) {
await productService.update(product.id, {
status: ProductStatus.PUBLISHED,
})
}
}
export default publishJob
export const config: ScheduledJobConfig = {
name: "publish-once-a-day",
schedule: "0 0 * * *",
data: {},
}
```
:::info
### Scheduled Job Configuration
The service taking care of background jobs was renamed in v1.7.1. If you are running a previous version, use `eventBusService` instead of `jobSchedulerService`.
The exported configuration object of type `ScheduledJobConfig` must include the following properties:
:::
This file should export a function that accepts a `container` and `options` parameters. `container` is the dependency container that you can use to resolve services, such as the JobSchedulerService. `options` are the plugin’s options if this scheduled job is created in a plugin.
You then resolve the `JobSchedulerService` and use the `jobSchedulerService.create` method to create the scheduled job. This method accepts four parameters:
- The first parameter is a unique name to give to the scheduled job. In the example above, you use the name `publish-products`.
- The second parameter is an object which can be used to [pass data to the job](#pass-data-to-the-scheduled-job).
- The third parameter is the scheduled job expression pattern. In this example, it will execute the scheduled job once a day at 12 AM.
- The fourth parameter is the function to execute. This is where you add the code to execute once the scheduled job runs. In this example, you retrieve the draft products using the [ProductService](../../references/services/classes/ProductService.mdx) and update the status of each of these products to `published`.
- `name`: a string indicating a unique name to give to the scheduled job. If another scheduled job has the same name, your custom scheduled job will replace it.
- `schedule`: a string indicating a [cron schedule](https://crontab.guru/#0_0_*_*_*) that determines when the job should run. In this example, it will execute the scheduled job once a day at 12 AM.
- `data`: Optional data you want to pass to the job.
:::tip
@@ -90,46 +83,19 @@ You can see examples of scheduled job expression patterns on [crontab guru](http
:::
### Scheduled Job Name
### Scheduled Job Handler Function
As mentioned earlier, the first parameter of the `create` method is the name of the scheduled job. By default, if another scheduled job has the same name, your custom scheduled job will replace it.
The default-export of the scheduled job file is a handler function that is executed when the events specified in the exported configuration is triggerd.
If you want to ensure both scheduled jobs are registered and used, you can pass as a fifth parameter an options object with a `keepExisting` property set to `true`. For example:
The function accepts a parameter of type `ScheduledJobArgs`, which has the following properties:
```ts
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// ...
},
{
keepExisting: true,
}
)
```
### Pass Data to the Scheduled Job
To pass data to your scheduled job, add the data to the object passed as a second parameter under the `data` property. This is helpful if you use one function to handle multiple scheduled jobs.
For example:
```ts
jobSchedulerService.create("publish-products", {
data: {
productId,
},
}, "0 0 * * *", async (job) => {
console.log(job.data) // {productId: 'prod_124...'}
// ...
})
```
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
- `data`: The data passed in the [configuration object](#scheduled-job-configuration).
- `pluginOptions`: When the scheduled job is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../backend/configurations.md).
---
## 3. Run Medusa Backend
## Test it Out
:::info
@@ -149,27 +115,27 @@ Then, run the following command to start your Medusa backend:
npx medusa develop
```
If the scheduled job was registered successfully, you should see a message similar to this logged on your Medusa backend:
If the scheduled job is registered successfully, you should see a message similar to this logged on your Medusa backend:
```bash
Registering publish-products
Registering publish-once-a-day
```
Where `publish-products` is the unique name you provided to the scheduled job.
Where `publish-once-a-day` is the unique name you provided to the scheduled job.
Once it is time to run your scheduled job based on the scheduled job expression pattern, the scheduled job will run and you can see it logged on your Medusa backend.
Once it's time to run your scheduled job based on the `schedule` configuration, the scheduled job will run and you can see it logged on your Medusa backend.
For example, the above scheduled job will run at 12 AM and, when it runs, you can see the following logged on your Medusa backend:
```bash noReport
info: Processing scheduled job: publish-products
info: Processing scheduled job: publish-once-a-day
```
If you log anything in the scheduled job, for example using `console.log`, or if any errors are thrown, it’ll also be logged on your Medusa backend.
:::tip
To test the previous example out instantly, you can change the scheduled job expression pattern passed as the third parameter to `jobSchedulerService.create` to `* * * * *`. This will run the scheduled job every minute.
To test the previous example out instantly, you can change the value of `schedule` in the exported configuration object to `* * * * *`. This will run the scheduled job every minute.
:::
@@ -391,15 +391,29 @@ res.json({
### In a Subscriber
To use your custom service in a subscriber, you can have easy access to it in the subscriber’s dependencies injected to the constructor of your subscriber:
To resolve your custom service in subscriber handler functions, use the `container` property of the handler function's parameter. The `container` has a method `resolve` which accepts the registration name of the service as a parameter.
For example:
```ts
class MySubscriber {
constructor({ postService, eventBusService }) {
this.postService = postService
}
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
import { PostService } from "../services/post.ts"
export default async function postHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const postService: PostService = container.resolve(
"postService"
)
// ...
}
// ...
```
---