From 41fb2aacb08c5a79d47a1ecad8d50fa0d48b248f Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Thu, 12 Sep 2024 09:51:00 +0300 Subject: [PATCH] docs: fixes to long-running workflows + relationship docs (#9101) --- .../data-models/relationships/page.mdx | 12 +- .../workflows/long-running-workflow/page.mdx | 218 ++++++++++++++++-- www/apps/book/generated/edit-dates.mjs | 4 +- 3 files changed, 215 insertions(+), 19 deletions(-) diff --git a/www/apps/book/app/advanced-development/data-models/relationships/page.mdx b/www/apps/book/app/advanced-development/data-models/relationships/page.mdx index 688ea9b9dd..6160d3dc03 100644 --- a/www/apps/book/app/advanced-development/data-models/relationships/page.mdx +++ b/www/apps/book/app/advanced-development/data-models/relationships/page.mdx @@ -108,7 +108,7 @@ For example: export const manyToManyHighlights = [ ["5", "manyToMany", "An order is associated with many products."], - ["10", "manyToMany", "A product is associated with many orders."] + ["12", "manyToMany", "A product is associated with many orders."] ] ```ts highlights={manyToManyHighlights} @@ -116,15 +116,21 @@ import { model } from "@medusajs/utils" const Order = model.define("order", { id: model.id().primaryKey(), - products: model.manyToMany(() => Product), + products: model.manyToMany(() => Product, { + mappedBy: "orders" + }), }) const Product = model.define("product", { id: model.id().primaryKey(), - orders: model.manyToMany(() => Order), + orders: model.manyToMany(() => Order, { + mappedBy: "orders" + }), }) ``` +At least one side of the many-to-many relationship should have the `mappedBy` property set in the second object parameter of the `manyToMany` object. Its value is the name of the relationship property in the other data model. + In this example, an order is associated with many products, and a product is associated with many orders. --- diff --git a/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx b/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx index 34bef1eb33..7bf86cc248 100644 --- a/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx +++ b/www/apps/book/app/advanced-development/workflows/long-running-workflow/page.mdx @@ -18,7 +18,7 @@ A long-running workflow is a workflow that continues its execution in the backgr ## Configure Long-Running Workflows -A workflow is considered long-running if at least one step has its `async` configuration set to `true`. +A workflow is considered long-running if at least one step has its `async` configuration set to `true` and doesn't return a step response. For example, consider the following workflow and steps: @@ -40,7 +40,7 @@ const step2 = createStep( async: true, }, async () => { - return new StepResponse({}) + console.log("Waiting to be successful...") } ) @@ -63,21 +63,211 @@ const myWorkflow = createWorkflow( export default myWorkflow ``` -The second step has in its configuration object `async` set to true. This indicates that this step is an asynchronous step. - - - -An asynchronous step must return for the execution to continue. - - +The second step has in its configuration object `async` set to `true` and it doesn't return a step response. This indicates that this step is an asynchronous step. So, when you execute the `hello-world` workflow, it continues its execution in the background once it reaches the second step. --- +## Change Step Status + +Once the workflow's execution reaches an async step, it'll wait in the background for the step to succeed or fail before it moves to the rest of the steps. + +To change fail or succeed a step, use the workflow engine's main service that is registered in the Medusa Container under the `ModuleRegistrationName.WORKFLOW_ENGINE` (or `workflowsModuleService`) key. + +### Retrieve Transaction ID + +Before changing the status of a workflow execution's async step, you must have the execution's transaction ID. + +When you execute the workflow, the object returned has a `transaction` property, which is an object that holds the details of the workflow execution's transaction. Use its `transactionId` to later change async steps' statuses: + +```ts +const { transaction } = await myWorkflow(req.scope) + .run() + +// use transaction.transactionId later +``` + +### Change Step Status to Successful + +For example, the following workflow step (used in a different workflow) resolves the workflow engine's main service and sets `step-2` of the previous workflow as successful: + +export const successStatusHighlights = [ + ["17", "transactionId", "Receive the workflow execution's transaction ID as an input to the step."], + ["20", "resolve", "Resolve the workflow engine's main service."], + ["24", "setStepSuccess", "Change the step's status to successful."], + ["28", "stepId", "The ID of the step as passed to `createStep`'s first parameter when it was created."], + ["29", "workflowId", "The ID of the workflow as passed to `createWorkflow`'s first parameter when it was created."], + ["31", "stepResponse", "The response returned by the step, since an `async` step can't return a response in its definition."] +] + +```ts highlights={successStatusHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports" +import { + ModuleRegistrationName, + TransactionHandlerType, +} from "@medusajs/utils"; +import { + StepResponse, + createStep +} from "@medusajs/workflows-sdk"; + +type SetStepSuccessStepInput = { + transactionId: string +}; + +export const setStepSuccessStep = createStep( + "set-step-success-step", + async function ( + { transactionId }: SetStepSuccessStepInput, + { container } + ) { + const workflowEngineService = container.resolve( + ModuleRegistrationName.WORKFLOW_ENGINE + ); + + await workflowEngineService.setStepSuccess({ + idempotencyKey: { + action: TransactionHandlerType.INVOKE, + transactionId, + stepId: "step-2", + workflowId: "hello-world", + }, + stepResponse: new StepResponse("Done!"), + options: { + container, + }, + }); + } +); +``` + +You use this step in another workflow that changes the long-running workflow's status. + +After change the async step's status to successful, the workflow execution continues to the next step. + +The `setStepSuccess` method of the workflow engine's main service accepts as a parameter an object having the following properties: + +`", + description: "Options to pass to the step.", + optional: true, + children: [ + { + name: "container", + type: "`MedusaContainer`", + description: "An instance of the Medusa Container", + optional: true + } + ] + } + ]} +/> + +### Change Step Status to Failed + +The workflow engine's main service also has a `setStepFailure` method that changes a step's status to failed. It accepts the same parameter as `setStepSuccess`. + +For example: + +export const failureStatusHighlights = [ + ["17", "transactionId", "Receive the workflow execution's transaction ID as an input to the step."], + ["20", "resolve", "Resolve the workflow engine's main service."], + ["24", "setStepSuccess", "Change the step's status to successful."], + ["28", "stepId", "The ID of the step as passed to `createStep`'s first parameter when it was created."], + ["29", "workflowId", "The ID of the workflow as passed to `createWorkflow`'s first parameter when it was created."], + ["31", "stepResponse", "The response returned by the step, since an `async` step can't return a response in its definition."] +] + +```ts highlights={failureStatusHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports" +import { + ModuleRegistrationName, + TransactionHandlerType, +} from "@medusajs/utils"; +import { + StepResponse, + createStep +} from "@medusajs/workflows-sdk"; + +type SetStepFailureStepInput = { + transactionId: string +}; + +export const setStepFailureStep = createStep( + "set-step-success-step", + async function ( + { transactionId }: SetStepFailureStepInput, + { container } + ) { + const workflowEngineService = container.resolve( + ModuleRegistrationName.WORKFLOW_ENGINE + ); + + await workflowEngineService.setStepFailure({ + idempotencyKey: { + action: TransactionHandlerType.INVOKE, + transactionId, + stepId: "step-2", + workflowId: "hello-world", + }, + stepResponse: new StepResponse("Failed!"), + options: { + container, + }, + }); + } +); +``` + +You use this step in another workflow that changes the long-running workflow's status. + +After change the async step's status to failed, the workflow execution fails and the compensation functions of previous steps are executed. + +--- + ## Access Long-Running Workflow Status and Result -To access the status and result of a long-running workflow, use the workflow engine registered in the Medusa Container. The workflow engine provides methods to access and subscribe to workflow executions. +To access the status and result of a long-running workflow, use the workflow engine's main service' `subscribe` and `unsubscribe` methods. For example: @@ -97,7 +287,7 @@ import { ModuleRegistrationName } from "@medusajs/utils" export async function GET(req: MedusaRequest, res: MedusaResponse) { const { transaction, result } = await myWorkflow(req.scope).run() - const workflowEngineModuleService = req.scope.resolve< + const workflowEngineService = req.scope.resolve< IWorkflowEngineService >( ModuleRegistrationName.WORKFLOW_ENGINE @@ -109,13 +299,13 @@ export async function GET(req: MedusaRequest, res: MedusaResponse) { subscriberId: "hello-world-subscriber", } - await workflowEngineModuleService.subscribe({ + await workflowEngineService.subscribe({ ...subscriptionOptions, subscriber: async (data) => { if (data.eventType === "onFinish") { console.log("Finished execution", data.result) // unsubscribe - await workflowEngineModuleService.unsubscribe({ + await workflowEngineService.unsubscribe({ ...subscriptionOptions, subscriberOrId: subscriptionOptions.subscriberId, }) @@ -158,4 +348,4 @@ The `subscribe` method accepts an object having three properties: Once the workflow execution finishes, the subscriber function is executed with the `eventType` of the received parameter set to `onFinish`. The workflow’s output is set in the `result` property of the parameter. -You can unsubscribe from the workflow using the workflow engine's `unsubscribe` method, which requires the same object parameter as the `subscribe` method. \ No newline at end of file +You can unsubscribe from the workflow using the workflow engine's `unsubscribe` method, which requires the same object parameter as the `subscribe` method. diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index 1c2bf0ae77..0e14c13afc 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -42,13 +42,13 @@ export const generatedEditDates = { "app/advanced-development/workflows/conditions/page.mdx": "2024-07-31T17:01:33+03:00", "app/advanced-development/modules/module-link-directions/page.mdx": "2024-07-24T09:16:01+02:00", "app/advanced-development/admin/page.mdx": "2024-05-29T13:50:19+03:00", - "app/advanced-development/workflows/long-running-workflow/page.mdx": "2024-07-31T17:01:33+03:00", + "app/advanced-development/workflows/long-running-workflow/page.mdx": "2024-09-11T11:29:58.203Z", "app/advanced-development/workflows/constructor-constraints/page.mdx": "2024-07-17T13:19:51+01:00", "app/advanced-development/data-models/write-migration/page.mdx": "2024-07-15T17:46:10+02:00", "app/advanced-development/data-models/manage-relationships/page.mdx": "2024-09-10T11:39:51.167Z", "app/advanced-development/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00", "app/advanced-development/modules/options/page.mdx": "2024-08-05T07:23:49+00:00", - "app/advanced-development/data-models/relationships/page.mdx": "2024-08-15T16:30:00+03:00", + "app/advanced-development/data-models/relationships/page.mdx": "2024-09-11T11:28:55.494Z", "app/advanced-development/workflows/compensation-function/page.mdx": "2024-07-31T17:01:33+03:00", "app/advanced-development/modules/service-factory/page.mdx": "2024-07-26T14:40:56+00:00", "app/advanced-development/data-models/primary-key/page.mdx": "2024-07-02T12:34:44+03:00",