diff --git a/packages/core/types/src/file/providers/s3.ts b/packages/core/types/src/file/providers/s3.ts index 750bdadcdb..afd1aff5b6 100644 --- a/packages/core/types/src/file/providers/s3.ts +++ b/packages/core/types/src/file/providers/s3.ts @@ -1,7 +1,8 @@ export interface S3FileServiceOptions { file_url: string - access_key_id: string - secret_access_key: string + access_key_id?: string + secret_access_key?: string + authentication_method?: "access-key" | "s3-iam-role" region: string bucket: string prefix?: string diff --git a/packages/modules/providers/file-s3/package.json b/packages/modules/providers/file-s3/package.json index 80d3e8db9b..f981d95789 100644 --- a/packages/modules/providers/file-s3/package.json +++ b/packages/modules/providers/file-s3/package.json @@ -21,7 +21,7 @@ "license": "MIT", "scripts": { "test": "jest --passWithNoTests src", - "test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.spec.ts", + "test:integration": "jest --forceExit -- integration-tests/__tests__/*.spec.ts", "build": "rimraf dist && tsc --build ./tsconfig.json", "watch": "tsc --watch" }, diff --git a/packages/modules/providers/file-s3/src/services/s3-file.ts b/packages/modules/providers/file-s3/src/services/s3-file.ts index 152ca60b6f..2f4dbf387b 100644 --- a/packages/modules/providers/file-s3/src/services/s3-file.ts +++ b/packages/modules/providers/file-s3/src/services/s3-file.ts @@ -23,10 +23,10 @@ type InjectedDependencies = { } interface S3FileServiceConfig { - // TODO: We probably don't need this as either the service should return it or we should be able to calculate it. fileUrl: string - accessKeyId: string - secretAccessKey: string + accessKeyId?: string + secretAccessKey?: string + authenticationMethod?: "access-key" | "s3-iam-role" region: string bucket: string prefix?: string @@ -36,7 +36,6 @@ interface S3FileServiceConfig { additionalClientConfig?: Record } -// FUTURE: At one point we will probably need to support authenticating with IAM roles instead. export class S3FileService extends AbstractFileProviderService { static identifier = "s3" protected config_: S3FileServiceConfig @@ -46,10 +45,23 @@ export class S3FileService extends AbstractFileProviderService { constructor({ logger }: InjectedDependencies, options: S3FileServiceOptions) { super() + const authenticationMethod = options.authentication_method ?? "access-key" + + if ( + authenticationMethod === "access-key" && + (!options.access_key_id || !options.secret_access_key) + ) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Access key ID and secret access key are required when using access key authentication` + ) + } + this.config_ = { fileUrl: options.file_url, accessKeyId: options.access_key_id, secretAccessKey: options.secret_access_key, + authenticationMethod: authenticationMethod, region: options.region, bucket: options.bucket, prefix: options.prefix ?? "", @@ -63,11 +75,17 @@ export class S3FileService extends AbstractFileProviderService { } protected getClient() { + // If none is provided, the SDK will use the default credentials provider chain, see https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-envvars.html + const credentials = + this.config_.authenticationMethod === "access-key" + ? { + accessKeyId: this.config_.accessKeyId!, + secretAccessKey: this.config_.secretAccessKey!, + } + : undefined + const config: S3ClientConfigType = { - credentials: { - accessKeyId: this.config_.accessKeyId, - secretAccessKey: this.config_.secretAccessKey, - }, + credentials, region: this.config_.region, endpoint: this.config_.endpoint, ...this.config_.additionalClientConfig, diff --git a/www/apps/book/app/learn/fundamentals/api-routes/validation/page.mdx b/www/apps/book/app/learn/fundamentals/api-routes/validation/page.mdx index da66e40201..35a7745d0c 100644 --- a/www/apps/book/app/learn/fundamentals/api-routes/validation/page.mdx +++ b/www/apps/book/app/learn/fundamentals/api-routes/validation/page.mdx @@ -185,7 +185,7 @@ Add the `validateAndTransformQuery` middleware to the API route in the file `src ```ts title="src/api/middlewares.ts" import { validateAndTransformQuery, - defineMiddlewares + defineMiddlewares, } from "@medusajs/framework/http" import { PostStoreCustomSchema } from "./custom/validators" diff --git a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx index f0589a27ff..7895b5c9d1 100644 --- a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx +++ b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx @@ -300,7 +300,7 @@ The first step is to use the `validateAndTransformQuery` middleware on the `GET` ```ts title="src/api/middlewares.ts" import { validateAndTransformQuery, - defineMiddlewares + defineMiddlewares, } from "@medusajs/framework/http" import { createFindParams } from "@medusajs/medusa/api/utils/validators" diff --git a/www/apps/book/app/learn/fundamentals/modules/isolation/page.mdx b/www/apps/book/app/learn/fundamentals/modules/isolation/page.mdx index f4601d1792..1e541aa691 100644 --- a/www/apps/book/app/learn/fundamentals/modules/isolation/page.mdx +++ b/www/apps/book/app/learn/fundamentals/modules/isolation/page.mdx @@ -105,7 +105,7 @@ export const syncBrandsWorkflow = createWorkflow( () => { const brands = retrieveBrandsStep() - updateBrandsInCmsStep({ brands }) + createBrandsInCmsStep({ brands }) } ) ``` diff --git a/www/apps/book/app/learn/fundamentals/workflows/conditions/page.mdx b/www/apps/book/app/learn/fundamentals/workflows/conditions/page.mdx index a99b0fb186..8c60aa3caf 100644 --- a/www/apps/book/app/learn/fundamentals/workflows/conditions/page.mdx +++ b/www/apps/book/app/learn/fundamentals/workflows/conditions/page.mdx @@ -8,19 +8,17 @@ In this chapter, you'll learn how to execute an action based on a condition in a ## Why If-Conditions Aren't Allowed in Workflows? -Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps. - -At that point, variables in the workflow don't have any values. They only do when you execute the workflow. +Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps. At that point, variables in the workflow don't have any values. They only do when you execute the workflow. So, you can't use an if-condition that checks a variable's value, as the condition will be evaluated when Medusa creates the internal representation of the workflow, rather than during execution. -Instead, use when-then from the Workflows SDK. +Instead, use when-then from the Workflows SDK. It allows you to perform steps in a workflow only if a condition that you specify is satisified. --- -## What is the When-Then Utility? +## How to use When-Then? -when-then from the Workflows SDK executes an action if a condition is satisfied. The `when` function accepts as a parameter a function that returns a boolean value, and the `then` function is chained to `when`. `then` accepts as a parameter a function that's executed if `when`'s parameter function returns a `true` value. +The Workflows SDK provides a `when` function that is used to check whether a condition is true. You chain a `then` function to `when` that specifies the steps to execute if the condition in `when` is satisfied. For example: @@ -77,4 +75,101 @@ In this code snippet, you execute the `isActiveStep` only if the `input.is_activ To specify the action to perform if the condition is satisfied, chain a `then` function to `when` and pass it a callback function. -The callback function is only executed if `when`'s second parameter function returns a `true` value. \ No newline at end of file +The callback function is only executed if `when`'s second parameter function returns a `true` value. + +--- + +## Implementing If-Else with When-Then + +when-then doesn't support if-else conditions. Instead, use two `when-then` conditions in your workflow. + +For example: + +export const ifElseHighlights = [ + ["7", "when", "This when-then block acts as an if condition."], + ["16", "when", "This when-then block acts as an else condiiton."] +] + +```ts highlights={ifElseHighlights} +const workflow = createWorkflow( + "workflow", + function (input: { + is_active: boolean + }) { + + const isActiveResult = when( + input, + (input) => { + return input.is_active + } + ).then(() => { + return isActiveStep() + }) + + const notIsActiveResult = when( + input, + (input) => { + return input.is_active + } + ).then(() => { + return notIsActiveStep() + }) + + // ... + } +) +``` + +In the above workflow, you use two `when-then` blocks. The first one performs a step if `input.is_active` is `true`, and the second performs a step if `input.is_active` is `false`, acting as an else condition. + +--- + +## Specify Name for When-Then + +Internally, `when-then` blocks have a unique name similar to a step. When you return a step's result in a `when-then` block, the block's name is derived from the step's name. For example: + +```ts +const isActiveResult = when( + input, + (input) => { + return input.is_active + } +).then(() => { + return isActiveStep() +}) +``` + +This `when-then` block's internal name will be `when-then-is-active`, where `is-active` is the step's name. + +However, if you need to return in your `when-then` block something other than a step's result, you need to specify a unique step name for that block. Otherwise, Medusa will generate a random name for it which can cause unexpected errors in production. + +You pass a name for `when-then` as a first parameter of `when`, whose signature can accept three parameters in this case. For example: + +export const nameHighlights = [ + ["2", `"check-is-active"`, "The when-then block's name."], + ["10", "return", "`then` returns a value other than the step's result."] +] + +```ts highlights={nameHighlights} +const { isActive } = when( + "check-is-active", + input, + (input) => { + return input.is_active + } +).then(() => { + const isActive = isActiveStep() + + return { + isActive, + } +}) +``` + +Since `then` returns a value different than the step's result, you pass to the `when` function the following parameters: + +1. A unique name to be assigned to the `when-then` block. +2. Either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter. +3. A function that returns a boolean indicating whether to execute the action in `then`. + +The second and third parameters are the same as the parameters you previously passed to `when`. diff --git a/www/apps/book/app/learn/fundamentals/workflows/constructor-constraints/page.mdx b/www/apps/book/app/learn/fundamentals/workflows/constructor-constraints/page.mdx index b53d6e7054..b8ceb3b31b 100644 --- a/www/apps/book/app/learn/fundamentals/workflows/constructor-constraints/page.mdx +++ b/www/apps/book/app/learn/fundamentals/workflows/constructor-constraints/page.mdx @@ -157,6 +157,8 @@ const myWorkflow = createWorkflow( }) ``` +You can also pair multiple `when-then` blocks to implement an `if-else` condition as explained in [this chapter](../conditions/page.mdx). + ### No Conditional Operators You can't use conditional operators in a workflow, such as `??` or `||`. diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index 91911b8aa6..afcf9fc0af 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -32,11 +32,11 @@ export const generatedEditDates = { "app/learn/fundamentals/data-models/default-properties/page.mdx": "2024-10-21T13:30:21.368Z", "app/learn/fundamentals/workflows/advanced-example/page.mdx": "2024-09-11T10:46:59.975Z", "app/learn/fundamentals/events-and-subscribers/emit-event/page.mdx": "2024-11-25T16:19:32.168Z", - "app/learn/fundamentals/workflows/conditions/page.mdx": "2024-12-09T15:55:51.565Z", + "app/learn/fundamentals/workflows/conditions/page.mdx": "2024-12-11T08:44:00.239Z", "app/learn/fundamentals/modules/module-link-directions/page.mdx": "2024-07-24T09:16:01+02:00", "app/learn/fundamentals/admin/page.mdx": "2024-10-23T07:08:55.898Z", "app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2024-12-04T07:37:59.822Z", - "app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2024-12-09T14:43:35.160Z", + "app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2024-12-11T08:36:08.282Z", "app/learn/fundamentals/data-models/write-migration/page.mdx": "2024-11-11T15:27:59.794Z", "app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2024-10-28T04:22:21.328Z", "app/learn/fundamentals/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00", diff --git a/www/apps/resources/app/commerce-modules/cart/extend/page.mdx b/www/apps/resources/app/commerce-modules/cart/extend/page.mdx index 204d3e302e..d4520101f8 100644 --- a/www/apps/resources/app/commerce-modules/cart/extend/page.mdx +++ b/www/apps/resources/app/commerce-modules/cart/extend/page.mdx @@ -529,12 +529,15 @@ In the workflow, you retrieve the cart's linked `Custom` record using Query. Next, replace the `TODO` with the following: ```ts title="src/workflows/update-custom-from-cart/index.ts" -const created = when({ - input, - carts, -}, (data) => - !data.carts[0].custom && - data.input.additional_data?.custom_name?.length > 0 +const created = when( + "create-cart-custom-link", + { + input, + carts, + }, + (data) => + !data.carts[0].custom && + data.input.additional_data?.custom_name?.length > 0 ) .then(() => { const custom = createCustomStep({ @@ -563,14 +566,16 @@ To create the `Custom` record, you use the `createCustomStep` you created in an Next, replace the new `TODO` with the following: ```ts title="src/workflows/update-custom-from-cart/index.ts" -const deleted = when({ - input, - carts, -}, (data) => - data.carts[0].custom && ( - data.input.additional_data?.custom_name === null || - data.input.additional_data?.custom_name.length === 0 - ) +const deleted = when( + "delete-cart-custom-link", + { + input, + carts, + }, (data) => + data.carts[0].custom && ( + data.input.additional_data?.custom_name === null || + data.input.additional_data?.custom_name.length === 0 + ) ) .then(() => { deleteCustomStep({ @@ -599,12 +604,10 @@ const updated = when({ carts, }, (data) => data.carts[0].custom && data.input.additional_data?.custom_name?.length > 0) .then(() => { - const custom = updateCustomStep({ + return updateCustomStep({ id: carts[0].custom.id, custom_name: input.additional_data.custom_name, }) - - return custom }) return new WorkflowResponse({ diff --git a/www/apps/resources/app/commerce-modules/customer/extend/page.mdx b/www/apps/resources/app/commerce-modules/customer/extend/page.mdx index 4728c23514..3a4b7941f5 100644 --- a/www/apps/resources/app/commerce-modules/customer/extend/page.mdx +++ b/www/apps/resources/app/commerce-modules/customer/extend/page.mdx @@ -541,12 +541,14 @@ In the workflow, you retrieve the customer's linked `Custom` record using Query. Next, replace the `TODO` with the following: ```ts title="src/workflows/update-custom-from-customer/index.ts" -const created = when({ - input, - customers, -}, (data) => - !data.customers[0].custom && - data.input.additional_data?.custom_name?.length > 0 +const created = when( + "create-customer-custom-link", + { + input, + customers, + }, (data) => + !data.customers[0].custom && + data.input.additional_data?.custom_name?.length > 0 ) .then(() => { const custom = createCustomStep({ @@ -575,14 +577,16 @@ To create the `Custom` record, you use the `createCustomStep` you created in an Next, replace the new `TODO` with the following: ```ts title="src/workflows/update-custom-from-customer/index.ts" -const deleted = when({ - input, - customers, -}, (data) => - data.customers[0].custom && ( - data.input.additional_data?.custom_name === null || - data.input.additional_data?.custom_name.length === 0 - ) +const deleted = when( + "delete-customer-custom-link", + { + input, + customers, + }, (data) => + data.customers[0].custom && ( + data.input.additional_data?.custom_name === null || + data.input.additional_data?.custom_name.length === 0 + ) ) .then(() => { deleteCustomStep({ @@ -611,12 +615,10 @@ const updated = when({ customers, }, (data) => data.customers[0].custom && data.input.additional_data?.custom_name?.length > 0) .then(() => { - const custom = updateCustomStep({ + return updateCustomStep({ id: customers[0].custom.id, custom_name: input.additional_data.custom_name, }) - - return custom }) return new WorkflowResponse({ diff --git a/www/apps/resources/app/commerce-modules/product/extend/page.mdx b/www/apps/resources/app/commerce-modules/product/extend/page.mdx index 7c2e68b045..553b800ca2 100644 --- a/www/apps/resources/app/commerce-modules/product/extend/page.mdx +++ b/www/apps/resources/app/commerce-modules/product/extend/page.mdx @@ -547,12 +547,14 @@ In the workflow, you retrieve the product's linked `Custom` record using Query. Next, replace the `TODO` with the following: ```ts title="src/workflows/update-custom-from-product/index.ts" -const created = when({ - input, - products, -}, (data) => - !data.products[0].custom && - data.input.additional_data?.custom_name?.length > 0 +const created = when( + "create-product-custom-link", + { + input, + products, + }, (data) => + !data.products[0].custom && + data.input.additional_data?.custom_name?.length > 0 ) .then(() => { const custom = createCustomStep({ @@ -581,14 +583,16 @@ To create the `Custom` record, you use the `createCustomStep` you created in an Next, replace the new `TODO` with the following: ```ts title="src/workflows/update-custom-from-product/index.ts" -const deleted = when({ - input, - products, -}, (data) => - data.products[0].custom && ( - data.input.additional_data?.custom_name === null || - data.input.additional_data?.custom_name.length === 0 - ) +const deleted = when( + "delete-product-custom-link", + { + input, + products, + }, (data) => + data.products[0].custom && ( + data.input.additional_data?.custom_name === null || + data.input.additional_data?.custom_name.length === 0 + ) ) .then(() => { deleteCustomStep({ @@ -617,12 +621,10 @@ const updated = when({ products, }, (data) => data.products[0].custom && data.input.additional_data?.custom_name?.length > 0) .then(() => { - const custom = updateCustomStep({ + return updateCustomStep({ id: products[0].custom.id, custom_name: input.additional_data.custom_name, }) - - return custom }) return new WorkflowResponse({ diff --git a/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx b/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx index 8f888d5eee..2b0e8bf2d6 100644 --- a/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx +++ b/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx @@ -553,12 +553,14 @@ In the workflow, you retrieve the promotion's linked `Custom` record using Query Next, replace the `TODO` with the following: ```ts title="src/workflows/update-custom-from-promotion/index.ts" -const created = when({ - input, - promotions, -}, (data) => - !data.promotions[0].custom && - data.input.additional_data?.custom_name?.length > 0 +const created = when( + "create-promotion-custom-link", + { + input, + promotions, + }, (data) => + !data.promotions[0].custom && + data.input.additional_data?.custom_name?.length > 0 ) .then(() => { const custom = createCustomStep({ @@ -587,14 +589,16 @@ To create the `Custom` record, you use the `createCustomStep` you created in an Next, replace the new `TODO` with the following: ```ts title="src/workflows/update-custom-from-promotion/index.ts" -const deleted = when({ - input, - promotions, -}, (data) => - data.promotions[0].custom && ( - data.input.additional_data?.custom_name === null || - data.input.additional_data?.custom_name.length === 0 - ) +const deleted = when( + "delete-promotion-custom-link", + { + input, + promotions, + }, (data) => + data.promotions[0].custom && ( + data.input.additional_data?.custom_name === null || + data.input.additional_data?.custom_name.length === 0 + ) ) .then(() => { deleteCustomStep({ @@ -623,12 +627,10 @@ const updated = when({ promotions, }, (data) => data.promotions[0].custom && data.input.additional_data?.custom_name?.length > 0) .then(() => { - const custom = updateCustomStep({ + return updateCustomStep({ id: promotions[0].custom.id, custom_name: input.additional_data.custom_name, }) - - return custom }) return new WorkflowResponse({ diff --git a/www/apps/resources/app/examples/page.mdx b/www/apps/resources/app/examples/page.mdx index 5641a71fc1..b496392427 100644 --- a/www/apps/resources/app/examples/page.mdx +++ b/www/apps/resources/app/examples/page.mdx @@ -334,7 +334,7 @@ export const PostStoreCustomSchema = z.object({ ```ts title="src/api/middlewares.ts" highlights={[["13", "validateAndTransformBody"]]} import { validateAndTransformBody, - defineMiddlewares + defineMiddlewares, } from "@medusajs/framework/http" import { PostStoreCustomSchema } from "./custom/validators" @@ -629,7 +629,7 @@ import type { MedusaNextFunction, MedusaRequest, MedusaResponse, - defineMiddlewares + defineMiddlewares, } from "@medusajs/framework/http" import { ConfigModule } from "@medusajs/framework/types" import { parseCorsOrigins } from "@medusajs/framework/utils" @@ -2296,8 +2296,7 @@ const workflow = createWorkflow( return input.is_active } ).then(() => { - const stepResult = isActiveStep() - return stepResult + return isActiveStep() }) // executed without condition diff --git a/www/apps/resources/app/integrations/guides/sanity/page.mdx b/www/apps/resources/app/integrations/guides/sanity/page.mdx index 1c78208e15..02839c4ec1 100644 --- a/www/apps/resources/app/integrations/guides/sanity/page.mdx +++ b/www/apps/resources/app/integrations/guides/sanity/page.mdx @@ -695,17 +695,17 @@ export const syncStep = createStep( const sanityModule: SanityModuleService = container.resolve(SANITY_MODULE) const query = container.resolve(ContainerRegistrationKeys.QUERY) - let total = 0; + const total = 0 const upsertMap: { before: any after: any }[] = [] - const batchSize = 200; - let hasMore = true; - let offset = 0; - let filters = input.product_ids ? { - id: input.product_ids + const batchSize = 200 + const hasMore = true + const offset = 0 + const filters = input.product_ids ? { + id: input.product_ids, } : {} while (hasMore) { @@ -772,16 +772,16 @@ while (hasMore) { const after = await sanityModule.upsertSyncDocument( "product", prod as ProductDTO - ); + ) upsertMap.push({ // @ts-ignore before: prod.sanity_product, - after + after, }) return after - }), + }) ) } catch (e) { return StepResponse.permanentFailure( @@ -805,7 +805,7 @@ You also wrap the `promiseAll` function within a try-catch block. In the catch b Finally, after the `while` loop and at the end of the step, add the following return statement: ```ts title="src/workflows/sanity-sync-products/steps/sync.ts" -return new StepResponse({ total }, upsertMap); +return new StepResponse({ total }, upsertMap) ``` If no errors occur, the step returns an instance of `StepResponse`, which must be returned by any step. It accepts as a first parameter the data to return to the workflow that executed this step. diff --git a/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx b/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx index 08929733d6..4be2c92ea3 100644 --- a/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx +++ b/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx @@ -440,7 +440,7 @@ export const validateVariantOutOfStockStep = createStep( const query = container.resolve("query") const availability = await getVariantAvailability(query, { variant_ids: [variant_id], - sales_channel_id + sales_channel_id, }) if (availability[variant_id].availability > 0) { @@ -599,7 +599,7 @@ export const subscriptionWorkflow1Highlights = [ ["16", "createWorkflow", "Create a workflow."], ["23", "transform", "Set the customer ID to an empty string if not provided."], ["28", "when", "If email is not set, try to retrieve customer by its ID."], - ["44", "transform", "Set the email either to the one in the input or the specified customer's email."], + ["48", "transform", "Set the email either to the one in the input or the specified customer's email."], ] ```ts title="src/workflows/create-restock-subscription/index.ts" highlights={subscriptionWorkflow1Highlights} @@ -623,24 +623,28 @@ export const createRestockSubscriptionWorkflow = createWorkflow( ({ variant_id, sales_channel_id, - customer + customer, }: CreateRestockSubscriptionWorkflowInput) => { const customerId = transform({ - customer + customer, }, (data) => { return data.customer.customer_id || "" }) - const retrievedCustomer = when({ customer }, ({ customer }) => { - return !customer.email - }).then(() => { + const retrievedCustomer = when( + "retrieve-customer-by-id", + { customer }, + ({ customer }) => { + return !customer.email + } + ).then(() => { // @ts-ignore const { data } = useQueryGraphStep({ entity: "customer", fields: ["email"], filters: { id: customerId }, options: { - throwIfKeyNotFound: true - } + throwIfKeyNotFound: true, + }, }).config({ name: "retrieve-customer" }) return data @@ -648,7 +652,7 @@ export const createRestockSubscriptionWorkflow = createWorkflow( const email = transform({ retrievedCustomer, - customer + customer, }, (data) => { return data.customer?.email ?? data.retrievedCustomer?.[0].email }) @@ -688,7 +692,7 @@ export const subscriptionWorkflow2Highlights = [ ```ts title="src/workflows/create-restock-subscription/index.ts" highlights={subscriptionWorkflow2Highlights} validateVariantOutOfStockStep({ variant_id, - sales_channel_id + sales_channel_id, }) // @ts-ignore @@ -698,8 +702,8 @@ const { data: restockSubscriptions } = useQueryGraphStep({ filters: { email, variant_id, - sales_channel_id - } + sales_channel_id, + }, }).config({ name: "retrieve-subscriptions" }) when({ restockSubscriptions }, ({ restockSubscriptions }) => { @@ -710,7 +714,7 @@ when({ restockSubscriptions }, ({ restockSubscriptions }) => { variant_id, sales_channel_id, email, - customer_id: customer.customer_id + customer_id: customer.customer_id, }) }) @@ -720,7 +724,7 @@ when({ restockSubscriptions }, ({ restockSubscriptions }) => { .then(() => { updateRestockSubscriptionStep({ id: restockSubscriptions[0].id, - customer_id: customer.customer_id + customer_id: customer.customer_id, }) }) @@ -731,8 +735,8 @@ const { data: restockSubscription } = useQueryGraphStep({ filters: { email, variant_id, - sales_channel_id - } + sales_channel_id, + }, }).config({ name: "retrieve-restock-subscription" }) return new WorkflowResponse( @@ -1116,12 +1120,12 @@ Before adding the step that does this, you'll add a method in the `RestockModule ```ts title="src/modules/restock/service.ts" // other imports... -import { InjectManager, MedusaContext } from "@medusajs/framework/utils"; +import { InjectManager, MedusaContext } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex"; +import { EntityManager } from "@mikro-orm/knex" class RestockModuleService extends MedusaService({ - RestockSubscription + RestockSubscription, }) { // ... @InjectManager() @@ -1147,9 +1151,9 @@ You'll use this method in the step. To create the step, create the file `src/wor ![Directory structure of the Medusa application after adding the step.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733399774/Medusa%20Resources/restock-dir-overview-22_kzchmm.jpg) ```ts title="src/workflows/send-restock-notifications/steps/get-distinct-subscriptions.ts" -import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"; -import RestockModuleService from "../../../modules/restock/service"; -import { RESTOCK_MODULE } from "../../../modules/restock"; +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" +import RestockModuleService from "../../../modules/restock/service" +import { RESTOCK_MODULE } from "../../../modules/restock" export const getDistinctSubscriptionsStep = createStep( "get-distinct-subscriptions", @@ -1194,7 +1198,7 @@ export const getRestockedStep = createStep( input.map(async (restockSubscription) => { const variantAvailability = await getVariantAvailability(query, { variant_ids: [restockSubscription.variant_id], - sales_channel_id: restockSubscription.sales_channel_id + sales_channel_id: restockSubscription.sales_channel_id, }) if (variantAvailability[restockSubscription.variant_id].availability > 0) { @@ -1318,12 +1322,12 @@ Create the file `src/workflows/send-restock-notifications/index.ts` with the fol ![The directory structure of the Medusa application after adding the workflow.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733234507/Medusa%20Resources/restock-dir-overview-20_mcqkkx.jpg) ```ts title="src/workflows/send-restock-notifications/index.ts" -import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk"; -import { useQueryGraphStep } from "@medusajs/medusa/core-flows"; -import { getRestockedStep } from "./steps/get-restocked"; -import { sendRestockNotificationStep } from "./steps/send-restock-notification"; -import { deleteRestockSubscriptionStep } from "./steps/delete-restock-subscriptions"; -import { getDistinctSubscriptionsStep } from "./steps/get-distinct-subscriptions"; +import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +import { getRestockedStep } from "./steps/get-restocked" +import { sendRestockNotificationStep } from "./steps/send-restock-notification" +import { deleteRestockSubscriptionStep } from "./steps/delete-restock-subscriptions" +import { getDistinctSubscriptionsStep } from "./steps/get-distinct-subscriptions" export const sendRestockNotificationsWorkflow = createWorkflow( "send-restock-notifications", @@ -1334,11 +1338,11 @@ export const sendRestockNotificationsWorkflow = createWorkflow( const restockedSubscriptions = getRestockedStep(subscriptions) const { variant_ids, sales_channel_ids } = transform({ - restockedSubscriptions + restockedSubscriptions, }, (data) => { const filters: Record = { variant_ids: [], - sales_channel_ids: [] + sales_channel_ids: [], } data.restockedSubscriptions.map((subscription) => { filters.variant_ids.push(subscription.variant_id) @@ -1354,8 +1358,8 @@ export const sendRestockNotificationsWorkflow = createWorkflow( fields: ["*", "product_variant.*"], filters: { variant_id: variant_ids, - sales_channel_id: sales_channel_ids - } + sales_channel_id: sales_channel_ids, + }, }) // @ts-ignore @@ -1365,7 +1369,7 @@ export const sendRestockNotificationsWorkflow = createWorkflow( deleteRestockSubscriptionStep(restockedSubscriptionsWithEmails) return new WorkflowResponse({ - subscriptions: restockedSubscriptionsWithEmails + subscriptions: restockedSubscriptionsWithEmails, }) } ) diff --git a/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx b/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx index eb6e09bc82..ec9dab37d9 100644 --- a/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx +++ b/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx @@ -1705,11 +1705,11 @@ export const createDpoWorkflowHighlights = [ ["27", "completeCartWorkflow", "Create an order for the cart."], ["33", "useQueryGraphStep", "Retrieve the order's items and their associated variants and linked digital products."], ["57", "when", "Check whether the order has any digital products."], - ["60", "then", "Perform the callback function if an order has digital products."], - ["63", "createDigitalProductOrderStep", "Create the digital product order."], - ["67", "createRemoteLinkStep", "Link the digital product order to the Medusa order."], - ["76", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."], - ["90", "emitEventStep", "Emit the `digital_product_order.created` event."] + ["63", "then", "Perform the callback function if an order has digital products."], + ["66", "createDigitalProductOrderStep", "Create the digital product order."], + ["70", "createRemoteLinkStep", "Link the digital product order to the Medusa order."], + ["79", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."], + ["93", "emitEventStep", "Emit the `digital_product_order.created` event."] ] ```ts title="src/workflows/create-digital-product-order/index.ts" highlights={createDpoWorkflowHighlights} collapsibleLines="1-17" expandMoreLabel="Show Imports" @@ -1769,10 +1769,13 @@ const createDigitalProductOrderWorkflow = createWorkflow( } ) - const digital_product_order = when(itemsWithDigitalProducts, (itemsWithDigitalProducts) => { - return itemsWithDigitalProducts.length - }) - .then(() => { + const digital_product_order = when( + "create-digital-product-order-condition", + itemsWithDigitalProducts, + (itemsWithDigitalProducts) => { + return itemsWithDigitalProducts.length + } + ).then(() => { const { digital_product_order, } = createDigitalProductOrderStep({ diff --git a/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx b/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx new file mode 100644 index 0000000000..0de9db5ec8 --- /dev/null +++ b/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx @@ -0,0 +1,24 @@ +export const metadata = { + title: `Workflow Errors`, +} + +# {metadata.title} + +## When-Then Error: Handler for action X Not Found + +The following error may occur in production if you use a `when-then` block in your workflow: + +```plain +custom-workflow:when-then-01JE8Z0M1FXSE2NCK1G04S0RR2:invoke - Handler for action \"when-then-01JE8Z0M1FXSE2NCK1G04S0RR2\" not found... +``` + +This occurs if the `when-then` block doesn't return a step's result and doesn't have a name specified. You can resolve it by passing a name as a first parameter of `when`: + +```ts +const result = when( + "custom-when-condition" + // ... rest of the parameters +) +``` + +Learn more about passing a name for `when-then` in [this documentation](!docs!/learn/fundamentals/workflows/conditions#specify-name-for-when-then) \ 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 0e52ad5456..64856fb17c 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -2180,15 +2180,15 @@ export const generatedEditDates = { "app/commerce-modules/auth/reset-password/page.mdx": "2024-11-27T13:33:55.940Z", "app/storefront-development/customers/reset-password/page.mdx": "2024-09-25T10:21:46.647Z", "app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2024-10-08T08:05:36.596Z", - "app/commerce-modules/cart/extend/page.mdx": "2024-12-09T16:11:39.857Z", + "app/commerce-modules/cart/extend/page.mdx": "2024-12-11T09:05:37.041Z", "app/commerce-modules/cart/links-to-other-modules/page.mdx": "2024-10-08T08:22:35.190Z", - "app/commerce-modules/customer/extend/page.mdx": "2024-12-09T16:15:01.163Z", + "app/commerce-modules/customer/extend/page.mdx": "2024-12-11T09:05:35.368Z", "app/commerce-modules/fulfillment/links-to-other-modules/page.mdx": "2024-10-08T14:58:24.935Z", "app/commerce-modules/inventory/links-to-other-modules/page.mdx": "2024-10-08T15:18:30.109Z", "app/commerce-modules/pricing/links-to-other-modules/page.mdx": "2024-10-09T13:51:49.986Z", - "app/commerce-modules/product/extend/page.mdx": "2024-12-09T16:15:01.163Z", + "app/commerce-modules/product/extend/page.mdx": "2024-12-11T09:07:25.252Z", "app/commerce-modules/product/links-to-other-modules/page.mdx": "2024-10-09T14:14:09.401Z", - "app/commerce-modules/promotion/extend/page.mdx": "2024-12-09T16:19:19.364Z", + "app/commerce-modules/promotion/extend/page.mdx": "2024-12-11T09:07:24.137Z", "app/commerce-modules/promotion/links-to-other-modules/page.mdx": "2024-10-09T14:51:37.194Z", "app/commerce-modules/order/edit/page.mdx": "2024-10-09T08:50:05.334Z", "app/commerce-modules/order/links-to-other-modules/page.mdx": "2024-10-09T11:23:05.488Z", @@ -2246,7 +2246,7 @@ export const generatedEditDates = { "app/commerce-modules/sales-channel/links-to-other-modules/page.mdx": "2024-10-15T14:25:29.097Z", "app/commerce-modules/stock-location/links-to-other-modules/page.mdx": "2024-10-15T14:33:11.483Z", "app/commerce-modules/store/links-to-other-modules/page.mdx": "2024-06-26T07:19:49.931Z", - "app/examples/page.mdx": "2024-12-09T16:19:18.598Z", + "app/examples/page.mdx": "2024-12-11T09:07:47.589Z", "app/medusa-cli/commands/build/page.mdx": "2024-11-11T11:00:49.665Z", "app/js-sdk/page.mdx": "2024-10-16T12:12:34.512Z", "references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.apiKey/page.mdx": "2024-12-09T13:21:58.136Z", @@ -5687,5 +5687,6 @@ export const generatedEditDates = { "references/modules/sales_channel_models/page.mdx": "2024-12-10T14:55:13.205Z", "references/types/DmlTypes/types/types.DmlTypes.KnownDataTypes/page.mdx": "2024-12-10T14:54:55.434Z", "references/types/DmlTypes/types/types.DmlTypes.RelationshipTypes/page.mdx": "2024-12-10T14:54:55.435Z", - "app/recipes/commerce-automation/restock-notification/page.mdx": "2024-12-10T14:15:39.178Z" + "app/recipes/commerce-automation/restock-notification/page.mdx": "2024-12-11T08:47:27.471Z", + "app/troubleshooting/workflow-errors/page.mdx": "2024-12-11T08:44:36.598Z" } \ No newline at end of file diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs index 10f2708757..7d43e5338f 100644 --- a/www/apps/resources/generated/files-map.mjs +++ b/www/apps/resources/generated/files-map.mjs @@ -1019,6 +1019,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/troubleshooting/s3/page.mdx", "pathname": "/troubleshooting/s3" }, + { + "filePath": "/www/apps/resources/app/troubleshooting/workflow-errors/page.mdx", + "pathname": "/troubleshooting/workflow-errors" + }, { "filePath": "/www/apps/resources/app/usage/page.mdx", "pathname": "/usage" diff --git a/www/apps/resources/generated/sidebar.mjs b/www/apps/resources/generated/sidebar.mjs index 39745a5434..40c94ead13 100644 --- a/www/apps/resources/generated/sidebar.mjs +++ b/www/apps/resources/generated/sidebar.mjs @@ -9309,6 +9309,14 @@ export const generatedSidebar = [ "path": "/troubleshooting/dist-imports", "title": "Importing from /dist", "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/troubleshooting/workflow-errors", + "title": "Workflow Errors", + "children": [] } ] }, diff --git a/www/apps/resources/sidebar.mjs b/www/apps/resources/sidebar.mjs index b148078d36..b236e431d2 100644 --- a/www/apps/resources/sidebar.mjs +++ b/www/apps/resources/sidebar.mjs @@ -2249,6 +2249,11 @@ export const sidebar = sidebarAttachHrefCommonOptions([ path: "/troubleshooting/dist-imports", title: "Importing from /dist", }, + { + type: "link", + path: "/troubleshooting/workflow-errors", + title: "Workflow Errors", + }, ], }, {