Merge branch 'develop' into docs/revise-recipe-examples

This commit is contained in:
Shahed Nasser
2024-12-11 12:41:02 +02:00
22 changed files with 328 additions and 155 deletions
@@ -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({
@@ -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({
@@ -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({
@@ -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({
+3 -4
View File
@@ -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
@@ -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.
@@ -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<string, string[]> = {
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,
})
}
)
@@ -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({
@@ -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)