diff --git a/www/apps/resources/app/architectural-modules/event/create/page.mdx b/www/apps/resources/app/architectural-modules/event/create/page.mdx index 9344ee3f49..0f47f55d33 100644 --- a/www/apps/resources/app/architectural-modules/event/create/page.mdx +++ b/www/apps/resources/app/architectural-modules/event/create/page.mdx @@ -1,3 +1,5 @@ +import { TypeList } from "docs-ui" + export const metadata = { title: `How to Create an Event Module`, } @@ -19,15 +21,18 @@ Create the file `src/modules/my-event/service.ts` that holds the implementation The Event Module's main service must extend the `AbstractEventBusModuleService` class imported from `@medusajs/framework/utils`: ```ts title="src/modules/my-event/service.ts" -import { EmitData, Message } from "@medusajs/framework/types" -import { AbstractEventBusModuleService } from "@medusajs/framework/utils" +import { AbstractEventBusModuleService } from "@medusajs/framework/utils"; +import { Message } from "@medusajs/types"; class MyEventService extends AbstractEventBusModuleService { - emit(eventName: string, data: T, options: Record): Promise; - emit(data: EmitData[]): Promise; - emit(data: Message[]): Promise; - emit(eventName: unknown, data?: unknown, options?: unknown): Promise { - throw new Error("Method not implemented.") + async emit(data: Message | Message[], options: Record): Promise { + throw new Error("Method not implemented."); + } + async releaseGroupedEvents(eventGroupId: string): Promise { + throw new Error("Method not implemented."); + } + async clearGroupedEvents(eventGroupId: string): Promise { + throw new Error("Method not implemented."); } } @@ -36,52 +41,175 @@ export default MyEventService The service implements the required methods based on the desired publish/subscribe logic. -### Note About the eventToSubscribersMap Property +### 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. +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) || [] + this.eventToSubscribersMap_.get(eventName) || [] ``` -### Implement emit Method +### 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. -The `emit` method has three different signatures: +An example implementation: -1. The first signature accepts three parameters: - - The first parameter is a string indicating the name of the event to trigger. - - The second parameter is data to send to subscribers of that event. - - The third optional parameter can be used to pass options specific to the event service. -2. The second signature accepts one parameter, which is an array of objects having the following properties: - - `eventName`: A string indicating the name of the event to trigger. - - `data`: The data to send to subsribers of that event. - - `options`: (optional) options specific to the event service. -3. The third signature accepts one parameter, which is an array of objects having the following properties: - - `eventName`: A string indicating the name of the event to trigger. - - `body`: An object of event-related data. It has two properties: `data` holding the data of the event, and `metadata` which is an object with more details on how the event was emitted, such as the action that occurred or the service that emitted it. - - `options`: (optional) options specific to the event service. +```ts title="src/modules/my-event/service.ts" +class MyEventService extends AbstractEventBusModuleService { + async emit(data: Message | Message[], options: Record): Promise { + const events = Array.isArray(data) ? data : [data] -You can implement your method in a way that supports both signatures by checking the type of the first input. For example: + 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: + + + +### 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 + + constructor() { + // @ts-ignore + super(...arguments) + + this.groupedEventsMap_ = new Map() + } + + async releaseGroupedEvents(eventGroupId: string): Promise { + 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 { // ... - emit(eventName: string, data: T, options: Record): Promise; - emit(data: EmitData[]): Promise; - emit(data: Message[]): Promise; - emit(eventOrData: unknown, data?: unknown, options?: unknown): Promise { - const isBulkEmit = Array.isArray(eventOrData) + async emit(data: Message | Message[], options: Record): Promise { + 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 + + async clearGroupedEvents(eventGroupId: string): Promise { + 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 diff --git a/www/apps/resources/app/architectural-modules/page.mdx b/www/apps/resources/app/architectural-modules/page.mdx index 86a3546e46..ae19437da6 100644 --- a/www/apps/resources/app/architectural-modules/page.mdx +++ b/www/apps/resources/app/architectural-modules/page.mdx @@ -8,4 +8,4 @@ export const metadata = { This section includes documentation for official Medusa architectural modules. - \ No newline at end of file + \ No newline at end of file diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 08137ab9f8..f5d862012f 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -236,7 +236,7 @@ export const generatedEditDates = { "app/architectural-modules/cache/create/page.mdx": "2024-10-16T08:51:35.074Z", "app/admin-widget-injection-zones/page.mdx": "2024-09-30T08:43:53.147Z", "app/architectural-modules/notification/page.mdx": "2024-10-15T12:51:28.735Z", - "app/architectural-modules/event/create/page.mdx": "2024-10-16T08:51:41.334Z", + "app/architectural-modules/event/create/page.mdx": "2024-11-12T11:54:51.583Z", "references/core_flows/Order/functions/core_flows.Order.orderEditUpdateItemQuantityValidationStep/page.mdx": "2024-08-20T00:10:58.913Z", "references/core_flows/Order/functions/core_flows.Order.orderEditUpdateItemQuantityWorkflow/page.mdx": "2024-08-20T00:10:58.949Z", "references/core_flows/Order/functions/core_flows.Order.updateOrderEditItemQuantityValidationStep/page.mdx": "2024-08-20T00:10:59.121Z",