diff --git a/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx b/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx index 3d5b40bd3f4ae..aa4f20d261c1d 100644 --- a/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx +++ b/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx @@ -124,7 +124,7 @@ You can now create the workflow that runs the `createBrandStep`. A workflow is c Add the following content in the same `src/workflows/create-brand.ts` file: -```ts +```ts title="src/workflows/create-brand.ts" // other imports... import { // ... diff --git a/www/apps/book/app/learn/customization/integrate-systems/handle-event/page.mdx b/www/apps/book/app/learn/customization/integrate-systems/handle-event/page.mdx index d0cad26da6354..dc9f7e51ad619 100644 --- a/www/apps/book/app/learn/customization/integrate-systems/handle-event/page.mdx +++ b/www/apps/book/app/learn/customization/integrate-systems/handle-event/page.mdx @@ -1,31 +1,43 @@ import { Prerequisites } from "docs-ui" export const metadata = { - title: `${pageNumber} Brand Example: Handle Event to Sync Third-Party System`, + title: `${pageNumber} Guide: Sync Brands from Medusa to CMS`, } # {metadata.title} - +In the [previous chapter](../service/page.mdx), you created a CMS Module that integrates a dummy third-party system. You can now perform actions using that module within your custom flows. -This chapter covers how to emit an event when a brand is created, listen to that event in a subscriber, and create the brand in the third-party system as a step of the ["Integrate Systems" chapter](../page.mdx). +In another previous chapter, you [added a workflow](../../custom-features/workflow/page.mdx) that creates a brand. After integrating the CMS, you want to sync that brand to the third-party system as well. + +Medusa has an event system that emits events when an operation is performed. It allows you to listen to those events and perform an asynchronous action in a function called a [subscriber](../../../basics/events-and-subscribers/page.mdx). This is useful to perform actions that aren't integral to the original flow, such as syncing data to a third-party system. + + + +Learn more about Medusa's event system and subscribers in [this chapter](../../../basics/events-and-subscribers/page.mdx). -## 1. Emit Custom Event for Brand Creation +In this chapter, you'll modify the `createBrandWorkflow` you created before to emit a custom event that indicates a brand was created. Then, you'll listen to that event in a subscriber to sync the brand to the third-party CMS. You'll implement the sync logic within a workflow that you execute in the subscriber. -To handle brand-creation event, you'll emit a custom event when a brand is created. +## 1. Emit Event in createBrandWorkflow -In the `createBrandWorkflow` defined in `src/workflows/create-brand/index.ts`, use the `emitEventStep` helper step imported from `@medusajs/medusa/core-flows` after the `createBrandStep`: +Since syncing the brand to the third-party system isn't integral to creating a brand, you'll emit a custom event indicating that a brand was created. + +Medusa provides an `emitEventStep` that allows you to emit an event in your workflows. So, in the `createBrandWorkflow` defined in `src/workflows/create-brand.ts`, use the `emitEventStep` helper step after the `createBrandStep`: export const eventHighlights = [ ["13", "emitEventStep", "Emit an event."], @@ -58,126 +70,183 @@ export const createBrandWorkflow = createWorkflow( ) ``` -The `emitEventStep` accepts as a parameter an object having two properties: +The `emitEventStep` accepts an object parameter having two properties: + +- `eventName`: The name of the event to emit. You'll use this name later to listen to the event in a subscriber. +- `data`: The data payload to emit with the event. This data is passed to subscribers that listen to the event. You add the brand's ID to the data payload, informing the subscribers which brand was created. -- `eventName`: The name of the event to emit. -- `data`: The data payload to emit with the event. This is useful for subscribers to access the created brand. +You'll learn how to handle this event in a later step. --- ## 2. Create Sync to Third-Party System Workflow -Next, you'll create the workflow that syncs the created brand to the third-party system. +The subscriber that will listen to the `brand.created` event will sync the created brand to the third-party CMS. So, you'll implement the syncing logic in a workflow, then execute the workflow in the subscriber. -Create the file `src/workflows/sync-brand-to-system/index.ts` with the following content: +Workflows have a built-in durable execution engine that helps you complete tasks spanning multiple systems. Also, their rollback mechanism ensures that data is consistent across systems even when errors occur during execution. -```ts title="src/workflows/sync-brand-to-system/index.ts" -import { - createWorkflow, - WorkflowResponse, -} from "@medusajs/framework/workflows-sdk" + -export type SyncBrandToSystemInput = { - id: string -} +Learn more about workflows in [this chapter](../../../basics/workflows/page.mdx). -export const syncBrandToSystemWorkflow = createWorkflow( - "sync-brand-to-system", - (input: SyncBrandToSystemInput) => { - // ... - } -) -``` + + +You'll create a `syncBrandToSystemWorkflow` that has two steps: + +- `useQueryGraphStep`: a step that Medusa provides to retrieve data using [Query](../../../advanced-development/module-links/query/page.mdx). You'll use this to retrieve the brand's details using its ID. +- `syncBrandToCmsStep`: a step that you'll create to sync the brand to the CMS. -This defines an empty workflow and its expected input. +### syncBrandToCmsStep -### Create createBrandInSystemStep +To implement the step that syncs the brand to the CMS, create the file `src/workflows/sync-brands-to-cms.ts` with the following content: -Next, create the step that syncs the brand in the file `src/workflows/sync-brand-to-system/steps/create-brand-in-system.ts`: +![Directory structure of the Medusa application after adding the file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733493547/Medusa%20Book/cms-dir-overview-4_u5t0ug.jpg) -export const stepHighlights = [ - ["18", "createBrand", "Create a brand in the third-party system."], - ["27", "deleteBrand", "Delete the brand in the third-party system if an error occurs."] +export const syncStepHighlights = [ + ["8", "InferTypeOf", "Get the `Brand` data model as a type."], + ["14", "cmsModuleService", "Resolve the CMS Module's service from the container."], + ["16", "createBrand", "Create the brand in the third-party CMS."], + ["18", "brand.id", "Pass the brand's ID to the compensation function."], + ["27", "deleteBrand", "Delete the brand in the third-party CMS if an error occurs."] ] -```ts title="src/workflows/sync-brand-to-system/steps/create-brand-in-system.ts" highlights={stepHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports" -import { - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import { SyncBrandToSystemInput } from ".." -import BrandModuleService from "../../../modules/brand/service" -import { BRAND_MODULE } from "../../../modules/brand" +```ts title="src/workflows/sync-brands-to-cms.ts" highlights={syncStepHighlights} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" +import { InferTypeOf } from "@medusajs/framework/types" +import { Brand } from "../modules/brand/models/brand" +import { CMS_MODULE } from "../modules/cms" +import CmsModuleService from "../modules/cms/service" + +type SyncBrandToCmsStepInput = { + brand: InferTypeOf +} -export const createBrandInSystemStep = createStep( - "create-brand-in-system", - async ({ id }: SyncBrandToSystemInput, { container }) => { - const brandModuleService: BrandModuleService = container.resolve( - BRAND_MODULE - ) +const syncBrandToCmsStep = createStep( + "sync-brand-to-cms", + async ({ brand }: SyncBrandToCmsStepInput, { container }) => { + const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE) - const brand = await brandModuleService.retrieveBrand(id) - - await brandModuleService.client.createBrand(brand) + await cmsModuleService.createBrand(brand) return new StepResponse(null, brand.id) }, async (id, { container }) => { - const brandModuleService: BrandModuleService = container.resolve( - BRAND_MODULE - ) + if (!id) { + return + } + + const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE) - await brandModuleService.client.deleteBrand(id) + await cmsModuleService.deleteBrand(id) } ) ``` -This step resolves the Brand Module's main service and uses its `client` property to access its internal service that integrates the third-party system. +You create the `syncBrandToCmsStep` that accepts a brand as an input. In the step, you resolve the CMS Module's service from the [Medusa container](../../../basics/medusa-container/page.mdx) and use its `createBrand` method. This method will create the brand in the third-party CMS. + +You also pass the brand's ID to the step's compensation function. In this function, you delete the brand in the third-party CMS if an error occurs during the workflow's execution. + + + +Learn more about compensation functions in [this chapter](../../../advanced-development/workflows/compensation-function/page.mdx). -In the step, you use the `createBrand` method of the client to create the brand in the third-party system. + -In the compensation function, you undo the step's action using the `deleteBrand` method of the client. +### Create Workflow -### Add Step to Workflow +You can now create the workflow that uses the above step. Add the workflow to the same `src/workflows/sync-brands-to-cms.ts` file: -Finally, add this step to the `syncBrandToSystemWorkflow` in `src/workflows/sync-brand-to-system/index.ts`: +export const syncWorkflowHighlights = [ + ["19", "useQueryGraphStep", "Retrieve the brand's details."], + ["23", "id", "Filter by the brand's ID."], + ["26", "throwIfKeyNotFound", "Throw an error if a brand with the specified ID doesn't exist."], + ["30", "syncBrandToCmsStep", "Create the brand in the third-party CMS."] +] -```ts title="src/workflows/sync-brand-to-system/index.ts" +```ts title="src/workflows/sync-brands-to-cms.ts" highlights={syncWorkflowHighlights} // other imports... -import { createBrandInSystemStep } from "./steps/create-brand-in-system" +import { + // ... + createWorkflow, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -export const syncBrandToSystemWorkflow = createWorkflow( - "sync-brand-to-system", - (input: SyncBrandToSystemInput) => { - createBrandInSystemStep(input) +type SyncBrandToCmsWorkflowInput = { + id: string +} - return new WorkflowResponse(undefined) +export const syncBrandToCmsWorkflow = createWorkflow( + "sync-brand-to-cms", + (input: SyncBrandToCmsWorkflowInput) => { + // @ts-ignore + const { data: brands } = useQueryGraphStep({ + entity: "brand", + fields: ["*"], + filters: { + id: input.id, + }, + options: { + throwIfKeyNotFound: true, + }, + }) + + syncBrandToCmsStep({ + brand: brands[0], + } as SyncBrandToCmsStepInput) + + return new WorkflowResponse({}) } ) ``` -The workflow now calls the step and returns an `undefined` result. +You create a `syncBrandToCmsWorkflow` that accepts the brand's ID as input. The workflow has the following steps: + +- `useQueryGraphStep`: Retrieve the brand's details using Query. You pass the brand's ID as a filter, and set the `throwIfKeyNotFound` option to true so that the step throws an error if a brand with the specified ID doesn't exist. +- `syncBrandToCmsStep`: Create the brand in the third-party CMS. + +You'll execute this workflow in the subscriber next. + + + +Learn more about `useQueryGraphStep` in [this reference](!resources!/references/helper-steps/useQueryGraphStep). + + --- ## 3. Handle brand.created Event -To handle the `brand.created` event, create a subscriber at `src/subscribers/brand-created.ts` with the following content: +You now have a workflow with the logic to sync a brand to the CMS. You need to execute this workflow whenever the `brand.created` event is emitted. So, you'll create a subscriber that listens to and handle the event. + +Subscribers are created in a TypeScript or JavaScript file under the `src/subscribers` directory. So, create the file `src/subscribers/brand-created.ts` with the following content: + +![Directory structure of the Medusa application after adding the subscriber](https://res.cloudinary.com/dza7lstvk/image/upload/v1733493774/Medusa%20Book/cms-dir-overview-5_iqqwvg.jpg) + +export const subscriberHighlights = [ + ["7", "brandCreatedHandler", "The function to execute when the event is emitted."], + ["8", "data", "The event's data payload."], + ["9", "container", "The Medusa container used to resolve resources."], + ["10", "id: string", "The expected data payload's type."], + ["11", "syncBrandToCmsWorkflow", "Execute the workflow to sync the brand to the CMS."], + ["16", "config", "Export the subscriber's configurations."], + ["17", "event", "The event that the subscriber is listening to."] +] -```ts title="src/subscribers/brand-created.ts" +```ts title="src/subscribers/brand-created.ts" highlights={subscriberHighlights} import type { SubscriberConfig, SubscriberArgs, } from "@medusajs/framework" -import { syncBrandToSystemWorkflow } from "../workflows/sync-brand-to-system" +import { syncBrandToCmsWorkflow } from "../workflows/sync-brands-to-cms" export default async function brandCreatedHandler({ event: { data }, container, }: SubscriberArgs<{ id: string }>) { - await syncBrandToSystemWorkflow(container).run({ + await syncBrandToCmsWorkflow(container).run({ input: data, }) } @@ -187,27 +256,84 @@ export const config: SubscriberConfig = { } ``` -The subscriber handler accesses the event payload in the `event.data` property of its object parameter. +A subscriber file must export: + +- The asynchronous function that's executed when the event is emitted. This must be the file's default export. +- An object that holds the subscriber's configurations. It has an `event` property that indicates the name of the event that the subscriber is listening to. + +The subscriber function accepts an object parameter that has two properties: + +- `event`: An object of event details. Its `data` property holds the event's data payload, which is the brand's ID. +- `container`: The Medusa container used to resolve framework and commerce tools. + +In the function, you execute the `syncBrandToCmsWorkflow`, passing it the data payload as an input. So, everytime a brand is created, Medusa will execute this function, which in turn executes the workflow to sync the brand to the CMS. -Learn more about subscribers [in this guide](../../../basics/events-and-subscribers/page.mdx). +Learn more about subscribers in [this chapter](../../../basics/events-and-subscribers/page.mdx). -It then executes the `syncBrandToSystemWorkflow`, passing it the ID of the brand to create in the third-party system. - --- ## Test it Out -To test it out, start the Medusa application and create a brand using the API route created in a [previous chapter](../../custom-features/api-route/page.mdx#test-api-route). +To test the subscriber and workflow out, you'll use the [Create Brand API route](../../custom-features/api-route/page.mdx) you created in a previous chapter. -If you check the logs, you'll find the `brand.created` event was emitted, and that the request to the third-party system was simulated. +First, start the Medusa application: ---- +```bash npm2yarn +npm run dev +``` + +Since the `/admin/brands` API route has a `/admin` prefix, it's only accessible by authenticated admin users. So, to retrieve an authenticated token of your admin user, send a `POST` request to the `/auth/user/emailpass` API Route: + +```bash +curl -X POST 'http://localhost:9000/auth/user/emailpass' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "admin@medusa-test.com", + "password": "supersecret" +}' +``` + +Make sure to replace the email and password with your admin user's credentials. + + + +Don't have an admin user? Refer to [this guide](../../../installation/page.mdx#create-medusa-admin-user). -## Next Chapter: Sync Brand from Third-Party System to Medusa + + +Then, send a `POST` request to `/admin/brands`, passing the token received from the previous request in the `Authorization` header: + +```bash +curl -X POST 'http://localhost:9000/admin/brands' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer {token}' \ +--data '{ + "name": "Acme" +}' +``` + +This request returns the created brand. If you check the logs, you'll find the `brand.created` event was emitted, and that the request to the third-party system was simulated: + +```plain +info: Processing brand.created which has 1 subscribers +http: POST /admin/brands ← - (200) - 16.418 ms +info: Sending a POST request to /brands. +info: Request Data: { + "id": "01JEDWENYD361P664WRQPMC3J8", + "name": "Acme", + "created_at": "2024-12-06T11:42:32.909Z", + "updated_at": "2024-12-06T11:42:32.909Z", + "deleted_at": null +} +info: API Key: "123" +``` + +--- -In the next chapter, you'll learn how to sync brands in the third-party system into Medusa using a workflow and a scheduled job. +## Next Chapter: Sync Brand from Third-Party CMS to Medusa +You can also automate syncing data from a third-party system to Medusa at a regular interval. In the next chapter, you'll learn how to sync brands from the third-party CMS to Medusa once a day. diff --git a/www/apps/book/app/learn/customization/integrate-systems/page.mdx b/www/apps/book/app/learn/customization/integrate-systems/page.mdx index 588ffbabd7090..ae4b51a07dd0a 100644 --- a/www/apps/book/app/learn/customization/integrate-systems/page.mdx +++ b/www/apps/book/app/learn/customization/integrate-systems/page.mdx @@ -4,25 +4,22 @@ export const metadata = { # {metadata.title} -In this chapter, you'll learn how to integrate a third-party system into Medusa. +Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails. -## How to Integrate a Third-Party System? +Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly. -To integrate a third-party system into Medusa, you: +In Medusa, you integrate a third-party system by: -1. Implement the methods to interact with the system in a service. It can either be the main module's service, or an internal service in the module that's used by the main one. -2. Implement in workflows custom features around the integration, such as sending data to the third-party system. - - Workflows roll-back mechanism ensures data consistency. This is essential as you integrate multiple systems into your application. -3. Use the workflow in other resources to expose or utilize the custom functionality. +1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system. +2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps. +3. Executing the workflows you built in an [API route](../../basics/api-routes/page.mdx), at a scheduled time, or when an event is emitted. --- -## Next Chapters: Syncing Brands Example +## Next Chapters: Sync Brands Example -In the next chapters, you'll implement an example of syncing brands with a third-party system, such as a Content Management System (CMS). +In the previous chapters, you've [added brands](../custom-features/module/page.mdx) to your Medusa application. In the next chapters, you will: -That requires: - -1. Implementing the service that integrates the third-party system. -2. Creating a brand in the third-party system when a brand is created in Medusa. -2. Retrieving the brands from the third-party system to sync them with Medusa's brands at a scheduled interval. +1. Integrate a dummy third-party CMS in the Brand Module. +2. Sync brands to the CMS when a brand is created. +2. Sync brands from the CMS at a daily schedule. diff --git a/www/apps/book/app/learn/customization/integrate-systems/schedule-task/page.mdx b/www/apps/book/app/learn/customization/integrate-systems/schedule-task/page.mdx index ba195c5cec52f..5c21a584edbb2 100644 --- a/www/apps/book/app/learn/customization/integrate-systems/schedule-task/page.mdx +++ b/www/apps/book/app/learn/customization/integrate-systems/schedule-task/page.mdx @@ -1,87 +1,106 @@ import { Prerequisites } from "docs-ui" export const metadata = { - title: `${pageNumber} Schedule Syncing Brands from Third-Party System`, + title: `${pageNumber} Guide: Schedule Syncing Brands from CMS`, } # {metadata.title} - +In the previous chapters, you've [integrated a third-party CMS](../service/page.mdx) and implemented the logic to [sync created brands](../handle-event/page.mdx) from Medusa to the CMS. -This chapter covers how to use workflows and scheduled jobs to sync brands from the third-party system as the last step of the ["Integrate Systems" chapter](../page.mdx). +However, when you integrate a third-party system, you want the data to be in sync between the Medusa application and the system. One way to do so is by automatically syncing the data once a day. + +You can create an action to be automatically executed at a specified interval using scheduled jobs. A scheduled job is an asynchronous function with a specified schedule of when the Medusa application should run it. Scheduled jobs are useful to automate repeated tasks. + + + +Learn more about scheduled jobs in [this chapter](../../../basics/scheduled-jobs/page.mdx). -## 1. Implement Syncing Workflow +In this chapter, you'll create a scheduled job that triggers syncing the brands from the third-party CMS to Medusa once a day. You'll implement the syncing logic in a workflow, and execute that workflow in the scheduled job. -Start by defining the workflow that syncs the brand from the third-party system. +--- + +## 1. Implement Syncing Workflow + +You'll start by implementing the syncing logic in a workflow, then execute the workflow later in the scheduled job. + +Workflows have a built-in durable execution engine that helps you complete tasks spanning multiple systems. Also, their rollback mechanism ensures that data is consistent across systems even when errors occur during execution. + + + +Learn more about workflows in [this chapter](../../../basics/workflows/page.mdx). + + -The workflow has the following steps: +This workflow will have three steps: -1. Retrieve brands from the third-party system. -2. Create new brands in Medusa. -3. Update existing brands in Medusa. +1. `retrieveBrandsFromCmsStep` to retrieve the brands from the CMS. +2. `createBrandsStep` to create the brands retrieved in the first step that don't exist in Medusa. +3. `updateBrandsStep` to update the brands retrieved in the first step that exist in Medusa. -### Retrieve Brands Step +### retrieveBrandsFromCmsStep -To create the step that retrieves the brands from the third-party service, create the file `src/workflows/sync-brands-from-system/steps/retrieve-brands-from-system.ts` with the following content: +To create the step that retrieves the brands from the third-party CMS, create the file `src/workflows/sync-brands-from-cms.ts` with the following content: -```ts title="src/workflows/sync-brands-from-system/steps/retrieve-brands-from-system.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports" +![Directory structure of the Medusa application after creating the file.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733494196/Medusa%20Book/cms-dir-overview-6_z1omsi.jpg) + +```ts title="src/workflows/sync-brands-from-cms.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports" import { createStep, StepResponse, } from "@medusajs/framework/workflows-sdk" -import BrandModuleService from "../../../modules/brand/service" -import { BRAND_MODULE } from "../../../modules/brand" +import CmsModuleService from "../modules/cms/service" +import { CMS_MODULE } from "../modules/cms" -export const retrieveBrandsFromSystemStep = createStep( - "retrieve-brands-from-system", +const retrieveBrandsFromCmsStep = createStep( + "retrieve-brands-from-cms", async (_, { container }) => { - const brandModuleService: BrandModuleService = container.resolve( - BRAND_MODULE + const cmsModuleService: CmsModuleService = container.resolve( + CMS_MODULE ) - const brands = await brandModuleService.client.retrieveBrands() + const brands = await cmsModuleService.retrieveBrands() return new StepResponse(brands) } ) ``` -In this step, you resolve the Brand Module's main service from the container, and use its client service to retrieve the brands from the third-party system. - -The step returns the retrieved brands. +You create a `retrieveBrandsFromSystemStep` that resolves the CMS Module's service and uses its `retrieveBrands` method to retrieve the brands in the CMS. You return those brands in the step's response. -### Create Brands Step +### createBrandsStep -Next, create the step that creates new brands in Medusa in the file `src/workflows/sync-brands-from-system/steps/create-brands.ts`: +The brands retrieved in the first step may have brands that don't exist in Medusa. So, you'll create a step that creates those brands. Add the step to the same `src/workflows/sync-brands-from-cms.ts` file: export const createBrandsHighlights = [ - ["21", "createBrands", "Create the brands in Medusa"], - ["30", "deleteBrands", "Delete the brands from Medusa"] + ["22", "createBrands", "Create the brands in Medusa"], + ["35", "deleteBrands", "Delete the brands from Medusa"] ] -```ts title="src/workflows/sync-brands-from-system/steps/create-brands.ts" highlights={createBrandsHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports" -import { - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import { InferTypeOf } from "@medusajs/framework/types" -import BrandModuleService from "../../../modules/brand/service" -import { BRAND_MODULE } from "../../../modules/brand" -import { Brand } from "../../../modules/brand/models/brand" +```ts title="src/workflows/sync-brands-from-cms.ts" highlights={createBrandsHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports" +// other imports... +import BrandModuleService from "../modules/brand/service" +import { BRAND_MODULE } from "../modules/brand" + +// ... + +type CreateBrand = { + name: string +} type CreateBrandsInput = { - brands: InferTypeOf[] + brands: CreateBrand[] } export const createBrandsStep = createStep( @@ -93,52 +112,52 @@ export const createBrandsStep = createStep( const brands = await brandModuleService.createBrands(input.brands) - return new StepResponse(brands, brands.map((brand) => brand.id)) + return new StepResponse(brands, brands) }, - async (ids: string[], { container }) => { + async (brands, { container }) => { + if (!brands) { + return + } + const brandModuleService: BrandModuleService = container.resolve( BRAND_MODULE ) - await brandModuleService.deleteBrands(ids) + await brandModuleService.deleteBrands(brands.map((brand) => brand.id)) } ) ``` -This step receives the brands to create as input. - - +The `createBrandsStep` accepts the brands to create as an input. It resolves the [Brand Module](../../custom-features/module/page.mdx)'s service and uses the generated `createBrands` method to create the brands. -Since a data model is a variable, use the `InferTypeOf` utility imported from `@medusajs/framework/types` to infer its type. +The step passes the created brands to the compensation function, which deletes those brands if an error occurs during the workflow's execution. - + -In the step, you resolve the Brand Module's main service and uses its `createBrands` method to create the brands. +Learn more about compensation functions in [this chapter](../../../advanced-development/workflows/compensation-function/page.mdx). -You return the created brands and pass their IDs to the compensation function, which deletes the brands if an error occurs. + ### Update Brands Step -To create the step that updates existing brands in Medusa, create the file `src/workflows/sync-brands-from-system/steps/update-brands.ts` with the following content: +The brands retrieved in the first step may also have brands that exist in Medusa. So, you'll create a step that updates their details to match that of the CMS. Add the step to the same `src/workflows/sync-brands-from-cms.ts` file: export const updateBrandsHighlights = [ - ["21", "prevUpdatedBrands", "Retrieve the data of the brands before the update."], - ["25", "updateBrands", "Update the brands in Medusa."], - ["34", "updateBrands", "Revert the update by reverting the brands' to before the update."] + ["19", "prevUpdatedBrands", "Retrieve the data of the brands before the update."], + ["23", "updateBrands", "Update the brands in Medusa."], + ["36", "updateBrands", "Revert the update by reverting the brands' details to before the update."] ] -```ts title="src/workflows/sync-brands-from-system/steps/update-brands.ts" highlights={updateBrandsHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports" -import { - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import { InferTypeOf } from "@medusajs/framework/types" -import BrandModuleService from "../../../modules/brand/service" -import { BRAND_MODULE } from "../../../modules/brand" -import { Brand } from "../../../modules/brand/models/brand" +```ts title="src/workflows/sync-brands-from-cms.ts" highlights={updateBrandsHighlights} +// ... + +type UpdateBrand = { + id: string + name: string +} type UpdateBrandsInput = { - brands: InferTypeOf[] + brands: UpdateBrand[] } export const updateBrandsStep = createStep( @@ -157,6 +176,10 @@ export const updateBrandsStep = createStep( return new StepResponse(updatedBrands, prevUpdatedBrands) }, async (prevUpdatedBrands, { container }) => { + if (!prevUpdatedBrands) { + return + } + const brandModuleService: BrandModuleService = container.resolve( BRAND_MODULE ) @@ -166,27 +189,24 @@ export const updateBrandsStep = createStep( ) ``` -This step receives the brands to update as input. - -In the step, you retrieve the brands first to pass them later to the compensation function, then update and return the brands. +The `updateBrandsStep` receives the brands to update in Medusa. In the step, you retrieve the brand's details in Medusa before the update to pass them to the compensation function. You then update the brands using the Brand Module's `updateBrands` generated method. -In the compensation function, you update the brands are again but to their data before the update made by the step. +In the compensation function, which receives the brand's old data, you revert the update using the same `updateBrands` method. ### Create Workflow -Finally, create the workflow in the file `src/workflows/sync-brands-from-system/index.ts` with the following content: +Finally, you'll create the workflow that uses the above steps to sync the brands from the CMS to Medusa. Add to the same `src/workflows/sync-brands-from-cms.ts` file the following: -```ts title="src/workflows/sync-brands-from-system/index.ts" +```ts title="src/workflows/sync-brands-from-cms.ts" +// other imports... import { + // ... createWorkflow, - WorkflowResponse, transform, + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" -import { InferTypeOf } from "@medusajs/framework/types" -import { retrieveBrandsFromSystemStep } from "./steps/retrieve-brands-from-system" -import { createBrandsStep } from "./steps/create-brands" -import { updateBrandsStep } from "./steps/update-brands" -import { Brand } from "../../modules/brand/models/brand" + +// ... export const syncBrandsFromSystemWorkflow = createWorkflow( "sync-brands-from-system", @@ -198,35 +218,37 @@ export const syncBrandsFromSystemWorkflow = createWorkflow( ) ``` -For now, you only add the `retrieveBrandsFromSystemStep` to the workflow that retrieves the brands from the third-party system. +In the workflow, you only use the `retrieveBrandsFromSystemStep` for now, which retrieves the brands from the third-party CMS. -### Identify Brands to Create or Update in Workflow +Next, you need to identify which brands must be created or updated. Since workflows are constructed internally and are only evaluated during execution, you can't access values to perform data manipulation directly. Instead, use [transform](../../../advanced-development/workflows/variable-manipulation/page.mdx) from the Workflows SDK that gives you access to the real-time values of the data, allowing you to create new variables using those values. -Next, you need to identify which brands must be created or updated. + -Since workflows are constructed internally and are only evaluated during execution, you can't access any data's value to perform data manipulation or checks. +Learn more about data manipulation using `transform` in [this chapter](../../../advanced-development/workflows/variable-manipulation/page.mdx). -Instead, use the `transform` utility function imported from `@medusajs/framework/workflows-sdk`, which gives you access to the real-time values of the data to perform actions on them. + So, replace the `TODO` with the following: -```ts title="src/workflows/sync-brands-from-system/index.ts" +```ts title="src/workflows/sync-brands-from-cms.ts" const { toCreate, toUpdate } = transform( { brands, }, (data) => { - const toCreate: InferTypeOf[] = [] - const toUpdate: InferTypeOf[] = [] + const toCreate: CreateBrand[] = [] + const toUpdate: UpdateBrand[] = [] data.brands.forEach((brand) => { if (brand.external_id) { toUpdate.push({ - ...brand, - id: brand.external_id, + id: brand.external_id as string, + name: brand.name as string, }) } else { - toCreate.push(brand) + toCreate.push({ + name: brand.name as string, + }) } }) @@ -242,19 +264,11 @@ const { toCreate, toUpdate } = transform( 1. The data to be passed to the function in the second parameter. 2. A function to execute only when the workflow is executed. Its return value can be consumed by the rest of the workflow. -In the function, you sort the brands as to be created or to be updated based on whether they have an `external_id` property. - - +In `transform`'s function, you loop over the brands array to check which should be created or updated. This logic assumes that a brand in the CMS has an `external_id` property whose value is the brand's ID in Medusa. -This approach assumes that the third-party system stores the ID of the brand in Medusa in `external_id`. - - +You now have the list of brands to create and update. So, replace the new `TODO` with the following: -### Create and Update the Brands - -Finally, replace the new `TODO` with the following: - -```ts title="src/workflows/sync-brands-from-system/index.ts" +```ts title="src/workflows/sync-brands-from-cms.ts" const created = createBrandsStep({ brands: toCreate }) const updated = updateBrandsStep({ brands: toUpdate }) @@ -264,24 +278,28 @@ return new WorkflowResponse({ }) ``` -You pass the brands to be created to the `createBrandsStep`, and the brands to be updated to the `updateBrandsStep`. +You first run the `createBrandsStep` to create the brands that don't exist in Medusa, then the `updateBrandsStep` to update the brands that exist in Medusa. You pass the arrays returned by `transform` as the inputs for the steps. -Then, you return the created and updated brands. +Finally, you return an object of the created and updated brands. You'll execute this workflow in the scheduled job next. --- ## 2. Schedule Syncing Task -To schedule a task that syncs brands from the third-party system, create a scheduled job at `src/jobs/sync-brands-from-system.ts`: +You now have the workflow to sync the brands from the CMS to Medusa. Next, you'll create a scheduled job that runs this workflow once a day to ensure the data between Medusa and the CMS are always in sync. + +A scheduled job is created in a TypeScript or JavaScript file under the `src/jobs` directory. So, create the file `src/jobs/sync-brands-from-cms.ts` with the following content: + +![Directory structure of the Medusa application after adding the scheduled job](https://res.cloudinary.com/dza7lstvk/image/upload/v1733494592/Medusa%20Book/cms-dir-overview-7_dkjb9s.jpg) ```ts title="src/jobs/sync-brands-from-system.ts" import { MedusaContainer } from "@medusajs/framework/types" -import { syncBrandsFromSystemWorkflow } from "../workflows/sync-brands-from-system" +import { syncBrandsFromCmsWorkflow } from "../workflows/sync-brands-from-cms" export default async function (container: MedusaContainer) { const logger = container.resolve("logger") - const { result } = await syncBrandsFromSystemWorkflow(container).run() + const { result } = await syncBrandsFromCmsWorkflow(container).run() logger.info( `Synced brands from third-party system: ${ @@ -291,31 +309,37 @@ export default async function (container: MedusaContainer) { export const config = { name: "sync-brands-from-system", - schedule: "* * * * *", + schedule: "0 0 * * *", // change to * * * * * for debugging } ``` -This defines a scheduled job that runs every minute (for testing purposes). - - +A scheduled job file must export: -Learn more about scheduled jobs [in this guide](../../../basics/scheduled-jobs/page.mdx). +- An asynchronous function that will be executed at the specified schedule. This function must be the file's default export. +- An object of scheduled jobs configuration. It has two properties: + - `name`: A unique name for the scheduled job. + - `schedule`: A string that holds a [cron expression](https://crontab.guru/) indicating the schedule to run the job. - +The scheduled job function accepts as a parameter the [Medusa container](../../../basics/medusa-container/page.mdx) used to resolve framework and commerce tools. You then execute the `syncBrandsFromCmsWorkflow` and use its result to log how many brands were created or updated. -The scheduled job executes the `syncBrandsFromSystemWorkflow` and prints how many brands were created and updated. +Based on the cron expression specified in `config.schedule`, Medusa will run the scheduled job every day at midnight. You can also change it to `* * * * *` to run it every minute for easier debugging. --- ## Test it Out -To test it out, start the Medusa application. In a minute, the scheduled job will run and you'll see a logged message indicating how many brands were created or updated. +To test out the scheduled job, start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +If you set the schedule to `* * * * *` for debugging, the scheduled job will run in a minute. You'll see in the logs how many brands were created or updated. --- ## Summary -In the previous chapters, you: +By following the previous chapters, you utilized Medusa's framework and orchestration tools to perform and automate tasks that span across systems. -- Created a service that acts as a client integrating a third-party system. -- Implemented two-way sync of brands between the third-party system and Medusa using a subscriber and a scheduled job. +With Medusa, you can integrate any service from your commerce ecosystem with ease. You don't have to set up separate applications to manage your different customizations, or worry about data inconsistency across systems. Your efforts only go into implementing the business logic that ties your systems together. diff --git a/www/apps/book/app/learn/customization/integrate-systems/service/page.mdx b/www/apps/book/app/learn/customization/integrate-systems/service/page.mdx index 82b96e44a10d7..daf0fb1121bab 100644 --- a/www/apps/book/app/learn/customization/integrate-systems/service/page.mdx +++ b/www/apps/book/app/learn/customization/integrate-systems/service/page.mdx @@ -1,41 +1,46 @@ import { Prerequisites } from "docs-ui" export const metadata = { - title: `${pageNumber} Integrate Third-Party Brand System in a Service`, + title: `${pageNumber} Guide: Integrate CMS Brand System`, } # {metadata.title} - +In the previous chapters, you've created a [Brand Module](../../custom-features/module/page.mdx) that adds brands to your application. In this chapter, you'll integrate a dummy Content-Management System (CMS) in a new module. The module's service will provide methods to retrieve and manage brands in the CMS. You'll later use this service to sync data from and to the CMS. -This chapter covers how to integrate a dummy third-party system in a service as a step of the ["Integrate Systems" chapter](../page.mdx). + + +Learn more about modules in [this chapter](../../../basics/modules/page.mdx). -## 1. Create Service +## 1. Create Module Directory - +You'll integrate the third-party system in a new CMS Module. So, create the directory `src/modules/cms` that will hold the module's resources. + +![Directory structure after adding the directory for the CMS Module](https://res.cloudinary.com/dza7lstvk/image/upload/v1733492447/Medusa%20Book/cms-dir-overview-1_gasguk.jpg) + +--- -Start by creating the file `src/modules/brand/services/client.ts` with the following content: +## 2. Create Module Service + +Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system. + +Create the CMS Module's service at `src/modules/cms/service.ts` with the following content: + +![Directory structure after adding the CMS Module's service](https://res.cloudinary.com/dza7lstvk/image/upload/v1733492583/Medusa%20Book/cms-dir-overview-2_zwcwh3.jpg) export const serviceHighlights = [ - ["4", "BrandClientOptions", "Define the options that the Brand Module receives necessary for the integration."], - ["8", "InjectedDependencies", "Define the dependencies injected into the service."], - ["20", "moduleDef", "Retrieve the module's configuration."] + ["3", "ModuleOptions", "The options that the CMS Module receives."], + ["7", "InjectedDependencies", "The dependencies injected into the service from the module's container."], + ["16", "logger", "Dependencies injected from the module's container"], + ["16", "options", "Options passed to the module in the configurations."] ] -```ts title="src/modules/brand/services/client.ts" highlights={serviceHighlights} +```ts title="src/modules/cms/service.ts" highlights={serviceHighlights} import { Logger, ConfigModule } from "@medusajs/framework/types" -import { BRAND_MODULE } from ".." -export type BrandClientOptions = { +export type ModuleOptions = { apiKey: string } @@ -44,68 +49,54 @@ type InjectedDependencies = { configModule: ConfigModule } -export class BrandClient { - private options_: BrandClientOptions +class CmsModuleService { + private options_: ModuleOptions private logger_: Logger - constructor({ logger, configModule }: InjectedDependencies) { + constructor({ logger }: InjectedDependencies, options: ModuleOptions) { this.logger_ = logger + this.options_ = options - const moduleDef = configModule.modules[BRAND_MODULE] - if (typeof moduleDef !== "boolean") { - this.options_ = moduleDef.options as BrandClientOptions - } + // TODO initialize SDK } } -``` -This creates a `BrandClient` service. Using dependency injection, you resolve the `logger` and `configModule` from the Module's container. - -`logger` is useful to log messages, and `configModule` has configurations exported in `medusa-config.ts`. - -You also define an `options_` property in your service to store the module's options. - -The `configModule`'s `modules` property is an object whose keys are registered module names and values are the module's configuration. +export default CmsModuleService +``` -If the module's configuration isn't a boolean, it has an `options` property that holds the module's options. You use it to set the `options_` property's value. +You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters: - +1. The module's container. Since a module is [isolated](../../../advanced-development/modules/isolation/page.mdx), it has a [local container](../../../advanced-development/modules/container/page.mdx) different than the Medusa container you use in other customizations. This container holds framework tools like the [Logger utility](../../../debugging-and-testing/logging/page.mdx) and resources within the module. +2. Options passed to the module when it's later added in Medusa's configurations. These options are useful to pass secret keys or configurations that ensure your module is re-usable across applications. For the CMS Module, you accept the API key to connect to the dummy CMS as an option. -If the service integrating the third-party system was a main service, it receives the module's options as a second parameter. - - +When integrating a third-party system that has a Node.js SDK or client, you can initialize that client in the constructor to be used in the service's methods. ### Integration Methods -Next, add the following methods to simulate sending requests to the third-party system: +Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS. + +Add the following methods in the `CmsModuleService`: export const methodsHighlights = [ - ["10", "sendRequest", "Since the third-party system isn't real, this method only logs a message."], - ["19", "createBrand", "A method that creates a brand in the third-party system."], - ["23", "deleteBrand", "A method that deletes a brand in the third-party system."], - ["27", "retrieveBrands", "A method that retrieves a brand from a third-party system."] + ["6", "sendRequest", "Since the third-party system isn't real, this method only logs a message."], + ["12", "createBrand", "A method that creates a brand in the third-party system."], + ["16", "deleteBrand", "A method that deletes a brand in the third-party system."], + ["20", "retrieveBrands", "A method that retrieves a brand from a third-party system."] ] ```ts title="src/modules/brand/services/client.ts" highlights={methodsHighlights} -// other imports... -import { InferTypeOf } from "@medusajs/framework/types" -import { Brand } from "../models/brand" - export class BrandClient { // ... // a dummy method to simulate sending a request, // in a realistic scenario, you'd use an SDK, fetch, or axios clients private async sendRequest(url: string, method: string, data?: any) { - this.logger_.info(`Sending a ${ - method - } request to ${url}. data: ${JSON.stringify(data, null, 2)}`) - this.logger_.info(`Client Options: ${ - JSON.stringify(this.options_, null, 2) - }`) + this.logger_.info(`Sending a ${method} request to ${url}.`) + this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`) + this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`) } - async createBrand(brand: InferTypeOf) { + async createBrand(brand: Record) { await this.sendRequest("/brands", "POST", brand) } @@ -113,7 +104,7 @@ export class BrandClient { await this.sendRequest(`/brands/${id}`, "DELETE") } - async retrieveBrands() { + async retrieveBrands(): Promise[]> { await this.sendRequest("/brands", "GET") return [] @@ -121,85 +112,70 @@ export class BrandClient { } ``` -The `sendRequest` method is a dummy method to simulate sending a request to a third-party system. +The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal. You also add three methods that use the `sendRequest` method: -- `createBrand` that creates a brand in the third-party system. To reference a brand's type, you use the `InferTypeOf` utility imported from `@medusajs/framework/types`. This transforms a data model, which is a variable, to its equivalent type. +- `createBrand` that creates a brand in the third-party system. - `deleteBrand` that deletes the brand in the third-party system. - `retrieveBrands` to retrieve a brand from the third-party system. --- -## 2. Export Service +## 3. Export Module Definition -If the service integrating the third-party system is the module's main service, you only need to export it in the module definition. +After creating the module's service, you'll export the module definition indicating the module's name and service. -However, since this service is an internal service in the Brand Module, you must export it in a `src/modules/brand/services/index.ts` file: +Create the file `src/modules/cms/index.ts` with the following content: -```ts title="src/modules/brand/services/index.ts" -export * from "./client" -``` +![Directory structure of the Medusa application after adding the module definition file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733492991/Medusa%20Book/cms-dir-overview-3_b0byks.jpg) -This registers the service in the module's container, allowing you to access it in the module's main service. +```ts title="src/modules/cms/index.ts" +import { Module } from "@medusajs/framework/utils" +import CmsModuleService from "./service" ---- - -## 3. Add Internal Service in Main Service - -In the main service at `src/modules/brand/service.ts`, add the following imports and types at the top of the file: - -```ts title="src/modules/brand/service.ts" -// other imports... -import { BrandClient, BrandClientOptions } from "./services" - -type InjectedDependencies = { - brandClient: BrandClient -} -``` - -Then, add the following in the `BrandModuleService` class: +export const CMS_MODULE = "cms" -```ts title="src/modules/brand/service.ts" -class BrandModuleService extends MedusaService({ - Brand, -}) { - public client: BrandClient - - constructor({ brandClient }: InjectedDependencies) { - super(...arguments) - - this.client = brandClient - } -} +export default Module(CMS_MODULE, { + service: CmsModuleService, +}) ``` -In the main module service, you first resolve through dependency injection the `brandClient` from the container and set it in a public property `client`. +You use `Module` from the Modules SDK to export the module's defintion, indicating that the module's name is `cms` and its service is `CmsModuleService`. --- -## 4. Pass Options to the Module +## 4. Add Module to Medusa's Configurations -To pass options in the module, change its configurations in `medusa-config.ts`: +Finally, add the module to the Medusa configurations at `medusa-config.ts`: ```ts title="medusa-config.ts" module.exports = defineConfig({ // ... modules: [ + // ... { - resolve: "./src/modules/brand", + resolve: "./src/modules/cms", options: { - apiKey: process.env.BRAND_API_KEY || "temp", + apiKey: process.env.CMS_API_KEY, }, }, ], }) ``` -A module's configuration accepts an `options` property, which can hold any options to pass to the module. +The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor. + +You can add the `CMS_API_KEY` environment variable to `.env`: + +```bash +CMS_API_KEY=123 +``` --- -## Next Steps: Sync Brand From Medusa to Third-Party System +## Next Steps: Sync Brand From Medusa to CMS + +You can now use the CMS Module's service to perform actions on the third-party CMS. -In the next chapter, you'll learn how to sync brands created in Medusa to the third-party system using a workflow and a scheduled job. +In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service. diff --git a/www/apps/book/app/learn/customization/next-steps/page.mdx b/www/apps/book/app/learn/customization/next-steps/page.mdx index 4dc5e068540cc..d5d355adb91ca 100644 --- a/www/apps/book/app/learn/customization/next-steps/page.mdx +++ b/www/apps/book/app/learn/customization/next-steps/page.mdx @@ -1,32 +1,20 @@ export const metadata = { - title: `${pageNumber} Customizations Next Steps`, + title: `${pageNumber} Customizations Next Steps: Learn the Fundamentals`, } # {metadata.title} -The previous examples under the Customization chapter explained more about customizing Medusa for a realistic use case. +The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS. -Your learning journey doesn't end here, and this only presents some of Medusa's powerful features. - -This chapter guides you into how to continue your learning journey, and what resources will be helpful for you during your development. - -## Follow the Rest of this Documentation - -The next chapters of the documentation provide more in-depth uses of the different concepts you learned about. - -While you can start playing around with Medusa and customize it, it's highly recommended to continue the rest of this documentation to learn about what more you can do with each concept. - ---- +The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals. ## Helpful Resources Guides -The [Development Resources](!resources!) documentation provides more helpful guides and references for your development journey. - -Some of these guides and references are: +The [Development Resources](!resources!) documentation provides more helpful guides and references for your development journey. Some of these guides and references include: -1. [Service Factory Reference](!resources!/service-factory-reference): Learn about the methods generated by the service factory with examples. -2. [Workflows Reference](!resources!/medusa-workflows-reference): Browse the list of workflows and their hooks. 3. [Commerce Modules](!resources!/commerce-modules): Browse the list of commerce modules in Medusa and their references to learn how to use them. +1. [Service Factory Reference](!resources!/service-factory-reference): Learn about the methods generated by `MedusaService` with examples. +2. [Workflows Reference](!resources!/medusa-workflows-reference): Browse the list of core workflows and their hooks that are useful for your customizations. 4. [Admin Injection Zones](!resources!/admin-widget-injection-zones): Browse the injection zones in the Medusa Admin to learn where you can inject widgets. --- diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index ec5da6af6327e..106bd650c190a 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -91,7 +91,7 @@ export const generatedEditDates = { "app/learn/advanced-development/workflows/variable-manipulation/page.mdx": "2024-11-14T16:11:24.538Z", "app/learn/customization/custom-features/api-route/page.mdx": "2024-11-28T13:12:10.521Z", "app/learn/customization/custom-features/module/page.mdx": "2024-11-28T09:25:29.098Z", - "app/learn/customization/custom-features/workflow/page.mdx": "2024-11-28T10:47:28.084Z", + "app/learn/customization/custom-features/workflow/page.mdx": "2024-12-06T14:34:53.354Z", "app/learn/customization/extend-features/extend-create-product/page.mdx": "2024-12-05T09:26:15.796Z", "app/learn/customization/custom-features/page.mdx": "2024-11-28T08:21:55.207Z", "app/learn/customization/customize-admin/page.mdx": "2024-12-06T07:21:02.303Z", @@ -100,11 +100,11 @@ export const generatedEditDates = { "app/learn/customization/extend-features/define-link/page.mdx": "2024-12-04T17:15:16.004Z", "app/learn/customization/extend-features/page.mdx": "2024-09-12T12:38:57.394Z", "app/learn/customization/extend-features/query-linked-records/page.mdx": "2024-12-05T10:36:32.357Z", - "app/learn/customization/integrate-systems/handle-event/page.mdx": "2024-09-30T08:43:53.135Z", - "app/learn/customization/integrate-systems/page.mdx": "2024-09-12T12:33:29.827Z", - "app/learn/customization/integrate-systems/schedule-task/page.mdx": "2024-09-30T08:43:53.135Z", - "app/learn/customization/integrate-systems/service/page.mdx": "2024-10-16T08:49:50.899Z", - "app/learn/customization/next-steps/page.mdx": "2024-09-12T10:50:04.873Z", + "app/learn/customization/integrate-systems/handle-event/page.mdx": "2024-12-06T14:34:53.355Z", + "app/learn/customization/integrate-systems/page.mdx": "2024-12-06T14:34:53.355Z", + "app/learn/customization/integrate-systems/schedule-task/page.mdx": "2024-12-06T14:34:53.355Z", + "app/learn/customization/integrate-systems/service/page.mdx": "2024-12-06T14:34:53.356Z", + "app/learn/customization/next-steps/page.mdx": "2024-12-06T14:34:53.356Z", "app/learn/customization/page.mdx": "2024-09-12T11:16:18.504Z", "app/learn/more-resources/cheatsheet/page.mdx": "2024-07-11T16:11:26.480Z", "app/learn/architecture/architectural-modules/page.mdx": "2024-09-23T12:51:04.520Z", diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs index 382db13feb7cd..b764aa8f2d628 100644 --- a/www/apps/book/sidebar.mjs +++ b/www/apps/book/sidebar.mjs @@ -159,17 +159,17 @@ export const sidebar = numberSidebarItems( children: [ { type: "link", - title: "Integrate with Service", + title: "CMS Module", path: "/learn/customization/integrate-systems/service", }, { type: "link", - title: "Handle Event", + title: "Sync to CMS", path: "/learn/customization/integrate-systems/handle-event", }, { type: "link", - title: "Schedule Task", + title: "Schedule Syncing", path: "/learn/customization/integrate-systems/schedule-task", }, ],