From 0d83918348b055e1c43e48e5cc69a4916f85381f Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Tue, 21 Oct 2025 16:12:35 +0300 Subject: [PATCH] docs: fixes to type errors in guides (#13797) --- www/apps/book/public/llms-full.txt | 724 ++++++++++-------- .../order/transactions/page.mdx | 2 +- .../tutorials/agentic-commerce/page.mdx | 238 +++--- .../caching/concepts/page.mdx | 6 +- .../caching/guides/memcached/page.mdx | 46 +- .../infrastructure-modules/caching/page.mdx | 14 +- .../caching/providers/page.mdx | 14 +- .../caching/providers/redis/page.mdx | 2 +- .../integrations/guides/meilisearch/page.mdx | 5 +- .../app/integrations/guides/resend/page.mdx | 35 +- .../guides/storefront-returns/page.mdx | 6 +- .../restock-notification/page.mdx | 4 +- .../examples/standard/page.mdx | 152 ++-- .../examples/restaurant-delivery/page.mdx | 145 ++-- .../marketplace/examples/vendors/page.mdx | 34 +- .../subscriptions/examples/standard/page.mdx | 97 ++- .../guides/express-checkout/page.mdx | 2 +- .../production-optimizations/page.mdx | 62 +- www/apps/resources/generated/edit-dates.mjs | 16 +- 19 files changed, 922 insertions(+), 682 deletions(-) diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index dd345e4f1b..665b8d6e53 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -33177,7 +33177,7 @@ const { data: [order] } = await query.graph({ fields: ["*", "transactions.*"], filters: { id: "order_123", - } + }, }) const refundTransactions = order.transactions?.filter( @@ -41658,7 +41658,7 @@ const key = await cachingModuleService.computeKey(data) await cachingModuleService.set({ key, tags: ["Product:prod_123", "Product:list:*"], - data + data, }) ``` @@ -41711,7 +41711,7 @@ const key = await cachingModuleService.computeKey(data) await cachingModuleService.set({ key, tags: ["Product:list:*", "Product:prod_123"], - data + data, }) ``` @@ -41770,7 +41770,7 @@ const key = await cachingModuleService.computeKey(data) await cachingModuleService.set({ key, tags: ["Product:prod_123", "Product:list:*"], - data + data, }) ``` @@ -41916,14 +41916,14 @@ export default async function connection({ serverUrls = ["127.0.0.1:11211"], username, password, - options: clientOptions + options: clientOptions, } = options || {} try { logger.info("Connecting to Memcached...") // Create Memcached client - const client = memjs.Client.create(serverUrls.join(','), { + const client = memjs.Client.create(serverUrls.join(","), { username, password, ...clientOptions, @@ -42074,9 +42074,9 @@ class MemcachedCachingProviderService implements ICachingProviderService { return { data, compressed: false } } - const buffer = Buffer.from(data, 'utf8') + const buffer = Buffer.from(data, "utf8") const compressed = await deflateAsync(buffer) - const compressedData = compressed.toString('base64') + const compressedData = compressed.toString("base64") // Only use compression if it actually reduces size if (compressedData.length < data.length) { @@ -42094,9 +42094,9 @@ class MemcachedCachingProviderService implements ICachingProviderService { return data } - const buffer = Buffer.from(data, 'base64') + const buffer = Buffer.from(data, "base64") const decompressed = await inflateAsync(buffer) - return decompressed.toString('utf8') + return decompressed.toString("utf8") } } ``` @@ -42214,12 +42214,12 @@ class MemcachedCachingProviderService implements ICachingProviderService { // Compress data if enabled const { data: finalData, - compressed + compressed, } = await this.compressData(serializedData) // Batch operations for better performance const operations: Promise[] = [ - this.client.set(prefixedKey, finalData, setOptions) + this.client.set(prefixedKey, finalData, setOptions), ] // Always store options (including compression flag) to allow checking them later @@ -42287,9 +42287,9 @@ class MemcachedCachingProviderService implements ICachingProviderService { const storedTags = JSON.parse(keyTagsResult.value.toString()) // Batch all namespace checks for better performance - const tagKeys = Object.keys(storedTags).map(tag => this.TAG_PREFIX + tag) + const tagKeys = Object.keys(storedTags).map((tag) => this.TAG_PREFIX + tag) const tagResults = await Promise.all( - tagKeys.map(tagKey => this.client.get(tagKey)) + tagKeys.map((tagKey) => this.client.get(tagKey)) ) // Check if any tag namespace is missing or changed @@ -42339,7 +42339,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { } // Get all keys associated with each tag - const tagKeysOperations = tags.map(tag => { + const tagKeysOperations = tags.map((tag) => { const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` return this.client.get(tagKeysKey) }) @@ -42351,7 +42351,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { for (const result of tagKeysResults) { if (result.value) { const keys = JSON.parse(result.value.toString()) as string[] - keys.forEach(key => allKeys.add(key)) + keys.forEach((key) => allKeys.add(key)) } } @@ -42360,7 +42360,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { } // Get all cached data for the collected keys - const dataOperations = Array.from(allKeys).map(async key => { + const dataOperations = Array.from(allKeys).map(async (key) => { const prefixedKey = this.CACHE_PREFIX + key const result = await this.client.get(prefixedKey) @@ -42520,7 +42520,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { let keys: string[] = JSON.parse(tagKeysResult.value.toString()) as string[] // Remove the specified keys - keys = keys.filter(key => !keysToRemove.includes(key)) + keys = keys.filter((key) => !keysToRemove.includes(key)) if (keys.length === 0) { // If no keys left, delete the tag keys entry @@ -42558,7 +42558,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { const operations: Promise[] = [ this.client.delete(this.CACHE_PREFIX + key), this.client.delete(this.OPTIONS_PREFIX + key), - this.client.delete(keyTagsKey) + this.client.delete(keyTagsKey), ] // If the key has tags, remove it from tag key lists @@ -42567,7 +42567,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { const tagNames = Object.keys(storedTags) // Batch tag cleanup operations - const tagCleanupOperations = tagNames.map(async tag => { + const tagCleanupOperations = tagNames.map(async (tag) => { await this.removeKeysFromTag(tag, [key]) }) operations.push(...tagCleanupOperations) @@ -42597,7 +42597,7 @@ Add the following method to the `MemcachedCachingProviderService` class: class MemcachedCachingProviderService implements ICachingProviderService { // ... private async clearByTags(tags: string[]): Promise { - const operations = tags.map(async tag => { + const operations = tags.map(async (tag) => { const tagKey = this.TAG_PREFIX + tag const result = await this.client.increment(tagKey, 1) if (result === null) { @@ -42771,7 +42771,7 @@ module.exports = defineConfig({ resolve: "@medusajs/medusa/caching", options: { in_memory: { - enable: true + enable: true, }, providers: [ { @@ -42780,16 +42780,16 @@ module.exports = defineConfig({ // Optional, makes this the default caching provider is_default: true, options: { - serverUrls: process.env.MEMCACHED_SERVERS?.split(',') || + serverUrls: process.env.MEMCACHED_SERVERS?.split(",") || ["127.0.0.1:11211"], // add other optional options here... }, }, // other caching providers... ], - } - } - ] + }, + }, + ], }) ``` @@ -42850,7 +42850,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { key: "test-cache-products", // If you didn't set is_default to true, uncomment the following line // providers: ["caching-memcached"], - } + }, }) res.status(200).json({ @@ -43079,10 +43079,10 @@ module.exports = defineConfig({ // more options... }, }, - ] - } - } - ] + ], + }, + }, + ], }) ``` @@ -43126,7 +43126,7 @@ For example: ```ts highlights={[["14"], ["15"], ["16"]]} import { createWorkflow, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "@medusajs/medusa/core-flows" @@ -43176,7 +43176,7 @@ export const getBrandStep = createStep( const cacheKey = await cachingModuleService.computeKey(input.filters) const cachedValue = await cachingModuleService.get({ - tags: ["brand"] + tags: ["brand"], }) if (cachedValue) { @@ -43188,7 +43188,7 @@ export const getBrandStep = createStep( await cachingModuleService.set({ key: cacheKey, tags: [`Brand:${brand.id}`], - data: brand + data: brand, }) return new StepResponse(brand) @@ -43290,11 +43290,11 @@ module.exports = defineConfig({ options: { // Memcached options... }, - } - ] - } - } - ] + }, + ], + }, + }, + ], }) ``` @@ -43317,7 +43317,7 @@ const { data: products } = useQueryGraphStep({ options: { cache: { enable: true, - providers: ["caching-memcached"] // Specify Memcached provider + providers: ["caching-memcached"], // Specify Memcached provider }, }, }) @@ -43331,7 +43331,7 @@ const cachingModuleService = container.resolve(Modules.CACHING) await cachingModuleService.set({ key: "product-list", data: products, - providers: ["caching-memcached"] // Specify Memcached provider + providers: ["caching-memcached"], // Specify Memcached provider }) ``` @@ -43416,7 +43416,7 @@ For example, you can create a workflow in `src/workflows/cache-products.ts` that ```ts title="src/workflows/cache-products.ts" highlights={[["14"], ["15"], ["16"], ["17"]]} import { createWorkflow, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "@medusajs/medusa/core-flows" @@ -58592,7 +58592,7 @@ export default class AgenticCommerceService { // ... async verifySignature({ signature, - payload + payload, }: { // base64 encoded signature signature: string @@ -58600,18 +58600,18 @@ export default class AgenticCommerceService { }) { try { // Decode the base64 signature - const receivedSignature = Buffer.from(signature, 'base64') + const receivedSignature = Buffer.from(signature, "base64") // Create HMAC-SHA256 signature using your signing key const expectedSignature = crypto - .createHmac('sha256', this.options.signatureKey) - .update(JSON.stringify(payload), 'utf8') + .createHmac("sha256", this.options.signatureKey) + .update(JSON.stringify(payload), "utf8") .digest() // Compare signatures using constant-time comparison to prevent timing attacks return crypto.timingSafeEqual(receivedSignature, expectedSignature) } catch (error) { - console.error('Signature verification failed:', error) + console.error("Signature verification failed:", error) return false } } @@ -58632,8 +58632,8 @@ Add the following method to the `AgenticCommerceService` class: export default class AgenticCommerceService { // ... async getSignature(data: any) { - return Buffer.from(crypto.createHmac('sha256', this.options.signatureKey) - .update(JSON.stringify(data), 'utf8').digest()).toString('base64') + return Buffer.from(crypto.createHmac("sha256", this.options.signatureKey) + .update(JSON.stringify(data), "utf8").digest()).toString("base64") } } ``` @@ -58671,7 +58671,7 @@ export default class AgenticCommerceService { // ... async sendWebhookEvent({ type, - data + data, }: AgenticCommerceWebhookEvent) { // Create signature const signature = this.getSignature(data) @@ -58723,7 +58723,7 @@ module.exports = defineConfig({ resolve: "./src/modules/agentic-commerce", options: { signatureKey: process.env.AGENTIC_COMMERCE_SIGNATURE_KEY || "supersecret", - } + }, }, ], }) @@ -58851,7 +58851,7 @@ export const getProductFeedItemsStep = createStep( do { const { data: products, - metadata + metadata, } = await query.graph({ entity: "product", fields: [ @@ -58867,7 +58867,7 @@ export const getProductFeedItemsStep = createStep( "sales_channels.*", "sales_channels.stock_locations.*", "sales_channels.stock_locations.address.*", - "categories.*" + "categories.*", ], filters: { status: "published", @@ -58877,19 +58877,19 @@ export const getProductFeedItemsStep = createStep( calculated_price: QueryContext({ currency_code: currencyCode, }), - } + }, }, pagination: { take: limit, skip: offset, - } + }, }) count = metadata?.count ?? 0 offset += limit for (const product of products) { - if (!product.variants.length) continue + if (!product.variants.length) {continue} const salesChannel = product.sales_channels?.find((channel) => { return channel?.stock_locations?.some((location) => { return location?.address?.country_code.toLowerCase() === countryCode @@ -59112,11 +59112,11 @@ export const sendProductFeedWorkflow = createWorkflow( const { items: feedItems } = getProductFeedItemsStep(input) const xml = buildProductFeedXmlStep({ - items: feedItems + items: feedItems, }) sendProductFeedStep({ - productFeed: xml + productFeed: xml, }) return new WorkflowResponse({ xml }) @@ -59146,9 +59146,9 @@ Create the file `src/jobs/sync-product-feed.ts` with the following content: ```ts title="src/jobs/sync-product-feed.ts" import { - MedusaContainer -} from "@medusajs/framework/types"; -import sendProductFeedWorkflow from "../workflows/send-product-feed"; + MedusaContainer, +} from "@medusajs/framework/types" +import sendProductFeedWorkflow from "../workflows/send-product-feed" export default async function syncProductFeed(container: MedusaContainer) { const logger = container.resolve("logger") @@ -59176,7 +59176,7 @@ export default async function syncProductFeed(container: MedusaContainer) { export const config = { name: "sync-product-feed", schedule: "*/15 * * * *", // Every 15 minutes -}; +} ``` In a scheduled job file, you must export: @@ -59231,11 +59231,11 @@ To create the workflow, create the file `src/workflows/prepare-checkout-session- import { createWorkflow, transform, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { listShippingOptionsForCartWithPricingWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" export type PrepareCheckoutSessionDataWorkflowInput = { @@ -59301,21 +59301,21 @@ const { data: carts } = useQueryGraphStep({ "original_item_total", "shipping_total", "metadata", - "order.id" + "order.id", ], filters: { id: input.cart_id, }, options: { - throwIfKeyNotFound: true - } + throwIfKeyNotFound: true, + }, }) // Retrieve shipping options const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) // TODO prepare response @@ -59363,7 +59363,7 @@ const responseData = transform({ item: { id: item?.variant_id, quantity: item?.quantity, - } + }, })), fulfillment_address: data.input.fulfillment_address, fulfillment_options: data.shippingOptions?.map((option) => ({ @@ -59421,7 +59421,7 @@ const responseData = transform({ display_name: "Total", // @ts-ignore amount: data.carts[0].total, - } + }, ], messages: data.input.messages || [], links: [ @@ -59436,8 +59436,8 @@ const responseData = transform({ { type: "seller_shop_policy", value: "https://www.medusa-commerce.com/seller-shop-policy", // TODO: replace with actual seller shop policy - } - ] + }, + ], } }) @@ -59473,7 +59473,7 @@ import { createWorkflow, transform, when, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { addShippingMethodToCartWorkflow, @@ -59481,10 +59481,10 @@ import { CreateCartWorkflowInput, createCustomersWorkflow, listShippingOptionsForCartWithPricingWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" import { - prepareCheckoutSessionDataWorkflow + prepareCheckoutSessionDataWorkflow, } from "./prepare-checkout-session-data" type WorkflowInput = { @@ -59528,7 +59528,7 @@ Replace the `TODO` in the workflow with the following: ```ts title="src/workflows/create-checkout-session.ts" // validate item IDs const variantIds = transform({ - input + input, }, (data) => { return data.input.items.map((item) => item.id) }) @@ -59538,11 +59538,11 @@ useQueryGraphStep({ entity: "variant", fields: ["id"], filters: { - id: variantIds + id: variantIds, }, options: { - throwIfKeyNotFound: true - } + throwIfKeyNotFound: true, + }, }) // TODO retrieve region and sales channel @@ -59563,9 +59563,9 @@ const { data: regions } = useQueryGraphStep({ fields: ["id"], filters: { countries: { - iso_2: "us" - } - } + iso_2: "us", + }, + }, }).config({ name: "find-region" }) // get sales channel @@ -59596,7 +59596,7 @@ const { data: customers } = useQueryGraphStep({ fields: ["id"], filters: { email: input.buyer?.email, - } + }, }).config({ name: "find-customer" }) // create customer if it does not exist @@ -59612,9 +59612,9 @@ const createdCustomers = when ({ customers }, ({ customers }) => first_name: input.buyer?.first_name, phone: input.buyer?.phone_number, has_account: false, - } - ] - } + }, + ], + }, }) }) @@ -59655,7 +59655,7 @@ const cartInput = transform({ return { items: data.input.items.map((item) => ({ variant_id: item.id, - quantity: item.quantity + quantity: item.quantity, })), region_id: data.regions[0]?.id, email: data.input.buyer?.email, @@ -59674,12 +59674,12 @@ const cartInput = transform({ sales_channel_id: data.salesChannels[0]?.id, metadata: { is_checkout_session: true, - } + }, } as CreateCartWorkflowInput }) const createdCart = createCartWorkflow.runAsStep({ - input: cartInput + input: cartInput, }) // TODO retrieve shipping options @@ -59703,7 +59703,7 @@ when(input, (input) => !!input.fulfillment_address) const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({ input: { cart_id: createdCart.id, - } + }, }) const shippingMethodData = transform({ @@ -59718,11 +59718,11 @@ when(input, (input) => !!input.fulfillment_address) cart_id: data.createdCart.id, options: [{ id: cheapestShippingOption.id, - }] + }], } }) addShippingMethodToCartWorkflow.runAsStep({ - input: shippingMethodData + input: shippingMethodData, }) }) @@ -59746,7 +59746,7 @@ const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({ buyer: input.buyer, fulfillment_address: input.fulfillment_address, cart_id: createdCart.id, - } + }, }) return new WorkflowResponse(responseData) @@ -59809,7 +59809,7 @@ export const POST = async ( input: req.validatedBody, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -59823,8 +59823,8 @@ export const POST = async ( code: "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }) } } @@ -59847,8 +59847,8 @@ Next, you'll create a [middleware](https://docs.medusajs.com/docs/learn/fundamen Create the file `src/api/middlewares/validate-agentic-request.ts` with the following content: ```ts title="src/api/middlewares/validate-agentic-request.ts" -import { MedusaNextFunction, MedusaRequest, MedusaResponse } from "@medusajs/framework"; -import { AGENTIC_COMMERCE_MODULE } from "../../modules/agentic-commerce"; +import { MedusaNextFunction, MedusaRequest, MedusaResponse } from "@medusajs/framework" +import { AGENTIC_COMMERCE_MODULE } from "../../modules/agentic-commerce" export async function validateAgenticRequest( req: MedusaRequest, @@ -59863,12 +59863,12 @@ export async function validateAgenticRequest( const isTokenValid = await apiKeyModuleService.authenticate(apiKey || "") const isSignatureValid = !!req.body || await agenticCommerceModuleService.verifySignature({ signature, - payload: req.body + payload: req.body, }) if (!isTokenValid || !isSignatureValid) { return res.status(401).json({ - message: "Unauthorized" + message: "Unauthorized", }) } @@ -59896,24 +59896,24 @@ You apply middlewares in the `src/api/middlewares.ts` file. Create this file wit import { defineMiddlewares, validateAndTransformBody, -} from "@medusajs/framework/http"; -import { validateAgenticRequest } from "./middlewares/validate-agentic-request"; -import { PostCreateSessionSchema } from "./checkout_sessions/route"; +} from "@medusajs/framework/http" +import { validateAgenticRequest } from "./middlewares/validate-agentic-request" +import { PostCreateSessionSchema } from "./checkout_sessions/route" export default defineMiddlewares({ routes: [ { matcher: "/checkout_sessions*", middlewares: [ - validateAgenticRequest - ] + validateAgenticRequest, + ], }, { matcher: "/checkout_sessions", method: ["POST"], - middlewares: [validateAndTransformBody(PostCreateSessionSchema)] + middlewares: [validateAndTransformBody(PostCreateSessionSchema)], }, - ] + ], }) ``` @@ -59932,7 +59932,7 @@ In `src/api/middlewares.ts`, add the following import at the top of the file: ```ts title="src/api/middlewares.ts" import { errorHandler, -} from "@medusajs/framework/http"; +} from "@medusajs/framework/http" const originalErrorHandler = errorHandler() ``` @@ -59956,8 +59956,8 @@ export default defineMiddlewares({ code: "invalid", content_type: "plain", content: error.message, - } - ] + }, + ], }) }, }) @@ -60113,16 +60113,16 @@ import { createWorkflow, transform, when, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { addShippingMethodToCartWorkflow, createCustomersWorkflow, updateCartWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" import { - prepareCheckoutSessionDataWorkflow + prepareCheckoutSessionDataWorkflow, } from "./prepare-checkout-session-data" type WorkflowInput = { @@ -60158,7 +60158,7 @@ export const updateCheckoutSessionWorkflow = createWorkflow( fields: ["id", "customer.*", "email"], filters: { id: input.cart_id, - } + }, }) // TODO retrieve or create customer @@ -60181,7 +60181,7 @@ const { data: customers } = useQueryGraphStep({ fields: ["id"], filters: { email: input.buyer?.email, - } + }, }).config({ name: "find-customer" }) const createdCustomers = when({ customers }, ({ customers }) => @@ -60195,9 +60195,9 @@ const createdCustomers = when({ customers }, ({ customers }) => email: input.buyer?.email, first_name: input.buyer?.first_name, phone: input.buyer?.phone_number, - } + }, ], - } + }, }) }) @@ -60235,7 +60235,7 @@ when(input, (input) => !!input.items) }, options: { throwIfKeyNotFound: true, - } + }, }).config({ name: "find-variant" }) }) @@ -60311,7 +60311,7 @@ const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({ cart_id: updateData.id, buyer: input.buyer, fulfillment_address: input.fulfillment_address, - } + }, }) return new WorkflowResponse(responseData) @@ -60377,7 +60377,7 @@ export const POST = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -60387,7 +60387,7 @@ export const POST = async ( await refreshPaymentCollectionForCartWorkflow(req.scope).run({ input: { cart_id: req.params.id, - } + }, }) const { result } = await prepareCheckoutSessionDataWorkflow(req.scope) @@ -60402,8 +60402,8 @@ export const POST = async ( "payment_declined" : "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }, }) @@ -60427,7 +60427,7 @@ Finally, you'll apply the validation middleware to the `POST /checkout_sessions/ In `src/api/middlewares.ts`, add the following import at the top of the file: ```ts title="src/api/middlewares.ts" -import { PostUpdateSessionSchema } from "./checkout_sessions/[id]/route"; +import { PostUpdateSessionSchema } from "./checkout_sessions/[id]/route" ``` Then, add a new route configuration in `defineMiddlewares`: @@ -60439,7 +60439,7 @@ export default defineMiddlewares({ { matcher: "/checkout_sessions/:id", method: ["POST"], - middlewares: [validateAndTransformBody(PostUpdateSessionSchema)] + middlewares: [validateAndTransformBody(PostUpdateSessionSchema)], }, ], // ... @@ -60524,7 +60524,7 @@ export const GET = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).status(201).json(result) @@ -60603,7 +60603,7 @@ import { createWorkflow, transform, when, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { completeCartWorkflow, @@ -60611,11 +60611,11 @@ import { createPaymentSessionsWorkflow, refreshPaymentCollectionForCartWorkflow, updateCartWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" import { prepareCheckoutSessionDataWorkflow, - PrepareCheckoutSessionDataWorkflowInput + PrepareCheckoutSessionDataWorkflowInput, } from "./prepare-checkout-session-data" type WorkflowInput = { @@ -60651,7 +60651,7 @@ export const completeCheckoutSessionWorkflow = createWorkflow( "id", "region.*", "region.payment_providers.*", - "shipping_address.*" + "shipping_address.*", ], filters: { id: input.cart_id, @@ -60693,7 +60693,7 @@ when(input, (input) => !!input.payment_data.billing_address) postal_code: data.input.payment_data.billing_address!.postal_code, country_code: data.input.payment_data.billing_address!.country, phone: data.input.payment_data.billing_address!.phone_number, - } + }, } }) return updateCartWorkflow.runAsStep({ @@ -60732,7 +60732,7 @@ const preparationInput = transform({ }) const paymentProviderId = transform({ - input + input, }, (data) => { switch (data.input.payment_data.provider) { case "stripe": @@ -60744,7 +60744,7 @@ const paymentProviderId = transform({ const completeCartResponse = when({ carts, - paymentProviderId + paymentProviderId, }, (data) => { // @ts-ignore return !!data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId) @@ -60753,7 +60753,7 @@ const completeCartResponse = when({ const paymentCollection = createPaymentCollectionForCartWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) createPaymentSessionsWorkflow.runAsStep({ @@ -60762,14 +60762,14 @@ const completeCartResponse = when({ provider_id: paymentProviderId, data: { shared_payment_token: input.payment_data.token, - } - } + }, + }, }) completeCartWorkflow.runAsStep({ input: { id: carts[0].id, - } + }, }) return prepareCheckoutSessionDataWorkflow.runAsStep({ @@ -60799,7 +60799,7 @@ Replace the `TODO` in the workflow with the following: ```ts title="src/workflows/complete-checkout-session.ts" const invalidPaymentResponse = when({ carts, - paymentProviderId + paymentProviderId, }, (data) => { return !data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId) }) @@ -60807,7 +60807,7 @@ const invalidPaymentResponse = when({ refreshPaymentCollectionForCartWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) const prepareDataWithMessages = transform({ prepareData: preparationInput, @@ -60820,12 +60820,12 @@ const invalidPaymentResponse = when({ code: "invalid", content_type: "plain", content: "Invalid payment provider", - } - ] + }, + ], } as PrepareCheckoutSessionDataWorkflowInput }) return prepareCheckoutSessionDataWorkflow.runAsStep({ - input: prepareDataWithMessages + input: prepareDataWithMessages, }).config({ name: "prepare-checkout-session-data-with-messages" }) }) @@ -60843,7 +60843,7 @@ Finally, you'll return the appropriate response based on whether the cart was co ```ts title="src/workflows/complete-checkout-session.ts" const responseData = transform({ completeCartResponse, - invalidPaymentResponse + invalidPaymentResponse, }, (data) => { return data.completeCartResponse || data.invalidPaymentResponse }) @@ -60908,7 +60908,7 @@ export const POST = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -60918,7 +60918,7 @@ export const POST = async ( await refreshPaymentCollectionForCartWorkflow(req.scope).run({ input: { cart_id: req.params.id, - } + }, }) const { result } = await prepareCheckoutSessionDataWorkflow(req.scope) .run({ @@ -60932,8 +60932,8 @@ export const POST = async ( "payment_declined" : "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }, }) @@ -60957,7 +60957,7 @@ Finally, you'll apply the validation middleware to the `POST /checkout_sessions/ In `src/api/middlewares.ts`, add the following import at the top of the file: ```ts title="src/api/middlewares.ts" -import { PostCompleteSessionSchema } from "./checkout_sessions/[id]/complete/route"; +import { PostCompleteSessionSchema } from "./checkout_sessions/[id]/complete/route" ``` Then, add a new route configuration in `defineMiddlewares`: @@ -60969,7 +60969,7 @@ export default defineMiddlewares({ { matcher: "/checkout_sessions/:id/complete", method: ["POST"], - middlewares: [validateAndTransformBody(PostCompleteSessionSchema)] + middlewares: [validateAndTransformBody(PostCompleteSessionSchema)], }, ], // ... @@ -61252,14 +61252,14 @@ export const cancelCheckoutSessionWorkflow = createWorkflow( "id", "payment_collection.*", "payment_collection.payment_sessions.*", - "order.id" + "order.id", ], filters: { id: input.cart_id, }, options: { throwIfKeyNotFound: true, - } + }, }) validateCartCancelationStep({ @@ -61279,11 +61279,11 @@ Next, you'll cancel the payment sessions if there are any. Replace the `TODO` in ```ts title="src/workflows/cancel-checkout-session.ts" when({ - carts + carts, }, (data) => !!data.carts[0].payment_collection?.payment_sessions?.length) .then(() => { const paymentSessionIds = transform({ - carts + carts, }, (data) => { return data.carts[0].payment_collection?.payment_sessions?.map((session) => session!.id) }) @@ -61297,8 +61297,8 @@ updateCartWorkflow.runAsStep({ id: carts[0].id, metadata: { checkout_session_canceled: true, - } - } + }, + }, }) // TODO prepare and return response @@ -61314,7 +61314,7 @@ Finally, you'll prepare and return the checkout session response. Replace the `T const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) return new WorkflowResponse(responseData) @@ -61349,7 +61349,7 @@ export const POST = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -61362,8 +61362,8 @@ export const POST = async ( code: "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }) } } @@ -61444,7 +61444,7 @@ export default async function orderWebhookHandler({ ], filters: { id: orderId, - } + }, }) // only send webhook if order is associated with a checkout session @@ -61466,7 +61466,7 @@ export default async function orderWebhookHandler({ type: "original_payment", amount: transaction!.amount * -1, })) || [], - } + }, } // set status based on order, fulfillments and transactions @@ -88346,6 +88346,7 @@ If you get a type error on resolving the Meilisearch Module, run the Medusa appl import { ProductDTO } from "@medusajs/framework/types" import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" import { MEILISEARCH_MODULE } from "../../modules/meilisearch" +import MeilisearchModuleService from "../../modules/meilisearch/service" export type SyncProductsStepInput = { products: ProductDTO[] @@ -88354,7 +88355,7 @@ export type SyncProductsStepInput = { export const syncProductsStep = createStep( "sync-products", async ({ products }: SyncProductsStepInput, { container }) => { - const meilisearchModuleService = container.resolve( + const meilisearchModuleService = container.resolve( MEILISEARCH_MODULE ) const existingProducts = await meilisearchModuleService.retrieveFromIndex( @@ -88410,7 +88411,7 @@ export const syncProductsStep = createStep( return } - const meilisearchModuleService = container.resolve( + const meilisearchModuleService = container.resolve( MEILISEARCH_MODULE ) @@ -93552,6 +93553,7 @@ Create the file `src/workflows/send-order-confirmation.ts` with the following co ```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights} import { createWorkflow, + when, WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "@medusajs/medusa/core-flows" @@ -93589,18 +93591,26 @@ export const sendOrderConfirmationWorkflow = createWorkflow( filters: { id, }, + options: { + throwIfKeyNotFound: true, + }, }) - const notification = sendNotificationStep([{ - to: orders[0].email, - channel: "email", - template: "order-placed", - data: { - order: orders[0], - }, - }]) + const notification = when({ orders }, (data) => !!data.orders[0].email) + .then(() => { + return sendNotificationStep([{ + to: orders[0].email!, + channel: "email", + template: "order-placed", + data: { + order: orders[0], + }, + }]) + }) - return new WorkflowResponse(notification) + return new WorkflowResponse({ + notification, + }) } ) ``` @@ -93610,13 +93620,13 @@ You create a workflow using `createWorkflow` from the Workflows SDK. It accepts It accepts as a second parameter a constructor function, which is the workflow's implementation. The workflow has the following steps: 1. `useQueryGraphStep`, which is a step implemented by Medusa that uses [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), a tool that allows you to retrieve data across modules. You use it to retrieve the order's details. -2. `sendNotificationStep` which is the step you implemented. You pass it an array with one object, which is the notification's details having following properties: +2. Ensure that the order has an email address by using `when-then`. If so, you send the notification using the `sendNotificationStep` you implemented earlier. You pass it an object with the following properties: - `to`: The address to send the email to. You pass the customer's email that is stored in the order. - `channel`: The channel to send the notification through, which is `email`. Since you specified `email` in the Resend Module Provider's `channel` option, the Notification Module will delegate the sending to the Resend Module Provider's service. - `template`: The email's template type. You retrieve the template content in the `ResendNotificationProviderService`'s `send` method based on the template specified here. - `data`: The data to pass to the email template, which is the order's details. -A workflow's constructor function has some constraints in implementation. Learn more about them in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md). +`when` allows you to perform steps based on a condition during execution. Learn more in the [Conditions in Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/conditions/index.html.md) documentation. You'll execute the workflow when you create the subscriber next. @@ -100635,7 +100645,11 @@ To learn more about the commerce features that Medusa provides, check out Medusa - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md) - [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.update/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md) @@ -105610,7 +105624,7 @@ export const createReturnRequest = async ( return_shipping: { option_id: returnShippingOptionId, }, - location_id: locationId + location_id: locationId, }, headers, }) @@ -106039,7 +106053,7 @@ To create the component, create the file `src/modules/account/components/return- import React, { useEffect, - useState + useState, } from "react" import { RadioGroup } from "@headlessui/react" import { HttpTypes } from "@medusajs/types" @@ -106267,7 +106281,7 @@ const ReturnRequestTemplate: React.FC = ({ formData.append("items", JSON.stringify(selectedItems)) formData.append("return_shipping_option_id", selectedShippingOption) // @ts-expect-error issue in HTTP types - const locationId = shippingOptions.find(opt => opt.id === selectedShippingOption)?.service_zone.fulfillment_set.location.id + const locationId = shippingOptions.find((opt) => opt.id === selectedShippingOption)?.service_zone.fulfillment_set.location.id formData.append("location_id", locationId) formAction(formData) } @@ -118574,7 +118588,7 @@ export const validateVariantOutOfStockStep = createStep( sales_channel_id, }) - if (availability[variant_id].availability > 0) { + if ((availability[variant_id].availability || 0) > 0) { throw new MedusaError( MedusaError.Types.INVALID_DATA, "Variant isn't out of stock." @@ -119227,7 +119241,7 @@ export const getRestockedStep = createStep( sales_channel_id: restockSubscription.sales_channel_id, }) - if (variantAvailability[restockSubscription.variant_id].availability > 0) { + if ((variantAvailability[restockSubscription.variant_id].availability || 0) > 0) { restocked.push(restockSubscription) } }) @@ -119938,12 +119952,15 @@ const createDigitalProductStep = createStep( digital_product: digitalProduct, }) }, - async ({ digital_product }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) await digitalProductModuleService.deleteDigitalProducts( - digital_product.id + data.digital_product.id ) } ) @@ -119996,12 +120013,15 @@ const createDigitalProductMediasStep = createStep( digital_product_medias: digitalProductMedias, }) }, - async ({ digital_product_medias }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) await digitalProductModuleService.deleteDigitalProductMedias( - digital_product_medias.map((media) => media.id) + data.digital_product_medias.map((media) => media.id) ) } ) @@ -121269,7 +121289,7 @@ import DigitalProductModuleService from "../../../modules/digital-product/servic import { DIGITAL_PRODUCT_MODULE } from "../../../modules/digital-product" import DigitalProduct from "../../../modules/digital-product/models/digital-product" -type StepInput = { +export type CreateDigitalProductOrderStepInput = { items: (OrderLineItemDTO & { variant: ProductVariantDTO & { digital_product: InferTypeOf @@ -121279,7 +121299,7 @@ type StepInput = { const createDigitalProductOrderStep = createStep( "create-digital-product-order", - async ({ items }: StepInput, { container }) => { + async ({ items }: CreateDigitalProductOrderStepInput, { container }) => { const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) @@ -121297,12 +121317,15 @@ const createDigitalProductOrderStep = createStep( digital_product_order: digitalProductOrder, }) }, - async ({ digital_product_order }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) await digitalProductModuleService.deleteDigitalProductOrders( - digital_product_order.id + data.digital_product_order.id ) } ) @@ -121335,7 +121358,9 @@ import { import { Modules, } from "@medusajs/framework/utils" -import createDigitalProductOrderStep from "./steps/create-digital-product-order" +import createDigitalProductOrderStep, { + CreateDigitalProductOrderStepInput, +} from "./steps/create-digital-product-order" import { DIGITAL_PRODUCT_MODULE } from "../../modules/digital-product" type WorkflowInput = { @@ -121372,7 +121397,7 @@ const createDigitalProductOrderWorkflow = createWorkflow( orders, }, (data) => { - return data.orders[0].items.filter((item) => item.variant.digital_product !== undefined) + return data.orders[0].items?.filter((item) => item?.variant?.digital_product !== undefined) } ) @@ -121380,14 +121405,14 @@ const createDigitalProductOrderWorkflow = createWorkflow( "create-digital-product-order-condition", itemsWithDigitalProducts, (itemsWithDigitalProducts) => { - return itemsWithDigitalProducts.length + return !!itemsWithDigitalProducts?.length } ).then(() => { const { digital_product_order, } = createDigitalProductOrderStep({ items: orders[0].items, - }) + } as unknown as CreateDigitalProductOrderStepInput) createRemoteLinkStep([{ [DIGITAL_PRODUCT_MODULE]: { @@ -121404,9 +121429,9 @@ const createDigitalProductOrderWorkflow = createWorkflow( items: transform({ itemsWithDigitalProducts, }, (data) => { - return data.itemsWithDigitalProducts.map((item) => ({ - id: item.id, - quantity: item.quantity, + return data.itemsWithDigitalProducts!.map((item) => ({ + id: item!.id, + quantity: item!.quantity, })) }), }, @@ -121578,10 +121603,14 @@ import { INotificationModuleService, IFileModuleService, } from "@medusajs/framework/types" -import { ModuleRegistrationName } from "@medusajs/framework/utils" +import { + MedusaError, + ModuleRegistrationName, + promiseAll, +} from "@medusajs/framework/utils" import { DigitalProductOrder, MediaType } from "../../../modules/digital-product/types" -type SendDigitalOrderNotificationStepInput = { +export type SendDigitalOrderNotificationStepInput = { digital_product_order: DigitalProductOrder } @@ -121597,6 +121626,20 @@ export const sendDigitalOrderNotificationStep = createStep( ModuleRegistrationName.FILE ) + if (!digitalProductOrder.order) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Digital product order is missing associated order." + ) + } + + if (!digitalProductOrder.order.email) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Order is missing email." + ) + } + // TODO assemble notification } ) @@ -121604,16 +121647,16 @@ export const sendDigitalOrderNotificationStep = createStep( This creates the `sendDigitalOrderNotificationStep` step that receives a digital product order as an input. -In the step, so far you resolve the main services of the Notification and File Modules. +In the step, so far you resolve the main services of the Notification and File Modules. You also check that the digital product order has an associated order and that the order has an email. -Replace the `TODO` with the following: +Next, you'll prepare the data to pass in the notification. Replace the `TODO` with the following: ```ts title="src/workflows/fulfill-digital-order/steps/send-digital-order-notification.ts" -const notificationData = await Promise.all( +const notificationData = await promiseAll( digitalProductOrder.products.map(async (product) => { - const medias = [] + const medias: string[] = [] - await Promise.all( + await promiseAll( product.medias .filter((media) => media.type === MediaType.MAIN) .map(async (media) => { @@ -121656,7 +121699,7 @@ You use the `createNotifications` method of the Notification Module's main servi Create the workflow in the file `src/workflows/fulfill-digital-order/index.ts`: -```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports" +```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-13" expandMoreLabel="Show Imports" import { createWorkflow, WorkflowResponse, @@ -121665,7 +121708,10 @@ import { markOrderFulfillmentAsDeliveredWorkflow, useQueryGraphStep, } from "@medusajs/medusa/core-flows" -import { sendDigitalOrderNotificationStep } from "./steps/send-digital-order-notification" +import { + sendDigitalOrderNotificationStep, + SendDigitalOrderNotificationStepInput, +} from "./steps/send-digital-order-notification" type FulfillDigitalOrderWorkflowInput = { id: string @@ -121693,14 +121739,17 @@ export const fulfillDigitalOrderWorkflow = createWorkflow( sendDigitalOrderNotificationStep({ digital_product_order: digitalProductOrders[0], - }) + } as unknown as SendDigitalOrderNotificationStepInput) - markOrderFulfillmentAsDeliveredWorkflow.runAsStep({ - input: { - orderId: digitalProductOrders[0].order.id, - fulfillmentId: digitalProductOrders[0].order.fulfillments[0].id, - }, - }) + when({ digitalProductOrders }, (data) => !!data.digitalProductOrders[0].order?.fulfillments?.length) + .then(() => { + markOrderFulfillmentAsDeliveredWorkflow.runAsStep({ + input: { + orderId: digitalProductOrders[0].order!.id, + fulfillmentId: digitalProductOrders[0].order!.fulfillments![0]!.id, + }, + }) + }) return new WorkflowResponse( digitalProductOrders[0] @@ -121713,7 +121762,9 @@ In the workflow, you: 1. Retrieve the digital product order's details using `useQueryGraphStep` from Medusa's core workflows. 2. Send a notification to the customer with the digital product download links using the `sendDigitalOrderNotificationStep`. -3. Mark the order's fulfillment as delivered using `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows. +3. Check whether the Medusa order has fulfillments. If so, mark the order's fulfillment as delivered using `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows. + +`when` allows you to perform steps based on a condition during execution. Learn more in the [Conditions in Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/conditions/index.html.md) documentation. ### Configure Notification Module Provider @@ -121893,8 +121944,11 @@ export const GET = async ( const digitalProducts = {} - customer.orders.forEach((order) => { - order.digital_product_order.products.forEach((product) => { + customer.orders?.forEach((order) => { + order?.digital_product_order?.products.forEach((product) => { + if (!product) { + return + } digitalProducts[product.id] = product }) }) @@ -121943,9 +121997,9 @@ export const POST = async ( }, }) - const customerDigitalOrderIds = customer.orders - .filter((order) => order.digital_product_order !== undefined) - .map((order) => order.digital_product_order.id) + const customerDigitalOrderIds = customer.orders?.filter( + (order) => order?.digital_product_order !== undefined + ).map((order) => order!.digital_product_order!.id) const { data: dpoResult } = await query.graph({ entity: "digital_product_order", @@ -121964,11 +122018,11 @@ export const POST = async ( ) } - let foundMedia = undefined + let foundMedia: any | undefined = undefined dpoResult[0].products.some((product) => { - return product.medias.some((media) => { - foundMedia = media.id === req.params.mediaId ? media : undefined + return product?.medias.some((media) => { + foundMedia = media?.id === req.params.mediaId ? media : undefined return foundMedia !== undefined }) @@ -124376,15 +124430,9 @@ export enum DeliveryStatus { IN_TRANSIT = "in_transit", DELIVERED = "delivered", } - -declare module "@medusajs/framework/types" { - export interface ModuleImplementations { - deliveryModuleService: DeliveryModuleService; - } -} ``` -This adds an enum that is used by the data models. It also adds a type for `deliveryModuleService` in `ModuleImplementations` so that when you resolve it from the Medusa container, it has the correct typing. +This adds an enum that is used by the data models. ### Create Delivery Data Models @@ -124634,15 +124682,9 @@ import { Restaurant } from "../models/restaurant" export type CreateRestaurant = Omit< InferTypeOf, "id" | "admins" > - -declare module "@medusajs/framework/types" { - export interface ModuleImplementations { - restaurantModuleService: RestaurantModuleService; - } -} ``` -This adds a type used for inputs in creating a restaurant. It also adds a type for `restaurantModuleService` in `ModuleImplementations` so that when you resolve it from the Medusa container, it has the correct typing. +This adds a type used for inputs in creating a restaurant. Since the `Restaurant` data model is a variable, use `InferTypeOf` to infer its type. @@ -125041,14 +125083,16 @@ const { user, authUserInput } = transform({ input, restaurantUser, driverUser }, authUserInput: { authIdentityId: data.input.auth_identity_id, actorType: data.input.user.actor_type, - value: user.id, + value: user?.id || "", }, } }) setAuthAppMetadataStep(authUserInput) -return new WorkflowResponse(user) +return new WorkflowResponse({ + user, +}) ``` In the workflow, you: @@ -125108,7 +125152,7 @@ export const POST = async ( } as CreateUserWorkflowInput, }) - res.status(201).json({ user: result }) + res.status(201).json({ user: result.user }) } ``` @@ -125250,12 +125294,15 @@ export const deleteRestaurantAdminStep = createStep( return new StepResponse(undefined, { admin }) }, - async ({ admin }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const restaurantModuleService: RestaurantModuleService = container.resolve( RESTAURANT_MODULE ) - const { restaurant: _, ...adminData } = admin + const { restaurant: _, ...adminData } = data.admin await restaurantModuleService.createRestaurantAdmins(adminData) } @@ -125307,6 +125354,7 @@ const { data: authIdentities } = useQueryGraphStep({ entity: "auth_identity", fields: ["id"], filters: { + // @ts-ignore app_metadata: { restaurant_id: input.id, }, @@ -125624,11 +125672,14 @@ export const createDeliveryStep = createStep( delivery_id: delivery.id, }) }, - async function ({ delivery_id }, { container }) { + async function (data, { container }) { + if (!data) { + return + } const deliverModuleService: DeliveryModuleService = container.resolve(DELIVERY_MODULE) - deliverModuleService.softDeleteDeliveries(delivery_id) + deliverModuleService.softDeleteDeliveries(data.delivery_id) } ) @@ -125647,6 +125698,7 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" +import { LinkDefinition } from "@medusajs/framework/types" import { Modules } from "@medusajs/framework/utils" import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" import { DELIVERY_MODULE } from "../../../modules/delivery" @@ -125688,7 +125740,7 @@ export const createDeliveryWorkflow = createWorkflow( delivery_id: data.delivery.id, }, }, - ])) + ] as LinkDefinition[])) createRemoteLinkStep(links) @@ -125813,7 +125865,7 @@ export const notifyRestaurantStep = createStep( await eventBus.emit({ name: "notify.restaurant", data: { - restaurant_id: delivery.restaurant.id, + restaurant_id: delivery.restaurant?.id, delivery_id: delivery.id, }, }) @@ -125863,6 +125915,7 @@ import { CreateOrderShippingMethodDTO } from "@medusajs/framework/types" import { Modules, ContainerRegistrationKeys, + MedusaError, } from "@medusajs/framework/utils" import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" import { DELIVERY_MODULE } from "../../../modules/delivery" @@ -125889,7 +125942,7 @@ export const createOrderStep = createStep( // TODO create order }, - async ({ orderId }, { container }) => { + async (data, { container }) => { // TODO add compensation } ) @@ -125903,19 +125956,59 @@ Replace the `TODO` with the following to create the order: ```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights2} const { cart } = delivery +if (!cart) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Cart for delivery with id: ${deliveryId} was not found` + ) +} + const orderModuleService = container.resolve(Modules.ORDER) const order = await orderModuleService.createOrders({ currency_code: cart.currency_code, email: cart.email, - shipping_address: cart.shipping_address, - billing_address: cart.billing_address, - items: cart.items, - region_id: cart.region_id, - customer_id: cart.customer_id, - sales_channel_id: cart.sales_channel_id, + shipping_address: { + first_name: cart.shipping_address?.first_name || "", + last_name: cart.shipping_address?.last_name || "", + address_1: cart.shipping_address?.address_1 || "", + address_2: cart.shipping_address?.address_2 || "", + city: cart.shipping_address?.city || "", + province: cart.shipping_address?.province || "", + postal_code: cart.shipping_address?.postal_code || "", + country_code: cart.shipping_address?.country_code || "", + phone: cart.shipping_address?.phone || "", + }, + billing_address: { + first_name: cart.billing_address?.first_name || "", + last_name: cart.billing_address?.last_name || "", + address_1: cart.billing_address?.address_1 || "", + address_2: cart.billing_address?.address_2 || "", + city: cart.billing_address?.city || "", + province: cart.billing_address?.province || "", + postal_code: cart.billing_address?.postal_code || "", + country_code: cart.billing_address?.country_code || "", + phone: cart.billing_address?.phone || "", + }, + items: cart.items.map((item) => ({ + title: item!.title, + quantity: item!.quantity, + variant_id: item!.variant_id || "", + unit_price: item!.unit_price, + metadata: item!.metadata || undefined, + product_id: item!.product_id || "", + })), + region_id: cart.region_id || "", + customer_id: cart.customer_id || "", + sales_channel_id: cart.sales_channel_id || "", shipping_methods: - cart.shipping_methods as unknown as CreateOrderShippingMethodDTO[], + cart.shipping_methods?.map((sm): CreateOrderShippingMethodDTO => ({ + shipping_option_id: sm?.shipping_option_id || "", + data: sm?.data || {}, + name: sm?.name || "", + amount: sm?.amount || 0, + order_id: "", // will be set internally + })) || [], }) const linkDef = [{ @@ -125940,9 +126033,12 @@ You create the order using the Order Module’s main service. Then, you create a Then, replace the `TODO` in the compensation function with the following: ```ts title="src/workflows/delivery/steps/create-order.ts" +if (!data) { + return +} const orderService = container.resolve(Modules.ORDER) -await orderService.softDeleteOrders([orderId]) +await orderService.softDeleteOrders([data.orderId]) ``` You delete the order in the compensation function. @@ -126384,14 +126480,17 @@ export const updateDeliveryStep = createStep( prevDeliveryData, }) }, - async ({ prevDeliveryData }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const deliverModuleService: DeliveryModuleService = container.resolve(DELIVERY_MODULE) const { driver, ...prevDeliveryDataWithoutDriver - } = prevDeliveryData + } = data.prevDeliveryData await deliverModuleService.updateDeliveries(prevDeliveryDataWithoutDriver) } @@ -126429,7 +126528,7 @@ export const setStepSuccessStep = createStep( await engineService.setStepSuccess({ idempotencyKey: { action: TransactionHandlerType.INVOKE, - transactionId: updatedDelivery.transaction_id, + transactionId: updatedDelivery.transaction_id || "", stepId, workflowId: handleDeliveryWorkflowId, }, @@ -126473,7 +126572,7 @@ export const setStepFailedStep = createStep( await engineService.setStepFailure({ idempotencyKey: { action: TransactionHandlerType.INVOKE, - transactionId: updatedDelivery.transaction_id, + transactionId: updatedDelivery.transaction_id || "", stepId, workflowId: handleDeliveryWorkflowId, }, @@ -126516,7 +126615,7 @@ export const updateDeliveryWorkflow = createWorkflow( when(input, ({ stepIdToSucceed }) => stepIdToSucceed !== undefined) .then(() => { setStepSuccessStep({ - stepId: input.stepIdToSucceed, + stepId: input.stepIdToSucceed!, updatedDelivery, }) }) @@ -126525,7 +126624,7 @@ export const updateDeliveryWorkflow = createWorkflow( when(input, ({ stepIdToFail }) => stepIdToFail !== undefined) .then(() => { setStepFailedStep({ - stepId: input.stepIdToFail, + stepId: input.stepIdToFail!, updatedDelivery, }) }) @@ -126642,7 +126741,7 @@ export const isDeliveryRestaurant = async ( }, }) - if (delivery.restaurant.id !== restaurantAdmin.restaurant.id) { + if (delivery.restaurant?.id !== restaurantAdmin.restaurant.id) { return res.status(403).json({ message: "unauthorized", }) @@ -126692,7 +126791,7 @@ import deliveriesMiddlewares from "./deliveries/[id]/middlewares" export default defineMiddlewares({ routes: [ // ... - ...deliveriesMiddlewares.routes, + ...deliveriesMiddlewares.routes!, ], }) ``` @@ -127291,7 +127390,7 @@ res.write( "data: " + JSON.stringify({ message: "Subscribed to workflow", - transactionId: delivery.transaction_id, + transactionId: delivery.transaction_id || undefined, }) + "\n\n" ) @@ -128249,6 +128348,7 @@ import { } from "@medusajs/framework/workflows-sdk" import { createProductsWorkflow, + CreateProductsWorkflowInput, createRemoteLinkStep, useQueryGraphStep, } from "@medusajs/medusa/core-flows" @@ -128288,7 +128388,7 @@ const createVendorProductWorkflow = createWorkflow( }) const createdProducts = createProductsWorkflow.runAsStep({ - input: productData, + input: productData as CreateProductsWorkflowInput, }) // TODO link vendor and products @@ -128559,26 +128659,28 @@ import { createStep, StepResponse, } from "@medusajs/framework/workflows-sdk" -import { CartDTO, CartLineItemDTO } from "@medusajs/framework/types" +import { CartLineItemDTO } from "@medusajs/framework/types" import { ContainerRegistrationKeys, promiseAll } from "@medusajs/framework/utils" -type StepInput = { - cart: CartDTO +export type GroupVendorItemsStepInput = { + cart: { + items?: CartLineItemDTO[] + } } const groupVendorItemsStep = createStep( "group-vendor-items", - async ({ cart }: StepInput, { container }) => { + async ({ cart }: GroupVendorItemsStepInput, { container }) => { const query = container.resolve(ContainerRegistrationKeys.QUERY) const vendorsItems: Record = {} - await promiseAll(cart.items?.map(async (item) => { + await promiseAll((cart.items || []).map(async (item) => { const { data: [product] } = await query.graph({ entity: "product", fields: ["vendor.*"], filters: { - id: [item.product_id], + id: item.product_id || "", }, }) @@ -128672,7 +128774,7 @@ const createVendorOrdersStep = createStep( created_orders: createdOrders, }) }, - async ({ created_orders }, { container, context }) => { + async (data, { container, context }) => { // TODO add compensation function } ) @@ -128787,19 +128889,19 @@ function prepareOrderData( // item/vendor. This requires changes in the storefront to commodate that // and passing the item/vendor ID in the `data` property, for example. // For simplicity here we just use the same shipping method. - shipping_methods: parentOrder.shipping_methods.map((shippingMethod) => ({ + shipping_methods: parentOrder.shipping_methods?.map((shippingMethod) => ({ name: shippingMethod.name, amount: shippingMethod.amount, shipping_option_id: shippingMethod.shipping_option_id, data: shippingMethod.data, - tax_lines: shippingMethod.tax_lines.map((taxLine) => ({ + tax_lines: shippingMethod.tax_lines?.map((taxLine) => ({ code: taxLine.code, rate: taxLine.rate, provider_id: taxLine.provider_id, tax_rate_id: taxLine.tax_rate_id, description: taxLine.description, })), - adjustments: shippingMethod.adjustments.map((adjustment) => ({ + adjustments: shippingMethod.adjustments?.map((adjustment) => ({ code: adjustment.code, amount: adjustment.amount, description: adjustment.description, @@ -128818,7 +128920,10 @@ When creating the child orders, the shipping method of the parent is used as-is Finally, replace the `TODO` in the compensation function with the following: ```ts title="src/workflows/marketplace/create-vendor-orders/steps/create-vendor-orders.ts" -await Promise.all(created_orders.map((createdOrder) => { +if (!data) { + return +} +await promiseAll(data.created_orders.map((createdOrder) => { return cancelOrderWorkflow(container).run({ input: { order_id: createdOrder.id, @@ -128875,7 +128980,7 @@ const createVendorOrdersWorkflow = createWorkflow( const { vendorsItems } = groupVendorItemsStep({ cart: carts[0], - }) + } as unknown as GroupVendorItemsStepInput) const order = getOrderDetailWorkflow.runAsStep({ input: { @@ -129048,7 +129153,7 @@ export const GET = async ( ], variables: { filters: { - id: vendorAdmin.vendor.orders.map((order) => order.id), + id: vendorAdmin.vendor.orders?.map((order) => order?.id), }, }, }, @@ -131282,7 +131387,7 @@ const createSubscriptionStep = createStep( }, { subscription: subscription[0], }) - }, async ({ subscription }, { container }) => { + }, async (data, { container }) => { // TODO implement compensation } ) @@ -131336,10 +131441,13 @@ This adds links between: The step also has a compensation function to undo the step’s changes if an error occurs. So, replace the second `TODO` with the following: ```ts title="src/workflows/create-subscription/steps/create-subscription.ts" +if (!data) { + return +} const subscriptionModuleService: SubscriptionModuleService = - container.resolve(SUBSCRIPTION_MODULE) + container.resolve(SUBSCRIPTION_MODULE) -await subscriptionModuleService.cancelSubscriptions(subscription.id) +await subscriptionModuleService.cancelSubscriptions(data.subscription.id) ``` The compensation function receives the subscription as a parameter. It cancels the subscription. @@ -131438,7 +131546,7 @@ const createSubscriptionWorkflow = createWorkflow( const { subscription, linkDefs } = createSubscriptionStep({ cart_id: input.cart_id, order_id: orders[0].id, - customer_id: orders[0].customer_id, + customer_id: orders[0].customer_id!, subscription_data: input.subscription_data, }) @@ -131491,6 +131599,7 @@ import { MedusaError, } from "@medusajs/framework/utils" import createSubscriptionWorkflow from "../../../../../workflows/create-subscription" +import { SubscriptionInterval } from "../../../../../modules/subscription/types" export const POST = async ( req: MedusaRequest, @@ -131523,8 +131632,8 @@ export const POST = async ( input: { cart_id: req.params.id, subscription_data: { - interval: metadata.subscription_interval, - period: metadata.subscription_period, + interval: metadata.subscription_interval as SubscriptionInterval, + period: metadata.subscription_period as number, }, }, }) @@ -131856,7 +131965,7 @@ export const GET = async ( const { data: subscriptions, - metadata: { count, take, skip }, + metadata: { count, take, skip } = {}, } = await query.graph({ entity: "subscription", ...req.queryConfig, @@ -132335,7 +132444,7 @@ import { AccountHolderDTO, CustomerDTO, PaymentMethodDTO } from "@medusajs/frame import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" export interface GetPaymentMethodStepInput { - customer: CustomerDTO & { + customer?: CustomerDTO & { account_holder: AccountHolderDTO } } @@ -132368,7 +132477,7 @@ export const getPaymentMethodStep = createStep( async ({ customer }: GetPaymentMethodStepInput, { container }) => { const paymentModuleService = container.resolve(Modules.PAYMENT) - if (!customer.account_holder) { + if (!customer?.account_holder) { throw new MedusaError( MedusaError.Types.INVALID_DATA, "No account holder found for the customer while retrieving payment method" @@ -132425,7 +132534,7 @@ import { createOrderWorkflow } from "@medusajs/medusa/core-flows" import { SubscriptionData } from "../../../modules/subscription/types" import { SUBSCRIPTION_MODULE } from "../../../modules/subscription" -type StepInput = { +export type CreateSubscriptionOrderStepInput = { subscription: SubscriptionData cart: CartWorkflowDTO payment_collection: PaymentCollectionDTO @@ -132439,7 +132548,7 @@ const createSubscriptionOrderStep = createStep( "create-subscription-order", async ({ subscription, cart, payment_collection, - }: StepInput, + }: CreateSubscriptionOrderStepInput, { container, context }) => { const linkDefs: LinkDefinition[] = [] @@ -132458,7 +132567,7 @@ const createSubscriptionOrderStep = createStep( order, }) }, - async ({ order }, { container }) => { + async (data, { container }) => { // TODO add compensation function } ) @@ -132489,20 +132598,20 @@ function getOrderData(cart: CartWorkflowDTO) { id: null, }, items: cart.items, - shipping_methods: cart.shipping_methods.map((method) => ({ + shipping_methods: cart.shipping_methods?.map((method) => ({ name: method.name, amount: method.amount, is_tax_inclusive: method.is_tax_inclusive, shipping_option_id: method.shipping_option_id, data: method.data, - tax_lines: method.tax_lines.map((taxLine) => ({ + tax_lines: method.tax_lines?.map((taxLine) => ({ description: taxLine.description, tax_rate_id: taxLine.tax_rate_id, code: taxLine.code, rate: taxLine.rate, provider_id: taxLine.provider_id, })), - adjustments: method.adjustments.map((adjustment) => ({ + adjustments: method.adjustments?.map((adjustment) => ({ code: adjustment.code, amount: adjustment.amount, description: adjustment.description, @@ -132542,11 +132651,14 @@ This adds links to be created into the `linkDefs` array between the new order an Finally, replace the `TODO` in the compensation function to cancel the order in case of an error: ```ts title="src/workflows/create-subscription-order/steps/create-subscription-order.ts" +if (!data) { + return +} const orderModuleService: IOrderModuleService = container.resolve( Modules.ORDER ) -await orderModuleService.cancel(order.id) +await orderModuleService.cancel(data.order.id) ``` ### Create updateSubscriptionStep (Ninth Step) @@ -132620,7 +132732,7 @@ const updateSubscriptionStep = createStep( }) }, async ({ - prev_data, + data, }, { container }) => { // TODO add compensation } @@ -132636,15 +132748,18 @@ Before updating the subscription, the step retrieves the old data and passes it So, replace the `TODO` in the compensation function with the following: ```ts title="src/workflows/create-subscription-order/steps/update-subscription.ts" +if (!data) { + return +} const subscriptionModuleService: SubscriptionModuleService = container.resolve( SUBSCRIPTION_MODULE ) await subscriptionModuleService.updateSubscriptions({ - id: prev_data.id, - last_order_date: prev_data.last_order_date, - next_order_date: prev_data.next_order_date, + id: data.prev_data.id, + last_order_date: data.prev_data.last_order_date, + next_order_date: data.prev_data.next_order_date, }) ``` @@ -132654,7 +132769,7 @@ This updates the subscription’s `last_order_date` and `next_order_date` proper Finally, create the file `src/workflows/create-subscription-order/index.ts` with the following content: -```ts title="src/workflows/create-subscription-order/index.ts" highlights={createSubscriptionOrderWorkflowHighlights} collapsibleLines="1-18" expandMoreLabel="Show Imports" +```ts title="src/workflows/create-subscription-order/index.ts" highlights={createSubscriptionOrderWorkflowHighlights} collapsibleLines="1-23" expandMoreLabel="Show Imports" import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep, @@ -132669,9 +132784,14 @@ import { authorizePaymentSessionStep, createPaymentCollectionsStep, } from "@medusajs/medusa/core-flows" -import createSubscriptionOrderStep from "./steps/create-subscription-order" +import createSubscriptionOrderStep, { + CreateSubscriptionOrderStepInput, +} from "./steps/create-subscription-order" import updateSubscriptionStep from "./steps/update-subscription" -import { getPaymentMethodStep } from "./steps/get-payment-method" +import { + getPaymentMethodStep, + GetPaymentMethodStepInput, +} from "./steps/get-payment-method" type WorkflowInput = { subscription: SubscriptionData @@ -132711,9 +132831,9 @@ const createSubscriptionOrderWorkflow = createWorkflow( }, (data) => { const cart = data.subscriptions[0].cart return { - currency_code: cart.currency_code, - amount: cart.payment_collection.amount, - metadata: cart.payment_collection.metadata, + currency_code: cart?.currency_code || "", + amount: cart?.payment_collection?.amount || 0, + metadata: cart?.payment_collection?.metadata || undefined, } }) @@ -132733,7 +132853,7 @@ const createSubscriptionOrderWorkflow = createWorkflow( return { payment_collection_id: data.payment_collection.id, provider_id: "pp_stripe_stripe", - customer_id: data.subscriptions[0].cart.customer.id, + customer_id: data.subscriptions[0].cart?.customer?.id, data: { payment_method: data.defaultPaymentMethod.id, off_session: true, @@ -132756,7 +132876,7 @@ const createSubscriptionOrderWorkflow = createWorkflow( subscription: input.subscription, cart: carts[0], payment_collection, - }) + } as unknown as CreateSubscriptionOrderStepInput) createRemoteLinkStep(linkDefs) diff --git a/www/apps/resources/app/commerce-modules/order/transactions/page.mdx b/www/apps/resources/app/commerce-modules/order/transactions/page.mdx index 64d1a5443a..63e1e25217 100644 --- a/www/apps/resources/app/commerce-modules/order/transactions/page.mdx +++ b/www/apps/resources/app/commerce-modules/order/transactions/page.mdx @@ -112,7 +112,7 @@ const { data: [order] } = await query.graph({ fields: ["*", "transactions.*"], filters: { id: "order_123", - } + }, }) const refundTransactions = order.transactions?.filter( diff --git a/www/apps/resources/app/how-to-tutorials/tutorials/agentic-commerce/page.mdx b/www/apps/resources/app/how-to-tutorials/tutorials/agentic-commerce/page.mdx index c49d49c90b..09b65cdcf6 100644 --- a/www/apps/resources/app/how-to-tutorials/tutorials/agentic-commerce/page.mdx +++ b/www/apps/resources/app/how-to-tutorials/tutorials/agentic-commerce/page.mdx @@ -197,7 +197,7 @@ export default class AgenticCommerceService { // ... async verifySignature({ signature, - payload + payload, }: { // base64 encoded signature signature: string @@ -205,18 +205,18 @@ export default class AgenticCommerceService { }) { try { // Decode the base64 signature - const receivedSignature = Buffer.from(signature, 'base64') + const receivedSignature = Buffer.from(signature, "base64") // Create HMAC-SHA256 signature using your signing key const expectedSignature = crypto - .createHmac('sha256', this.options.signatureKey) - .update(JSON.stringify(payload), 'utf8') + .createHmac("sha256", this.options.signatureKey) + .update(JSON.stringify(payload), "utf8") .digest() // Compare signatures using constant-time comparison to prevent timing attacks return crypto.timingSafeEqual(receivedSignature, expectedSignature) } catch (error) { - console.error('Signature verification failed:', error) + console.error("Signature verification failed:", error) return false } } @@ -237,8 +237,8 @@ Add the following method to the `AgenticCommerceService` class: export default class AgenticCommerceService { // ... async getSignature(data: any) { - return Buffer.from(crypto.createHmac('sha256', this.options.signatureKey) - .update(JSON.stringify(data), 'utf8').digest()).toString('base64') + return Buffer.from(crypto.createHmac("sha256", this.options.signatureKey) + .update(JSON.stringify(data), "utf8").digest()).toString("base64") } } ``` @@ -276,7 +276,7 @@ export default class AgenticCommerceService { // ... async sendWebhookEvent({ type, - data + data, }: AgenticCommerceWebhookEvent) { // Create signature const signature = this.getSignature(data) @@ -328,7 +328,7 @@ module.exports = defineConfig({ resolve: "./src/modules/agentic-commerce", options: { signatureKey: process.env.AGENTIC_COMMERCE_SIGNATURE_KEY || "supersecret", - } + }, }, ], }) @@ -487,7 +487,7 @@ export const getProductFeedItemsStep = createStep( do { const { data: products, - metadata + metadata, } = await query.graph({ entity: "product", fields: [ @@ -503,7 +503,7 @@ export const getProductFeedItemsStep = createStep( "sales_channels.*", "sales_channels.stock_locations.*", "sales_channels.stock_locations.address.*", - "categories.*" + "categories.*", ], filters: { status: "published", @@ -513,19 +513,19 @@ export const getProductFeedItemsStep = createStep( calculated_price: QueryContext({ currency_code: currencyCode, }), - } + }, }, pagination: { take: limit, skip: offset, - } + }, }) count = metadata?.count ?? 0 offset += limit for (const product of products) { - if (!product.variants.length) continue + if (!product.variants.length) {continue} const salesChannel = product.sales_channels?.find((channel) => { return channel?.stock_locations?.some((location) => { return location?.address?.country_code.toLowerCase() === countryCode @@ -752,11 +752,11 @@ export const sendProductFeedWorkflow = createWorkflow( const { items: feedItems } = getProductFeedItemsStep(input) const xml = buildProductFeedXmlStep({ - items: feedItems + items: feedItems, }) sendProductFeedStep({ - productFeed: xml + productFeed: xml, }) return new WorkflowResponse({ xml }) @@ -786,9 +786,9 @@ Create the file `src/jobs/sync-product-feed.ts` with the following content: ```ts title="src/jobs/sync-product-feed.ts" import { - MedusaContainer -} from "@medusajs/framework/types"; -import sendProductFeedWorkflow from "../workflows/send-product-feed"; + MedusaContainer, +} from "@medusajs/framework/types" +import sendProductFeedWorkflow from "../workflows/send-product-feed" export default async function syncProductFeed(container: MedusaContainer) { const logger = container.resolve("logger") @@ -816,7 +816,7 @@ export default async function syncProductFeed(container: MedusaContainer) { export const config = { name: "sync-product-feed", schedule: "*/15 * * * *", // Every 15 minutes -}; +} ``` In a scheduled job file, you must export: @@ -890,11 +890,11 @@ To create the workflow, create the file `src/workflows/prepare-checkout-session- import { createWorkflow, transform, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { listShippingOptionsForCartWithPricingWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" export type PrepareCheckoutSessionDataWorkflowInput = { @@ -960,21 +960,21 @@ const { data: carts } = useQueryGraphStep({ "original_item_total", "shipping_total", "metadata", - "order.id" + "order.id", ], filters: { id: input.cart_id, }, options: { - throwIfKeyNotFound: true - } + throwIfKeyNotFound: true, + }, }) // Retrieve shipping options const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) // TODO prepare response @@ -1022,7 +1022,7 @@ const responseData = transform({ item: { id: item?.variant_id, quantity: item?.quantity, - } + }, })), fulfillment_address: data.input.fulfillment_address, fulfillment_options: data.shippingOptions?.map((option) => ({ @@ -1080,7 +1080,7 @@ const responseData = transform({ display_name: "Total", // @ts-ignore amount: data.carts[0].total, - } + }, ], messages: data.input.messages || [], links: [ @@ -1095,8 +1095,8 @@ const responseData = transform({ { type: "seller_shop_policy", value: "https://www.medusa-commerce.com/seller-shop-policy", // TODO: replace with actual seller shop policy - } - ] + }, + ], } }) @@ -1215,7 +1215,7 @@ import { createWorkflow, transform, when, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { addShippingMethodToCartWorkflow, @@ -1223,10 +1223,10 @@ import { CreateCartWorkflowInput, createCustomersWorkflow, listShippingOptionsForCartWithPricingWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" import { - prepareCheckoutSessionDataWorkflow + prepareCheckoutSessionDataWorkflow, } from "./prepare-checkout-session-data" type WorkflowInput = { @@ -1270,7 +1270,7 @@ Replace the `TODO` in the workflow with the following: ```ts title="src/workflows/create-checkout-session.ts" // validate item IDs const variantIds = transform({ - input + input, }, (data) => { return data.input.items.map((item) => item.id) }) @@ -1280,11 +1280,11 @@ useQueryGraphStep({ entity: "variant", fields: ["id"], filters: { - id: variantIds + id: variantIds, }, options: { - throwIfKeyNotFound: true - } + throwIfKeyNotFound: true, + }, }) // TODO retrieve region and sales channel @@ -1305,9 +1305,9 @@ const { data: regions } = useQueryGraphStep({ fields: ["id"], filters: { countries: { - iso_2: "us" - } - } + iso_2: "us", + }, + }, }).config({ name: "find-region" }) // get sales channel @@ -1338,7 +1338,7 @@ const { data: customers } = useQueryGraphStep({ fields: ["id"], filters: { email: input.buyer?.email, - } + }, }).config({ name: "find-customer" }) // create customer if it does not exist @@ -1354,9 +1354,9 @@ const createdCustomers = when ({ customers }, ({ customers }) => first_name: input.buyer?.first_name, phone: input.buyer?.phone_number, has_account: false, - } - ] - } + }, + ], + }, }) }) @@ -1397,7 +1397,7 @@ const cartInput = transform({ return { items: data.input.items.map((item) => ({ variant_id: item.id, - quantity: item.quantity + quantity: item.quantity, })), region_id: data.regions[0]?.id, email: data.input.buyer?.email, @@ -1416,12 +1416,12 @@ const cartInput = transform({ sales_channel_id: data.salesChannels[0]?.id, metadata: { is_checkout_session: true, - } + }, } as CreateCartWorkflowInput }) const createdCart = createCartWorkflow.runAsStep({ - input: cartInput + input: cartInput, }) // TODO retrieve shipping options @@ -1445,7 +1445,7 @@ when(input, (input) => !!input.fulfillment_address) const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({ input: { cart_id: createdCart.id, - } + }, }) const shippingMethodData = transform({ @@ -1460,11 +1460,11 @@ when(input, (input) => !!input.fulfillment_address) cart_id: data.createdCart.id, options: [{ id: cheapestShippingOption.id, - }] + }], } }) addShippingMethodToCartWorkflow.runAsStep({ - input: shippingMethodData + input: shippingMethodData, }) }) @@ -1488,7 +1488,7 @@ const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({ buyer: input.buyer, fulfillment_address: input.fulfillment_address, cart_id: createdCart.id, - } + }, }) return new WorkflowResponse(responseData) @@ -1555,7 +1555,7 @@ export const POST = async ( input: req.validatedBody, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -1569,8 +1569,8 @@ export const POST = async ( code: "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }) } } @@ -1593,8 +1593,8 @@ Next, you'll create a [middleware](!docs!/learn/fundamentals/api-routes/middlewa Create the file `src/api/middlewares/validate-agentic-request.ts` with the following content: ```ts title="src/api/middlewares/validate-agentic-request.ts" -import { MedusaNextFunction, MedusaRequest, MedusaResponse } from "@medusajs/framework"; -import { AGENTIC_COMMERCE_MODULE } from "../../modules/agentic-commerce"; +import { MedusaNextFunction, MedusaRequest, MedusaResponse } from "@medusajs/framework" +import { AGENTIC_COMMERCE_MODULE } from "../../modules/agentic-commerce" export async function validateAgenticRequest( req: MedusaRequest, @@ -1609,12 +1609,12 @@ export async function validateAgenticRequest( const isTokenValid = await apiKeyModuleService.authenticate(apiKey || "") const isSignatureValid = !!req.body || await agenticCommerceModuleService.verifySignature({ signature, - payload: req.body + payload: req.body, }) if (!isTokenValid || !isSignatureValid) { return res.status(401).json({ - message: "Unauthorized" + message: "Unauthorized", }) } @@ -1642,24 +1642,24 @@ You apply middlewares in the `src/api/middlewares.ts` file. Create this file wit import { defineMiddlewares, validateAndTransformBody, -} from "@medusajs/framework/http"; -import { validateAgenticRequest } from "./middlewares/validate-agentic-request"; -import { PostCreateSessionSchema } from "./checkout_sessions/route"; +} from "@medusajs/framework/http" +import { validateAgenticRequest } from "./middlewares/validate-agentic-request" +import { PostCreateSessionSchema } from "./checkout_sessions/route" export default defineMiddlewares({ routes: [ { matcher: "/checkout_sessions*", middlewares: [ - validateAgenticRequest - ] + validateAgenticRequest, + ], }, { matcher: "/checkout_sessions", method: ["POST"], - middlewares: [validateAndTransformBody(PostCreateSessionSchema)] + middlewares: [validateAndTransformBody(PostCreateSessionSchema)], }, - ] + ], }) ``` @@ -1678,7 +1678,7 @@ In `src/api/middlewares.ts`, add the following import at the top of the file: ```ts title="src/api/middlewares.ts" import { errorHandler, -} from "@medusajs/framework/http"; +} from "@medusajs/framework/http" const originalErrorHandler = errorHandler() ``` @@ -1702,8 +1702,8 @@ export default defineMiddlewares({ code: "invalid", content_type: "plain", content: error.message, - } - ] + }, + ], }) }, }) @@ -1935,16 +1935,16 @@ import { createWorkflow, transform, when, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { addShippingMethodToCartWorkflow, createCustomersWorkflow, updateCartWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" import { - prepareCheckoutSessionDataWorkflow + prepareCheckoutSessionDataWorkflow, } from "./prepare-checkout-session-data" type WorkflowInput = { @@ -1980,7 +1980,7 @@ export const updateCheckoutSessionWorkflow = createWorkflow( fields: ["id", "customer.*", "email"], filters: { id: input.cart_id, - } + }, }) // TODO retrieve or create customer @@ -2003,7 +2003,7 @@ const { data: customers } = useQueryGraphStep({ fields: ["id"], filters: { email: input.buyer?.email, - } + }, }).config({ name: "find-customer" }) const createdCustomers = when({ customers }, ({ customers }) => @@ -2017,9 +2017,9 @@ const createdCustomers = when({ customers }, ({ customers }) => email: input.buyer?.email, first_name: input.buyer?.first_name, phone: input.buyer?.phone_number, - } + }, ], - } + }, }) }) @@ -2057,7 +2057,7 @@ when(input, (input) => !!input.items) }, options: { throwIfKeyNotFound: true, - } + }, }).config({ name: "find-variant" }) }) @@ -2133,7 +2133,7 @@ const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({ cart_id: updateData.id, buyer: input.buyer, fulfillment_address: input.fulfillment_address, - } + }, }) return new WorkflowResponse(responseData) @@ -2199,7 +2199,7 @@ export const POST = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -2209,7 +2209,7 @@ export const POST = async ( await refreshPaymentCollectionForCartWorkflow(req.scope).run({ input: { cart_id: req.params.id, - } + }, }) const { result } = await prepareCheckoutSessionDataWorkflow(req.scope) @@ -2224,8 +2224,8 @@ export const POST = async ( "payment_declined" : "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }, }) @@ -2249,7 +2249,7 @@ Finally, you'll apply the validation middleware to the `POST /checkout_sessions/ In `src/api/middlewares.ts`, add the following import at the top of the file: ```ts title="src/api/middlewares.ts" -import { PostUpdateSessionSchema } from "./checkout_sessions/[id]/route"; +import { PostUpdateSessionSchema } from "./checkout_sessions/[id]/route" ``` Then, add a new route configuration in `defineMiddlewares`: @@ -2261,7 +2261,7 @@ export default defineMiddlewares({ { matcher: "/checkout_sessions/:id", method: ["POST"], - middlewares: [validateAndTransformBody(PostUpdateSessionSchema)] + middlewares: [validateAndTransformBody(PostUpdateSessionSchema)], }, ], // ... @@ -2346,7 +2346,7 @@ export const GET = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).status(201).json(result) @@ -2499,7 +2499,7 @@ import { createWorkflow, transform, when, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { completeCartWorkflow, @@ -2507,11 +2507,11 @@ import { createPaymentSessionsWorkflow, refreshPaymentCollectionForCartWorkflow, updateCartWorkflow, - useQueryGraphStep + useQueryGraphStep, } from "@medusajs/medusa/core-flows" import { prepareCheckoutSessionDataWorkflow, - PrepareCheckoutSessionDataWorkflowInput + PrepareCheckoutSessionDataWorkflowInput, } from "./prepare-checkout-session-data" type WorkflowInput = { @@ -2547,7 +2547,7 @@ export const completeCheckoutSessionWorkflow = createWorkflow( "id", "region.*", "region.payment_providers.*", - "shipping_address.*" + "shipping_address.*", ], filters: { id: input.cart_id, @@ -2589,7 +2589,7 @@ when(input, (input) => !!input.payment_data.billing_address) postal_code: data.input.payment_data.billing_address!.postal_code, country_code: data.input.payment_data.billing_address!.country, phone: data.input.payment_data.billing_address!.phone_number, - } + }, } }) return updateCartWorkflow.runAsStep({ @@ -2628,7 +2628,7 @@ const preparationInput = transform({ }) const paymentProviderId = transform({ - input + input, }, (data) => { switch (data.input.payment_data.provider) { case "stripe": @@ -2640,7 +2640,7 @@ const paymentProviderId = transform({ const completeCartResponse = when({ carts, - paymentProviderId + paymentProviderId, }, (data) => { // @ts-ignore return !!data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId) @@ -2649,7 +2649,7 @@ const completeCartResponse = when({ const paymentCollection = createPaymentCollectionForCartWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) createPaymentSessionsWorkflow.runAsStep({ @@ -2658,14 +2658,14 @@ const completeCartResponse = when({ provider_id: paymentProviderId, data: { shared_payment_token: input.payment_data.token, - } - } + }, + }, }) completeCartWorkflow.runAsStep({ input: { id: carts[0].id, - } + }, }) return prepareCheckoutSessionDataWorkflow.runAsStep({ @@ -2695,7 +2695,7 @@ Replace the `TODO` in the workflow with the following: ```ts title="src/workflows/complete-checkout-session.ts" const invalidPaymentResponse = when({ carts, - paymentProviderId + paymentProviderId, }, (data) => { return !data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId) }) @@ -2703,7 +2703,7 @@ const invalidPaymentResponse = when({ refreshPaymentCollectionForCartWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) const prepareDataWithMessages = transform({ prepareData: preparationInput, @@ -2716,12 +2716,12 @@ const invalidPaymentResponse = when({ code: "invalid", content_type: "plain", content: "Invalid payment provider", - } - ] + }, + ], } as PrepareCheckoutSessionDataWorkflowInput }) return prepareCheckoutSessionDataWorkflow.runAsStep({ - input: prepareDataWithMessages + input: prepareDataWithMessages, }).config({ name: "prepare-checkout-session-data-with-messages" }) }) @@ -2739,7 +2739,7 @@ Finally, you'll return the appropriate response based on whether the cart was co ```ts title="src/workflows/complete-checkout-session.ts" const responseData = transform({ completeCartResponse, - invalidPaymentResponse + invalidPaymentResponse, }, (data) => { return data.completeCartResponse || data.invalidPaymentResponse }) @@ -2804,7 +2804,7 @@ export const POST = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -2814,7 +2814,7 @@ export const POST = async ( await refreshPaymentCollectionForCartWorkflow(req.scope).run({ input: { cart_id: req.params.id, - } + }, }) const { result } = await prepareCheckoutSessionDataWorkflow(req.scope) .run({ @@ -2828,8 +2828,8 @@ export const POST = async ( "payment_declined" : "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }, }) @@ -2853,7 +2853,7 @@ Finally, you'll apply the validation middleware to the `POST /checkout_sessions/ In `src/api/middlewares.ts`, add the following import at the top of the file: ```ts title="src/api/middlewares.ts" -import { PostCompleteSessionSchema } from "./checkout_sessions/[id]/complete/route"; +import { PostCompleteSessionSchema } from "./checkout_sessions/[id]/complete/route" ``` Then, add a new route configuration in `defineMiddlewares`: @@ -2865,7 +2865,7 @@ export default defineMiddlewares({ { matcher: "/checkout_sessions/:id/complete", method: ["POST"], - middlewares: [validateAndTransformBody(PostCompleteSessionSchema)] + middlewares: [validateAndTransformBody(PostCompleteSessionSchema)], }, ], // ... @@ -3190,14 +3190,14 @@ export const cancelCheckoutSessionWorkflow = createWorkflow( "id", "payment_collection.*", "payment_collection.payment_sessions.*", - "order.id" + "order.id", ], filters: { id: input.cart_id, }, options: { throwIfKeyNotFound: true, - } + }, }) validateCartCancelationStep({ @@ -3217,11 +3217,11 @@ Next, you'll cancel the payment sessions if there are any. Replace the `TODO` in ```ts title="src/workflows/cancel-checkout-session.ts" when({ - carts + carts, }, (data) => !!data.carts[0].payment_collection?.payment_sessions?.length) .then(() => { const paymentSessionIds = transform({ - carts + carts, }, (data) => { return data.carts[0].payment_collection?.payment_sessions?.map((session) => session!.id) }) @@ -3235,8 +3235,8 @@ updateCartWorkflow.runAsStep({ id: carts[0].id, metadata: { checkout_session_canceled: true, - } - } + }, + }, }) // TODO prepare and return response @@ -3252,7 +3252,7 @@ Finally, you'll prepare and return the checkout session response. Replace the `T const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({ input: { cart_id: carts[0].id, - } + }, }) return new WorkflowResponse(responseData) @@ -3287,7 +3287,7 @@ export const POST = async ( }, context: { idempotencyKey: req.headers["idempotency-key"] as string, - } + }, }) res.set(responseHeaders).json(result) @@ -3300,8 +3300,8 @@ export const POST = async ( code: "invalid", content_type: "plain", content: medusaError.message, - } - ] + }, + ], }) } } @@ -3382,7 +3382,7 @@ export default async function orderWebhookHandler({ ], filters: { id: orderId, - } + }, }) // only send webhook if order is associated with a checkout session @@ -3404,7 +3404,7 @@ export default async function orderWebhookHandler({ type: "original_payment", amount: transaction!.amount * -1, })) || [], - } + }, } // set status based on order, fulfillments and transactions diff --git a/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx index 348f9bd844..b1af9741a9 100644 --- a/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/caching/concepts/page.mdx @@ -22,7 +22,7 @@ const key = await cachingModuleService.computeKey(data) await cachingModuleService.set({ key, tags: ["Product:prod_123", "Product:list:*"], - data + data, }) ``` @@ -79,7 +79,7 @@ const key = await cachingModuleService.computeKey(data) await cachingModuleService.set({ key, tags: ["Product:list:*", "Product:prod_123"], - data + data, }) ``` @@ -170,7 +170,7 @@ const key = await cachingModuleService.computeKey(data) await cachingModuleService.set({ key, tags: ["Product:prod_123", "Product:list:*"], - data + data, }) ``` diff --git a/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx index 2091959751..cc2bbacc4a 100644 --- a/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/caching/guides/memcached/page.mdx @@ -108,14 +108,14 @@ export default async function connection({ serverUrls = ["127.0.0.1:11211"], username, password, - options: clientOptions + options: clientOptions, } = options || {} try { logger.info("Connecting to Memcached...") // Create Memcached client - const client = memjs.Client.create(serverUrls.join(','), { + const client = memjs.Client.create(serverUrls.join(","), { username, password, ...clientOptions, @@ -271,9 +271,9 @@ class MemcachedCachingProviderService implements ICachingProviderService { return { data, compressed: false } } - const buffer = Buffer.from(data, 'utf8') + const buffer = Buffer.from(data, "utf8") const compressed = await deflateAsync(buffer) - const compressedData = compressed.toString('base64') + const compressedData = compressed.toString("base64") // Only use compression if it actually reduces size if (compressedData.length < data.length) { @@ -291,9 +291,9 @@ class MemcachedCachingProviderService implements ICachingProviderService { return data } - const buffer = Buffer.from(data, 'base64') + const buffer = Buffer.from(data, "base64") const decompressed = await inflateAsync(buffer) - return decompressed.toString('utf8') + return decompressed.toString("utf8") } } ``` @@ -411,12 +411,12 @@ class MemcachedCachingProviderService implements ICachingProviderService { // Compress data if enabled const { data: finalData, - compressed + compressed, } = await this.compressData(serializedData) // Batch operations for better performance const operations: Promise[] = [ - this.client.set(prefixedKey, finalData, setOptions) + this.client.set(prefixedKey, finalData, setOptions), ] // Always store options (including compression flag) to allow checking them later @@ -484,9 +484,9 @@ class MemcachedCachingProviderService implements ICachingProviderService { const storedTags = JSON.parse(keyTagsResult.value.toString()) // Batch all namespace checks for better performance - const tagKeys = Object.keys(storedTags).map(tag => this.TAG_PREFIX + tag) + const tagKeys = Object.keys(storedTags).map((tag) => this.TAG_PREFIX + tag) const tagResults = await Promise.all( - tagKeys.map(tagKey => this.client.get(tagKey)) + tagKeys.map((tagKey) => this.client.get(tagKey)) ) // Check if any tag namespace is missing or changed @@ -536,7 +536,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { } // Get all keys associated with each tag - const tagKeysOperations = tags.map(tag => { + const tagKeysOperations = tags.map((tag) => { const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}` return this.client.get(tagKeysKey) }) @@ -548,7 +548,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { for (const result of tagKeysResults) { if (result.value) { const keys = JSON.parse(result.value.toString()) as string[] - keys.forEach(key => allKeys.add(key)) + keys.forEach((key) => allKeys.add(key)) } } @@ -557,7 +557,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { } // Get all cached data for the collected keys - const dataOperations = Array.from(allKeys).map(async key => { + const dataOperations = Array.from(allKeys).map(async (key) => { const prefixedKey = this.CACHE_PREFIX + key const result = await this.client.get(prefixedKey) @@ -717,7 +717,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { let keys: string[] = JSON.parse(tagKeysResult.value.toString()) as string[] // Remove the specified keys - keys = keys.filter(key => !keysToRemove.includes(key)) + keys = keys.filter((key) => !keysToRemove.includes(key)) if (keys.length === 0) { // If no keys left, delete the tag keys entry @@ -755,7 +755,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { const operations: Promise[] = [ this.client.delete(this.CACHE_PREFIX + key), this.client.delete(this.OPTIONS_PREFIX + key), - this.client.delete(keyTagsKey) + this.client.delete(keyTagsKey), ] // If the key has tags, remove it from tag key lists @@ -764,7 +764,7 @@ class MemcachedCachingProviderService implements ICachingProviderService { const tagNames = Object.keys(storedTags) // Batch tag cleanup operations - const tagCleanupOperations = tagNames.map(async tag => { + const tagCleanupOperations = tagNames.map(async (tag) => { await this.removeKeysFromTag(tag, [key]) }) operations.push(...tagCleanupOperations) @@ -794,7 +794,7 @@ Add the following method to the `MemcachedCachingProviderService` class: class MemcachedCachingProviderService implements ICachingProviderService { // ... private async clearByTags(tags: string[]): Promise { - const operations = tags.map(async tag => { + const operations = tags.map(async (tag) => { const tagKey = this.TAG_PREFIX + tag const result = await this.client.increment(tagKey, 1) if (result === null) { @@ -972,7 +972,7 @@ module.exports = defineConfig({ resolve: "@medusajs/medusa/caching", options: { in_memory: { - enable: true + enable: true, }, providers: [ { @@ -981,16 +981,16 @@ module.exports = defineConfig({ // Optional, makes this the default caching provider is_default: true, options: { - serverUrls: process.env.MEMCACHED_SERVERS?.split(',') || + serverUrls: process.env.MEMCACHED_SERVERS?.split(",") || ["127.0.0.1:11211"], // add other optional options here... }, }, // other caching providers... ], - } - } - ] + }, + }, + ], }) ``` @@ -1051,7 +1051,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { key: "test-cache-products", // If you didn't set is_default to true, uncomment the following line // providers: ["caching-memcached"], - } + }, }) res.status(200).json({ diff --git a/www/apps/resources/app/infrastructure-modules/caching/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/page.mdx index 6637ce8f25..e2c23595c0 100644 --- a/www/apps/resources/app/infrastructure-modules/caching/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/caching/page.mdx @@ -74,10 +74,10 @@ module.exports = defineConfig({ // more options... }, }, - ] - } - } - ] + ], + }, + }, + ], }) ``` @@ -143,7 +143,7 @@ For example: ```ts highlights={[["14"], ["15"], ["16"]]} import { createWorkflow, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "@medusajs/medusa/core-flows" @@ -201,7 +201,7 @@ export const getBrandStep = createStep( const cacheKey = await cachingModuleService.computeKey(input.filters) const cachedValue = await cachingModuleService.get({ - tags: ["brand"] + tags: ["brand"], }) if (cachedValue) { @@ -213,7 +213,7 @@ export const getBrandStep = createStep( await cachingModuleService.set({ key: cacheKey, tags: [`Brand:${brand.id}`], - data: brand + data: brand, }) return new StepResponse(brand) diff --git a/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx index 460c62f2bd..e6fef75dda 100644 --- a/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/caching/providers/page.mdx @@ -102,11 +102,11 @@ module.exports = defineConfig({ options: { // Memcached options... }, - } - ] - } - } - ] + }, + ], + }, + }, + ], }) ``` @@ -130,7 +130,7 @@ const { data: products } = useQueryGraphStep({ options: { cache: { enable: true, - providers: ["caching-memcached"] // Specify Memcached provider + providers: ["caching-memcached"], // Specify Memcached provider }, }, }) @@ -145,7 +145,7 @@ const cachingModuleService = container.resolve(Modules.CACHING) await cachingModuleService.set({ key: "product-list", data: products, - providers: ["caching-memcached"] // Specify Memcached provider + providers: ["caching-memcached"], // Specify Memcached provider }) ``` diff --git a/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx b/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx index 6389a8bd0c..010e208960 100644 --- a/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx +++ b/www/apps/resources/app/infrastructure-modules/caching/providers/redis/page.mdx @@ -146,7 +146,7 @@ For example, you can create a workflow in `src/workflows/cache-products.ts` that ```ts title="src/workflows/cache-products.ts" highlights={[["14"], ["15"], ["16"], ["17"]]} import { createWorkflow, - WorkflowResponse + WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "@medusajs/medusa/core-flows" diff --git a/www/apps/resources/app/integrations/guides/meilisearch/page.mdx b/www/apps/resources/app/integrations/guides/meilisearch/page.mdx index f894ff6ba9..ab79794848 100644 --- a/www/apps/resources/app/integrations/guides/meilisearch/page.mdx +++ b/www/apps/resources/app/integrations/guides/meilisearch/page.mdx @@ -406,6 +406,7 @@ If you get a type error on resolving the Meilisearch Module, run the Medusa appl import { ProductDTO } from "@medusajs/framework/types" import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" import { MEILISEARCH_MODULE } from "../../modules/meilisearch" +import MeilisearchModuleService from "../../modules/meilisearch/service" export type SyncProductsStepInput = { products: ProductDTO[] @@ -414,7 +415,7 @@ export type SyncProductsStepInput = { export const syncProductsStep = createStep( "sync-products", async ({ products }: SyncProductsStepInput, { container }) => { - const meilisearchModuleService = container.resolve( + const meilisearchModuleService = container.resolve( MEILISEARCH_MODULE ) const existingProducts = await meilisearchModuleService.retrieveFromIndex( @@ -470,7 +471,7 @@ export const syncProductsStep = createStep( return } - const meilisearchModuleService = container.resolve( + const meilisearchModuleService = container.resolve( MEILISEARCH_MODULE ) diff --git a/www/apps/resources/app/integrations/guides/resend/page.mdx b/www/apps/resources/app/integrations/guides/resend/page.mdx index 90ef849d5d..68d4e8b53f 100644 --- a/www/apps/resources/app/integrations/guides/resend/page.mdx +++ b/www/apps/resources/app/integrations/guides/resend/page.mdx @@ -1200,13 +1200,14 @@ Create the file `src/workflows/send-order-confirmation.ts` with the following co export const workflowHighlights = [ ["13", "sendOrderConfirmationWorkflow", "Create the workflow that sends an order confirmation email."], - ["15", "useQueryGraphStep", "Retrieve the order's details."], - ["43", "sendNotificationStep", "Send the order confirmation email."] + ["16", "useQueryGraphStep", "Retrieve the order's details."], + ["48", "sendNotificationStep", "Send the order confirmation email."] ] ```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights} import { createWorkflow, + when, WorkflowResponse, } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep } from "@medusajs/medusa/core-flows" @@ -1244,18 +1245,26 @@ export const sendOrderConfirmationWorkflow = createWorkflow( filters: { id, }, + options: { + throwIfKeyNotFound: true, + }, }) - const notification = sendNotificationStep([{ - to: orders[0].email, - channel: "email", - template: "order-placed", - data: { - order: orders[0], - }, - }]) + const notification = when({ orders }, (data) => !!data.orders[0].email) + .then(() => { + return sendNotificationStep([{ + to: orders[0].email!, + channel: "email", + template: "order-placed", + data: { + order: orders[0], + }, + }]) + }) - return new WorkflowResponse(notification) + return new WorkflowResponse({ + notification, + }) } ) ``` @@ -1265,7 +1274,7 @@ You create a workflow using `createWorkflow` from the Workflows SDK. It accepts It accepts as a second parameter a constructor function, which is the workflow's implementation. The workflow has the following steps: 1. `useQueryGraphStep`, which is a step implemented by Medusa that uses [Query](!docs!/learn/fundamentals/module-links/query), a tool that allows you to retrieve data across modules. You use it to retrieve the order's details. -2. `sendNotificationStep` which is the step you implemented. You pass it an array with one object, which is the notification's details having following properties: +2. Ensure that the order has an email address by using `when-then`. If so, you send the notification using the `sendNotificationStep` you implemented earlier. You pass it an object with the following properties: - `to`: The address to send the email to. You pass the customer's email that is stored in the order. - `channel`: The channel to send the notification through, which is `email`. Since you specified `email` in the Resend Module Provider's `channel` option, the Notification Module will delegate the sending to the Resend Module Provider's service. - `template`: The email's template type. You retrieve the template content in the `ResendNotificationProviderService`'s `send` method based on the template specified here. @@ -1273,7 +1282,7 @@ It accepts as a second parameter a constructor function, which is the workflow's -A workflow's constructor function has some constraints in implementation. Learn more about them in [this documentation](!docs!/learn/fundamentals/workflows/constructor-constraints). +`when` allows you to perform steps based on a condition during execution. Learn more in the [Conditions in Workflows](!docs!/learn/fundamentals/workflows/conditions) documentation. diff --git a/www/apps/resources/app/nextjs-starter/guides/storefront-returns/page.mdx b/www/apps/resources/app/nextjs-starter/guides/storefront-returns/page.mdx index 710fd8517d..8e4892dee3 100644 --- a/www/apps/resources/app/nextjs-starter/guides/storefront-returns/page.mdx +++ b/www/apps/resources/app/nextjs-starter/guides/storefront-returns/page.mdx @@ -215,7 +215,7 @@ export const createReturnRequest = async ( return_shipping: { option_id: returnShippingOptionId, }, - location_id: locationId + location_id: locationId, }, headers, }) @@ -671,7 +671,7 @@ export const returnShippingSelectorHighlights = [ import React, { useEffect, - useState + useState, } from "react" import { RadioGroup } from "@headlessui/react" import { HttpTypes } from "@medusajs/types" @@ -911,7 +911,7 @@ const ReturnRequestTemplate: React.FC = ({ formData.append("items", JSON.stringify(selectedItems)) formData.append("return_shipping_option_id", selectedShippingOption) // @ts-expect-error issue in HTTP types - const locationId = shippingOptions.find(opt => opt.id === selectedShippingOption)?.service_zone.fulfillment_set.location.id + const locationId = shippingOptions.find((opt) => opt.id === selectedShippingOption)?.service_zone.fulfillment_set.location.id formData.append("location_id", locationId) formAction(formData) } 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 5b74a3dd79..bd7eea1cea 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 @@ -450,7 +450,7 @@ export const validateVariantOutOfStockStep = createStep( sales_channel_id, }) - if (availability[variant_id].availability > 0) { + if ((availability[variant_id].availability || 0) > 0) { throw new MedusaError( MedusaError.Types.INVALID_DATA, "Variant isn't out of stock." @@ -1212,7 +1212,7 @@ export const getRestockedStep = createStep( sales_channel_id: restockSubscription.sales_channel_id, }) - if (variantAvailability[restockSubscription.variant_id].availability > 0) { + if ((variantAvailability[restockSubscription.variant_id].availability || 0) > 0) { restocked.push(restockSubscription) } }) 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 ee088c7ec2..dad826a384 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 @@ -461,7 +461,7 @@ Create the file `src/workflows/create-digital-product/steps/create-digital-produ export const createDpHighlights = [ ["19", "createDigitalProducts", "Create the digital product."], ["24", "digital_product", "Pass the digital product to the compensation function."], - ["31", "deleteDigitalProducts", "Delete the digital product if an error occurs in the workflow."] + ["34", "deleteDigitalProducts", "Delete the digital product if an error occurs in the workflow."] ] ```ts title="src/workflows/create-digital-product/steps/create-digital-product.ts" highlights={createDpHighlights} collapsibleLines="1-7" expandMoreLabel="Show Imports" @@ -491,12 +491,15 @@ const createDigitalProductStep = createStep( digital_product: digitalProduct, }) }, - async ({ digital_product }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) await digitalProductModuleService.deleteDigitalProducts( - digital_product.id + data.digital_product.id ) } ) @@ -515,7 +518,7 @@ Create the file `src/workflows/create-digital-product/steps/create-digital-produ export const createDigitalProductMediaHighlights = [ ["29", "createDigitalProductMedias", "Create the digital product medias."], ["34", "digital_product_medias", "Pass the digital product medias to the compensation function."], - ["41", "deleteDigitalProductMedias", "Delete the digital product medias if an error occurs in the workflow."] + ["44", "deleteDigitalProductMedias", "Delete the digital product medias if an error occurs in the workflow."] ] ```ts title="src/workflows/create-digital-product/steps/create-digital-product-medias.ts" highlights={createDigitalProductMediaHighlights} collapsibleLines="1-8" expandMoreLabel="Show Imports" @@ -555,12 +558,15 @@ const createDigitalProductMediasStep = createStep( digital_product_medias: digitalProductMedias, }) }, - async ({ digital_product_medias }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) await digitalProductModuleService.deleteDigitalProductMedias( - digital_product_medias.map((media) => media.id) + data.digital_product_medias.map((media) => media.id) ) } ) @@ -1883,7 +1889,7 @@ export const createDpoHighlights = [ ["18", "InferTypeOf", "Infer the type of the `DigitalProduct` data model since it's a variable."], ["33", "createDigitalProductOrders", "Create the digital product order."], ["41", "digital_product_order", "Pass the created digital product order to the compensation function."], - ["48", "deleteDigitalProductOrders", "Delete the digital product order if an error occurs in the workflow."] + ["51", "deleteDigitalProductOrders", "Delete the digital product order if an error occurs in the workflow."] ] ```ts title="src/workflows/create-digital-product-order/steps/create-digital-product-order.ts" highlights={createDpoHighlights} collapsibleLines="1-14" expandMoreLabel="Show Imports" @@ -1901,7 +1907,7 @@ import DigitalProductModuleService from "../../../modules/digital-product/servic import { DIGITAL_PRODUCT_MODULE } from "../../../modules/digital-product" import DigitalProduct from "../../../modules/digital-product/models/digital-product" -type StepInput = { +export type CreateDigitalProductOrderStepInput = { items: (OrderLineItemDTO & { variant: ProductVariantDTO & { digital_product: InferTypeOf @@ -1911,7 +1917,7 @@ type StepInput = { const createDigitalProductOrderStep = createStep( "create-digital-product-order", - async ({ items }: StepInput, { container }) => { + async ({ items }: CreateDigitalProductOrderStepInput, { container }) => { const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) @@ -1929,12 +1935,15 @@ const createDigitalProductOrderStep = createStep( digital_product_order: digitalProductOrder, }) }, - async ({ digital_product_order }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const digitalProductModuleService: DigitalProductModuleService = container.resolve(DIGITAL_PRODUCT_MODULE) await digitalProductModuleService.deleteDigitalProductOrders( - digital_product_order.id + data.digital_product_order.id ) } ) @@ -1951,14 +1960,14 @@ In the compensation function, you delete the digital product order. Create the file `src/workflows/create-digital-product-order/index.ts` with the following content: 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."], - ["58", "when", "Check whether the order has any digital products."], - ["64", "then", "Perform the callback function if an order has digital products."], - ["67", "createDigitalProductOrderStep", "Create the digital product order."], - ["71", "createRemoteLinkStep", "Link the digital product order to the Medusa order."], - ["80", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."], - ["94", "emitEventStep", "Emit the `digital_product_order.created` event."] + ["29", "completeCartWorkflow", "Create an order for the cart."], + ["35", "useQueryGraphStep", "Retrieve the order's items and their associated variants and linked digital products."], + ["60", "when", "Check whether the order has any digital products."], + ["66", "then", "Perform the callback function if an order has digital products."], + ["69", "createDigitalProductOrderStep", "Create the digital product order."], + ["73", "createRemoteLinkStep", "Link the digital product order to the Medusa order."], + ["82", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."], + ["96", "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" @@ -1978,7 +1987,9 @@ import { import { Modules, } from "@medusajs/framework/utils" -import createDigitalProductOrderStep from "./steps/create-digital-product-order" +import createDigitalProductOrderStep, { + CreateDigitalProductOrderStepInput, +} from "./steps/create-digital-product-order" import { DIGITAL_PRODUCT_MODULE } from "../../modules/digital-product" type WorkflowInput = { @@ -2015,7 +2026,7 @@ const createDigitalProductOrderWorkflow = createWorkflow( orders, }, (data) => { - return data.orders[0].items.filter((item) => item.variant.digital_product !== undefined) + return data.orders[0].items?.filter((item) => item?.variant?.digital_product !== undefined) } ) @@ -2023,14 +2034,14 @@ const createDigitalProductOrderWorkflow = createWorkflow( "create-digital-product-order-condition", itemsWithDigitalProducts, (itemsWithDigitalProducts) => { - return itemsWithDigitalProducts.length + return !!itemsWithDigitalProducts?.length } ).then(() => { const { digital_product_order, } = createDigitalProductOrderStep({ items: orders[0].items, - }) + } as unknown as CreateDigitalProductOrderStepInput) createRemoteLinkStep([{ [DIGITAL_PRODUCT_MODULE]: { @@ -2047,9 +2058,9 @@ const createDigitalProductOrderWorkflow = createWorkflow( items: transform({ itemsWithDigitalProducts, }, (data) => { - return data.itemsWithDigitalProducts.map((item) => ({ - id: item.id, - quantity: item.quantity, + return data.itemsWithDigitalProducts!.map((item) => ({ + id: item!.id, + quantity: item!.quantity, })) }), }, @@ -2225,10 +2236,14 @@ import { INotificationModuleService, IFileModuleService, } from "@medusajs/framework/types" -import { ModuleRegistrationName } from "@medusajs/framework/utils" +import { + MedusaError, + ModuleRegistrationName, + promiseAll, +} from "@medusajs/framework/utils" import { DigitalProductOrder, MediaType } from "../../../modules/digital-product/types" -type SendDigitalOrderNotificationStepInput = { +export type SendDigitalOrderNotificationStepInput = { digital_product_order: DigitalProductOrder } @@ -2244,6 +2259,20 @@ export const sendDigitalOrderNotificationStep = createStep( ModuleRegistrationName.FILE ) + if (!digitalProductOrder.order) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Digital product order is missing associated order." + ) + } + + if (!digitalProductOrder.order.email) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Order is missing email." + ) + } + // TODO assemble notification } ) @@ -2251,16 +2280,16 @@ export const sendDigitalOrderNotificationStep = createStep( This creates the `sendDigitalOrderNotificationStep` step that receives a digital product order as an input. -In the step, so far you resolve the main services of the Notification and File Modules. +In the step, so far you resolve the main services of the Notification and File Modules. You also check that the digital product order has an associated order and that the order has an email. -Replace the `TODO` with the following: +Next, you'll prepare the data to pass in the notification. Replace the `TODO` with the following: ```ts title="src/workflows/fulfill-digital-order/steps/send-digital-order-notification.ts" -const notificationData = await Promise.all( +const notificationData = await promiseAll( digitalProductOrder.products.map(async (product) => { - const medias = [] + const medias: string[] = [] - await Promise.all( + await promiseAll( product.medias .filter((media) => media.type === MediaType.MAIN) .map(async (media) => { @@ -2304,12 +2333,12 @@ You use the `createNotifications` method of the Notification Module's main servi Create the workflow in the file `src/workflows/fulfill-digital-order/index.ts`: export const fulfillWorkflowHighlights = [ - ["18", "useQueryGraphStep", "Retrieve the digital product order's details."], - ["35", "sendDigitalOrderNotificationStep", "Send a notification to the customer."], - ["39", "markOrderFulfillmentAsDeliveredWorkflow", "Mark the order's fulfillment as delivered."], + ["21", "useQueryGraphStep", "Retrieve the digital product order's details."], + ["38", "sendDigitalOrderNotificationStep", "Send a notification to the customer."], + ["44", "markOrderFulfillmentAsDeliveredWorkflow", "Mark the order's fulfillment as delivered."], ] -```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports" +```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-13" expandMoreLabel="Show Imports" import { createWorkflow, WorkflowResponse, @@ -2318,7 +2347,10 @@ import { markOrderFulfillmentAsDeliveredWorkflow, useQueryGraphStep, } from "@medusajs/medusa/core-flows" -import { sendDigitalOrderNotificationStep } from "./steps/send-digital-order-notification" +import { + sendDigitalOrderNotificationStep, + SendDigitalOrderNotificationStepInput, +} from "./steps/send-digital-order-notification" type FulfillDigitalOrderWorkflowInput = { id: string @@ -2346,14 +2378,17 @@ export const fulfillDigitalOrderWorkflow = createWorkflow( sendDigitalOrderNotificationStep({ digital_product_order: digitalProductOrders[0], - }) + } as unknown as SendDigitalOrderNotificationStepInput) - markOrderFulfillmentAsDeliveredWorkflow.runAsStep({ - input: { - orderId: digitalProductOrders[0].order.id, - fulfillmentId: digitalProductOrders[0].order.fulfillments[0].id, - }, - }) + when({ digitalProductOrders }, (data) => !!data.digitalProductOrders[0].order?.fulfillments?.length) + .then(() => { + markOrderFulfillmentAsDeliveredWorkflow.runAsStep({ + input: { + orderId: digitalProductOrders[0].order!.id, + fulfillmentId: digitalProductOrders[0].order!.fulfillments![0]!.id, + }, + }) + }) return new WorkflowResponse( digitalProductOrders[0] @@ -2366,7 +2401,13 @@ In the workflow, you: 1. Retrieve the digital product order's details using `useQueryGraphStep` from Medusa's core workflows. 2. Send a notification to the customer with the digital product download links using the `sendDigitalOrderNotificationStep`. -3. Mark the order's fulfillment as delivered using `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows. +3. Check whether the Medusa order has fulfillments. If so, mark the order's fulfillment as delivered using `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows. + + + +`when` allows you to perform steps based on a condition during execution. Learn more in the [Conditions in Workflows](!docs!/learn/fundamentals/workflows/conditions) documentation. + + ### Configure Notification Module Provider @@ -2555,8 +2596,11 @@ export const GET = async ( const digitalProducts = {} - customer.orders.forEach((order) => { - order.digital_product_order.products.forEach((product) => { + customer.orders?.forEach((order) => { + order?.digital_product_order?.products.forEach((product) => { + if (!product) { + return + } digitalProducts[product.id] = product }) }) @@ -2613,9 +2657,9 @@ export const POST = async ( }, }) - const customerDigitalOrderIds = customer.orders - .filter((order) => order.digital_product_order !== undefined) - .map((order) => order.digital_product_order.id) + const customerDigitalOrderIds = customer.orders?.filter( + (order) => order?.digital_product_order !== undefined + ).map((order) => order!.digital_product_order!.id) const { data: dpoResult } = await query.graph({ entity: "digital_product_order", @@ -2634,11 +2678,11 @@ export const POST = async ( ) } - let foundMedia = undefined + let foundMedia: any | undefined = undefined dpoResult[0].products.some((product) => { - return product.medias.some((media) => { - foundMedia = media.id === req.params.mediaId ? media : undefined + return product?.medias.some((media) => { + foundMedia = media?.id === req.params.mediaId ? media : undefined return foundMedia !== undefined }) diff --git a/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx b/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx index 65f08b142f..81aa15253a 100644 --- a/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx +++ b/www/apps/resources/app/recipes/marketplace/examples/restaurant-delivery/page.mdx @@ -231,15 +231,9 @@ export enum DeliveryStatus { IN_TRANSIT = "in_transit", DELIVERED = "delivered", } - -declare module "@medusajs/framework/types" { - export interface ModuleImplementations { - deliveryModuleService: DeliveryModuleService; - } -} ``` -This adds an enum that is used by the data models. It also adds a type for `deliveryModuleService` in `ModuleImplementations` so that when you resolve it from the Medusa container, it has the correct typing. +This adds an enum that is used by the data models. ### Create Delivery Data Models @@ -489,15 +483,9 @@ import { Restaurant } from "../models/restaurant" export type CreateRestaurant = Omit< InferTypeOf, "id" | "admins" > - -declare module "@medusajs/framework/types" { - export interface ModuleImplementations { - restaurantModuleService: RestaurantModuleService; - } -} ``` -This adds a type used for inputs in creating a restaurant. It also adds a type for `restaurantModuleService` in `ModuleImplementations` so that when you resolve it from the Medusa container, it has the correct typing. +This adds a type used for inputs in creating a restaurant. @@ -920,14 +908,16 @@ const { user, authUserInput } = transform({ input, restaurantUser, driverUser }, authUserInput: { authIdentityId: data.input.auth_identity_id, actorType: data.input.user.actor_type, - value: user.id, + value: user?.id || "", }, } }) setAuthAppMetadataStep(authUserInput) -return new WorkflowResponse(user) +return new WorkflowResponse({ + user, +}) ``` In the workflow, you: @@ -987,7 +977,7 @@ export const POST = async ( } as CreateUserWorkflowInput, }) - res.status(201).json({ user: result }) + res.status(201).json({ user: result.user }) } ``` @@ -1133,12 +1123,15 @@ export const deleteRestaurantAdminStep = createStep( return new StepResponse(undefined, { admin }) }, - async ({ admin }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const restaurantModuleService: RestaurantModuleService = container.resolve( RESTAURANT_MODULE ) - const { restaurant: _, ...adminData } = admin + const { restaurant: _, ...adminData } = data.admin await restaurantModuleService.createRestaurantAdmins(adminData) } @@ -1190,6 +1183,7 @@ const { data: authIdentities } = useQueryGraphStep({ entity: "auth_identity", fields: ["id"], filters: { + // @ts-ignore app_metadata: { restaurant_id: input.id, }, @@ -1498,7 +1492,7 @@ Next, create the file `src/workflows/delivery/steps/create-delivery.ts` with the export const createDeliveryStepHighlights = [ ["11", "createDeliveries", "Create the delivery."], - ["21", "softDeleteDeliveries", "Delete the delivery if an error occurs in the workflow."] + ["24", "softDeleteDeliveries", "Delete the delivery if an error occurs in the workflow."] ] ```ts title="src/workflows/delivery/steps/create-delivery.ts" highlights={createDeliveryStepHighlights} @@ -1518,11 +1512,14 @@ export const createDeliveryStep = createStep( delivery_id: delivery.id, }) }, - async function ({ delivery_id }, { container }) { + async function (data, { container }) { + if (!data) { + return + } const deliverModuleService: DeliveryModuleService = container.resolve(DELIVERY_MODULE) - deliverModuleService.softDeleteDeliveries(delivery_id) + deliverModuleService.softDeleteDeliveries(data.delivery_id) } ) @@ -1535,10 +1532,10 @@ This step creates a delivery and returns it. In the compensation function, it de Finally, create the workflow in `src/workflows/delivery/workflows/create-delivery.ts`: export const createDeliveryWorkflowHighlights = [ - ["23", "validateRestaurantStep", "Ensure that the specified restaurant exists before creating the delivery."], - ["26", "createDeliveryStep", "Create the delivery."], - ["28", "transform", "Define an array of link objects to create."], - ["50", "createRemoteLinkStep", "Create the links between the delivery and cart and restaurant."] + ["24", "validateRestaurantStep", "Ensure that the specified restaurant exists before creating the delivery."], + ["27", "createDeliveryStep", "Create the delivery."], + ["29", "transform", "Define an array of link objects to create."], + ["51", "createRemoteLinkStep", "Create the links between the delivery and cart and restaurant."] ] ```ts title="src/workflows/delivery/workflows/create-delivery.ts" highlights={createDeliveryWorkflowHighlights} collapsibleLines="1-13" expandButtonLabel="Show Imports" @@ -1548,6 +1545,7 @@ import { createWorkflow, transform, } from "@medusajs/framework/workflows-sdk" +import { LinkDefinition } from "@medusajs/framework/types" import { Modules } from "@medusajs/framework/utils" import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" import { DELIVERY_MODULE } from "../../../modules/delivery" @@ -1589,7 +1587,7 @@ export const createDeliveryWorkflow = createWorkflow( delivery_id: data.delivery.id, }, }, - ])) + ] as LinkDefinition[])) createRemoteLinkStep(links) @@ -1787,7 +1785,7 @@ export const notifyRestaurantStep = createStep( await eventBus.emit({ name: "notify.restaurant", data: { - restaurant_id: delivery.restaurant.id, + restaurant_id: delivery.restaurant?.id, delivery_id: delivery.id, }, }) @@ -1837,7 +1835,7 @@ This step is async and its only purpose is to wait until it’s marked as succes Create the file `src/workflows/delivery/steps/create-order.ts` with the following content: export const createOrderStepHighlights1 = [ - ["15", "deliveryQuery", "Retrieve the delivery with its linked cart's details."] + ["16", "deliveryQuery", "Retrieve the delivery with its linked cart's details."] ] ```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights1} collapsibleLines="1-9" expandButtonLabel="Show Imports" @@ -1845,6 +1843,7 @@ import { CreateOrderShippingMethodDTO } from "@medusajs/framework/types" import { Modules, ContainerRegistrationKeys, + MedusaError, } from "@medusajs/framework/utils" import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" import { DELIVERY_MODULE } from "../../../modules/delivery" @@ -1871,7 +1870,7 @@ export const createOrderStep = createStep( // TODO create order }, - async ({ orderId }, { container }) => { + async (data, { container }) => { // TODO add compensation } ) @@ -1883,26 +1882,66 @@ This creates the `createOrderStep`, which so far only retrieves the delivery wit Replace the `TODO` with the following to create the order: export const createOrderStepHighlights2 = [ - ["5", "createOrders", "Create the order."], - ["18", "linkDef", "Define an array of links to be created."] + ["12", "createOrders", "Create the order."], + ["58", "linkDef", "Define an array of links to be created."] ] ```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights2} const { cart } = delivery +if (!cart) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Cart for delivery with id: ${deliveryId} was not found` + ) +} + const orderModuleService = container.resolve(Modules.ORDER) const order = await orderModuleService.createOrders({ currency_code: cart.currency_code, email: cart.email, - shipping_address: cart.shipping_address, - billing_address: cart.billing_address, - items: cart.items, - region_id: cart.region_id, - customer_id: cart.customer_id, - sales_channel_id: cart.sales_channel_id, + shipping_address: { + first_name: cart.shipping_address?.first_name || "", + last_name: cart.shipping_address?.last_name || "", + address_1: cart.shipping_address?.address_1 || "", + address_2: cart.shipping_address?.address_2 || "", + city: cart.shipping_address?.city || "", + province: cart.shipping_address?.province || "", + postal_code: cart.shipping_address?.postal_code || "", + country_code: cart.shipping_address?.country_code || "", + phone: cart.shipping_address?.phone || "", + }, + billing_address: { + first_name: cart.billing_address?.first_name || "", + last_name: cart.billing_address?.last_name || "", + address_1: cart.billing_address?.address_1 || "", + address_2: cart.billing_address?.address_2 || "", + city: cart.billing_address?.city || "", + province: cart.billing_address?.province || "", + postal_code: cart.billing_address?.postal_code || "", + country_code: cart.billing_address?.country_code || "", + phone: cart.billing_address?.phone || "", + }, + items: cart.items.map((item) => ({ + title: item!.title, + quantity: item!.quantity, + variant_id: item!.variant_id || "", + unit_price: item!.unit_price, + metadata: item!.metadata || undefined, + product_id: item!.product_id || "", + })), + region_id: cart.region_id || "", + customer_id: cart.customer_id || "", + sales_channel_id: cart.sales_channel_id || "", shipping_methods: - cart.shipping_methods as unknown as CreateOrderShippingMethodDTO[], + cart.shipping_methods?.map((sm): CreateOrderShippingMethodDTO => ({ + shipping_option_id: sm?.shipping_option_id || "", + data: sm?.data || {}, + name: sm?.name || "", + amount: sm?.amount || 0, + order_id: "", // will be set internally + })) || [], }) const linkDef = [{ @@ -1927,9 +1966,12 @@ You create the order using the Order Module’s main service. Then, you create a Then, replace the `TODO` in the compensation function with the following: ```ts title="src/workflows/delivery/steps/create-order.ts" +if (!data) { + return +} const orderService = container.resolve(Modules.ORDER) -await orderService.softDeleteOrders([orderId]) +await orderService.softDeleteOrders([data.orderId]) ``` You delete the order in the compensation function. @@ -2374,7 +2416,7 @@ So, start by creating the first step at `src/workflows/delivery/steps/update-del export const updateDeliveryStepHighlights = [ ["16", "prevDeliveryData", "Retrieve the previous data of the delivery for the compensation."], ["19", "updateDeliveries", "Update the delivery."], - ["35", "updateDeliveries", "Update the delivery with the old data if an error occurs in the workflow."] + ["38", "updateDeliveries", "Update the delivery with the old data if an error occurs in the workflow."] ] ```ts title="src/workflows/delivery/steps/update-delivery.ts" highlights={updateDeliveryStepHighlights} @@ -2403,14 +2445,17 @@ export const updateDeliveryStep = createStep( prevDeliveryData, }) }, - async ({ prevDeliveryData }, { container }) => { + async (data, { container }) => { + if (!data) { + return + } const deliverModuleService: DeliveryModuleService = container.resolve(DELIVERY_MODULE) const { driver, ...prevDeliveryDataWithoutDriver - } = prevDeliveryData + } = data.prevDeliveryData await deliverModuleService.updateDeliveries(prevDeliveryDataWithoutDriver) } @@ -2452,7 +2497,7 @@ export const setStepSuccessStep = createStep( await engineService.setStepSuccess({ idempotencyKey: { action: TransactionHandlerType.INVOKE, - transactionId: updatedDelivery.transaction_id, + transactionId: updatedDelivery.transaction_id || "", stepId, workflowId: handleDeliveryWorkflowId, }, @@ -2500,7 +2545,7 @@ export const setStepFailedStep = createStep( await engineService.setStepFailure({ idempotencyKey: { action: TransactionHandlerType.INVOKE, - transactionId: updatedDelivery.transaction_id, + transactionId: updatedDelivery.transaction_id || "", stepId, workflowId: handleDeliveryWorkflowId, }, @@ -2551,7 +2596,7 @@ export const updateDeliveryWorkflow = createWorkflow( when(input, ({ stepIdToSucceed }) => stepIdToSucceed !== undefined) .then(() => { setStepSuccessStep({ - stepId: input.stepIdToSucceed, + stepId: input.stepIdToSucceed!, updatedDelivery, }) }) @@ -2560,7 +2605,7 @@ export const updateDeliveryWorkflow = createWorkflow( when(input, ({ stepIdToFail }) => stepIdToFail !== undefined) .then(() => { setStepFailedStep({ - stepId: input.stepIdToFail, + stepId: input.stepIdToFail!, updatedDelivery, }) }) @@ -2688,7 +2733,7 @@ export const isDeliveryRestaurant = async ( }, }) - if (delivery.restaurant.id !== restaurantAdmin.restaurant.id) { + if (delivery.restaurant?.id !== restaurantAdmin.restaurant.id) { return res.status(403).json({ message: "unauthorized", }) @@ -2738,7 +2783,7 @@ import deliveriesMiddlewares from "./deliveries/[id]/middlewares" export default defineMiddlewares({ routes: [ // ... - ...deliveriesMiddlewares.routes, + ...deliveriesMiddlewares.routes!, ], }) ``` @@ -3387,7 +3432,7 @@ res.write( "data: " + JSON.stringify({ message: "Subscribed to workflow", - transactionId: delivery.transaction_id, + transactionId: delivery.transaction_id || undefined, }) + "\n\n" ) diff --git a/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx b/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx index 39890cd358..d3e0ee9776 100644 --- a/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx +++ b/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx @@ -975,6 +975,7 @@ import { } from "@medusajs/framework/workflows-sdk" import { createProductsWorkflow, + CreateProductsWorkflowInput, createRemoteLinkStep, useQueryGraphStep, } from "@medusajs/medusa/core-flows" @@ -1014,7 +1015,7 @@ const createVendorProductWorkflow = createWorkflow( }) const createdProducts = createProductsWorkflow.runAsStep({ - input: productData, + input: productData as CreateProductsWorkflowInput, }) // TODO link vendor and products @@ -1333,26 +1334,28 @@ import { createStep, StepResponse, } from "@medusajs/framework/workflows-sdk" -import { CartDTO, CartLineItemDTO } from "@medusajs/framework/types" +import { CartLineItemDTO } from "@medusajs/framework/types" import { ContainerRegistrationKeys, promiseAll } from "@medusajs/framework/utils" -type StepInput = { - cart: CartDTO +export type GroupVendorItemsStepInput = { + cart: { + items?: CartLineItemDTO[] + } } const groupVendorItemsStep = createStep( "group-vendor-items", - async ({ cart }: StepInput, { container }) => { + async ({ cart }: GroupVendorItemsStepInput, { container }) => { const query = container.resolve(ContainerRegistrationKeys.QUERY) const vendorsItems: Record = {} - await promiseAll(cart.items?.map(async (item) => { + await promiseAll((cart.items || []).map(async (item) => { const { data: [product] } = await query.graph({ entity: "product", fields: ["vendor.*"], filters: { - id: [item.product_id], + id: item.product_id || "", }, }) @@ -1451,7 +1454,7 @@ const createVendorOrdersStep = createStep( created_orders: createdOrders, }) }, - async ({ created_orders }, { container, context }) => { + async (data, { container, context }) => { // TODO add compensation function } ) @@ -1586,19 +1589,19 @@ function prepareOrderData( // item/vendor. This requires changes in the storefront to commodate that // and passing the item/vendor ID in the `data` property, for example. // For simplicity here we just use the same shipping method. - shipping_methods: parentOrder.shipping_methods.map((shippingMethod) => ({ + shipping_methods: parentOrder.shipping_methods?.map((shippingMethod) => ({ name: shippingMethod.name, amount: shippingMethod.amount, shipping_option_id: shippingMethod.shipping_option_id, data: shippingMethod.data, - tax_lines: shippingMethod.tax_lines.map((taxLine) => ({ + tax_lines: shippingMethod.tax_lines?.map((taxLine) => ({ code: taxLine.code, rate: taxLine.rate, provider_id: taxLine.provider_id, tax_rate_id: taxLine.tax_rate_id, description: taxLine.description, })), - adjustments: shippingMethod.adjustments.map((adjustment) => ({ + adjustments: shippingMethod.adjustments?.map((adjustment) => ({ code: adjustment.code, amount: adjustment.amount, description: adjustment.description, @@ -1621,7 +1624,10 @@ When creating the child orders, the shipping method of the parent is used as-is Finally, replace the `TODO` in the compensation function with the following: ```ts title="src/workflows/marketplace/create-vendor-orders/steps/create-vendor-orders.ts" -await Promise.all(created_orders.map((createdOrder) => { +if (!data) { + return +} +await promiseAll(data.created_orders.map((createdOrder) => { return cancelOrderWorkflow(container).run({ input: { order_id: createdOrder.id, @@ -1687,7 +1693,7 @@ const createVendorOrdersWorkflow = createWorkflow( const { vendorsItems } = groupVendorItemsStep({ cart: carts[0], - }) + } as unknown as GroupVendorItemsStepInput) const order = getOrderDetailWorkflow.runAsStep({ input: { @@ -1869,7 +1875,7 @@ export const GET = async ( ], variables: { filters: { - id: vendorAdmin.vendor.orders.map((order) => order.id), + id: vendorAdmin.vendor.orders?.map((order) => order?.id), }, }, }, diff --git a/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx b/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx index 48a8c18c32..f373b5b328 100644 --- a/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx +++ b/www/apps/resources/app/recipes/subscriptions/examples/standard/page.mdx @@ -646,7 +646,7 @@ const createSubscriptionStep = createStep( }, { subscription: subscription[0], }) - }, async ({ subscription }, { container }) => { + }, async (data, { container }) => { // TODO implement compensation } ) @@ -706,10 +706,13 @@ This adds links between: The step also has a compensation function to undo the step’s changes if an error occurs. So, replace the second `TODO` with the following: ```ts title="src/workflows/create-subscription/steps/create-subscription.ts" +if (!data) { + return +} const subscriptionModuleService: SubscriptionModuleService = - container.resolve(SUBSCRIPTION_MODULE) + container.resolve(SUBSCRIPTION_MODULE) -await subscriptionModuleService.cancelSubscriptions(subscription.id) +await subscriptionModuleService.cancelSubscriptions(data.subscription.id) ``` The compensation function receives the subscription as a parameter. It cancels the subscription. @@ -815,7 +818,7 @@ const createSubscriptionWorkflow = createWorkflow( const { subscription, linkDefs } = createSubscriptionStep({ cart_id: input.cart_id, order_id: orders[0].id, - customer_id: orders[0].customer_id, + customer_id: orders[0].customer_id!, subscription_data: input.subscription_data, }) @@ -859,9 +862,9 @@ In this step, you’ll create a custom API route similar to the [Complete Cart A Create the file `src/api/store/carts/[id]/subscribe/route.ts` with the following content: export const completeCartHighlights = [ - ["17", "graph", "Retrieve the cart to retrieve the subscription details from the `metadata`."], - ["29", "", "If the subscription data isn't set in the cart's `metadata`, throw an error"], - ["36", "createSubscriptionWorkflow", "Execute the workflow created in the previous step."] + ["18", "graph", "Retrieve the cart to retrieve the subscription details from the `metadata`."], + ["30", "", "If the subscription data isn't set in the cart's `metadata`, throw an error"], + ["37", "createSubscriptionWorkflow", "Execute the workflow created in the previous step."] ] ```ts title="src/api/store/carts/[id]/subscribe/route.ts" highlights={completeCartHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports" @@ -874,6 +877,7 @@ import { MedusaError, } from "@medusajs/framework/utils" import createSubscriptionWorkflow from "../../../../../workflows/create-subscription" +import { SubscriptionInterval } from "../../../../../modules/subscription/types" export const POST = async ( req: MedusaRequest, @@ -906,8 +910,8 @@ export const POST = async ( input: { cart_id: req.params.id, subscription_data: { - interval: metadata.subscription_interval, - period: metadata.subscription_period, + interval: metadata.subscription_interval as SubscriptionInterval, + period: metadata.subscription_period as number, }, }, }) @@ -1247,7 +1251,7 @@ export const GET = async ( const { data: subscriptions, - metadata: { count, take, skip }, + metadata: { count, take, skip } = {}, } = await query.graph({ entity: "subscription", ...req.queryConfig, @@ -1743,7 +1747,7 @@ import { AccountHolderDTO, CustomerDTO, PaymentMethodDTO } from "@medusajs/frame import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" export interface GetPaymentMethodStepInput { - customer: CustomerDTO & { + customer?: CustomerDTO & { account_holder: AccountHolderDTO } } @@ -1776,7 +1780,7 @@ export const getPaymentMethodStep = createStep( async ({ customer }: GetPaymentMethodStepInput, { container }) => { const paymentModuleService = container.resolve(Modules.PAYMENT) - if (!customer.account_holder) { + if (!customer?.account_holder) { throw new MedusaError( MedusaError.Types.INVALID_DATA, "No account holder found for the customer while retrieving payment method" @@ -1840,7 +1844,7 @@ import { createOrderWorkflow } from "@medusajs/medusa/core-flows" import { SubscriptionData } from "../../../modules/subscription/types" import { SUBSCRIPTION_MODULE } from "../../../modules/subscription" -type StepInput = { +export type CreateSubscriptionOrderStepInput = { subscription: SubscriptionData cart: CartWorkflowDTO payment_collection: PaymentCollectionDTO @@ -1854,7 +1858,7 @@ const createSubscriptionOrderStep = createStep( "create-subscription-order", async ({ subscription, cart, payment_collection, - }: StepInput, + }: CreateSubscriptionOrderStepInput, { container, context }) => { const linkDefs: LinkDefinition[] = [] @@ -1873,7 +1877,7 @@ const createSubscriptionOrderStep = createStep( order, }) }, - async ({ order }, { container }) => { + async (data, { container }) => { // TODO add compensation function } ) @@ -1904,20 +1908,20 @@ function getOrderData(cart: CartWorkflowDTO) { id: null, }, items: cart.items, - shipping_methods: cart.shipping_methods.map((method) => ({ + shipping_methods: cart.shipping_methods?.map((method) => ({ name: method.name, amount: method.amount, is_tax_inclusive: method.is_tax_inclusive, shipping_option_id: method.shipping_option_id, data: method.data, - tax_lines: method.tax_lines.map((taxLine) => ({ + tax_lines: method.tax_lines?.map((taxLine) => ({ description: taxLine.description, tax_rate_id: taxLine.tax_rate_id, code: taxLine.code, rate: taxLine.rate, provider_id: taxLine.provider_id, })), - adjustments: method.adjustments.map((adjustment) => ({ + adjustments: method.adjustments?.map((adjustment) => ({ code: adjustment.code, amount: adjustment.amount, description: adjustment.description, @@ -1962,11 +1966,14 @@ This adds links to be created into the `linkDefs` array between the new order an Finally, replace the `TODO` in the compensation function to cancel the order in case of an error: ```ts title="src/workflows/create-subscription-order/steps/create-subscription-order.ts" +if (!data) { + return +} const orderModuleService: IOrderModuleService = container.resolve( Modules.ORDER ) -await orderModuleService.cancel(order.id) +await orderModuleService.cancel(data.order.id) ``` ### Create updateSubscriptionStep (Ninth Step) @@ -2046,7 +2053,7 @@ const updateSubscriptionStep = createStep( }) }, async ({ - prev_data, + data, }, { container }) => { // TODO add compensation } @@ -2062,15 +2069,18 @@ Before updating the subscription, the step retrieves the old data and passes it So, replace the `TODO` in the compensation function with the following: ```ts title="src/workflows/create-subscription-order/steps/update-subscription.ts" +if (!data) { + return +} const subscriptionModuleService: SubscriptionModuleService = container.resolve( SUBSCRIPTION_MODULE ) await subscriptionModuleService.updateSubscriptions({ - id: prev_data.id, - last_order_date: prev_data.last_order_date, - next_order_date: prev_data.next_order_date, + id: data.prev_data.id, + last_order_date: data.prev_data.last_order_date, + next_order_date: data.prev_data.next_order_date, }) ``` @@ -2081,19 +2091,19 @@ This updates the subscription’s `last_order_date` and `next_order_date` proper Finally, create the file `src/workflows/create-subscription-order/index.ts` with the following content: export const createSubscriptionOrderWorkflowHighlights = [ - ["26", "useQueryGraphStep", "Retrieve the cart linked to the subscription."], - ["63", "createPaymentCollectionsStep", "Create a payment collection using the same information in the cart."], - ["67", "getPaymentMethodStep", "Get the customer's saved payment method."], - ["80", "data", "Pass data required by Stripe to capture the payment."], - ["89", "createPaymentSessionsWorkflow", "Create a payment session in the payment collection from the previous step."], - ["93", "authorizePaymentSessionStep", "Authorize the payment session created from the first step."], - ["98", "createSubscriptionOrderStep", "Create the new order for the subscription."], - ["104", "createRemoteLinkStep", "Create links returned by the previous step."], - ["106", "capturePaymentStep", "Capture the order’s payment."], - ["111", "updateSubscriptionStep", "Update the subscription’s `last_order_date` and `next_order_date`."] + ["31", "useQueryGraphStep", "Retrieve the cart linked to the subscription."], + ["68", "createPaymentCollectionsStep", "Create a payment collection using the same information in the cart."], + ["72", "getPaymentMethodStep", "Get the customer's saved payment method."], + ["85", "data", "Pass data required by Stripe to capture the payment."], + ["94", "createPaymentSessionsWorkflow", "Create a payment session in the payment collection from the previous step."], + ["98", "authorizePaymentSessionStep", "Authorize the payment session created from the first step."], + ["103", "createSubscriptionOrderStep", "Create the new order for the subscription."], + ["109", "createRemoteLinkStep", "Create links returned by the previous step."], + ["111", "capturePaymentStep", "Capture the order’s payment."], + ["116", "updateSubscriptionStep", "Update the subscription’s `last_order_date` and `next_order_date`."] ] -```ts title="src/workflows/create-subscription-order/index.ts" highlights={createSubscriptionOrderWorkflowHighlights} collapsibleLines="1-18" expandMoreLabel="Show Imports" +```ts title="src/workflows/create-subscription-order/index.ts" highlights={createSubscriptionOrderWorkflowHighlights} collapsibleLines="1-23" expandMoreLabel="Show Imports" import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk" import { useQueryGraphStep, @@ -2108,9 +2118,14 @@ import { authorizePaymentSessionStep, createPaymentCollectionsStep, } from "@medusajs/medusa/core-flows" -import createSubscriptionOrderStep from "./steps/create-subscription-order" +import createSubscriptionOrderStep, { + CreateSubscriptionOrderStepInput, +} from "./steps/create-subscription-order" import updateSubscriptionStep from "./steps/update-subscription" -import { getPaymentMethodStep } from "./steps/get-payment-method" +import { + getPaymentMethodStep, + GetPaymentMethodStepInput, +} from "./steps/get-payment-method" type WorkflowInput = { subscription: SubscriptionData @@ -2150,9 +2165,9 @@ const createSubscriptionOrderWorkflow = createWorkflow( }, (data) => { const cart = data.subscriptions[0].cart return { - currency_code: cart.currency_code, - amount: cart.payment_collection.amount, - metadata: cart.payment_collection.metadata, + currency_code: cart?.currency_code || "", + amount: cart?.payment_collection?.amount || 0, + metadata: cart?.payment_collection?.metadata || undefined, } }) @@ -2172,7 +2187,7 @@ const createSubscriptionOrderWorkflow = createWorkflow( return { payment_collection_id: data.payment_collection.id, provider_id: "pp_stripe_stripe", - customer_id: data.subscriptions[0].cart.customer.id, + customer_id: data.subscriptions[0].cart?.customer?.id, data: { payment_method: data.defaultPaymentMethod.id, off_session: true, @@ -2195,7 +2210,7 @@ const createSubscriptionOrderWorkflow = createWorkflow( subscription: input.subscription, cart: carts[0], payment_collection, - }) + } as unknown as CreateSubscriptionOrderStepInput) createRemoteLinkStep(linkDefs) diff --git a/www/apps/resources/app/storefront-development/guides/express-checkout/page.mdx b/www/apps/resources/app/storefront-development/guides/express-checkout/page.mdx index fe56b33839..d97ad31c55 100644 --- a/www/apps/resources/app/storefront-development/guides/express-checkout/page.mdx +++ b/www/apps/resources/app/storefront-development/guides/express-checkout/page.mdx @@ -1542,7 +1542,7 @@ return ( placeholder="Quantity" type="number" min="1" - max={selectedVariant?.inventory_quantity} + max={selectedVariant?.inventory_quantity || undefined} value={quantity} onChange={(e) => setQuantity(parseInt(e.target.value))} /> diff --git a/www/apps/resources/app/storefront-development/production-optimizations/page.mdx b/www/apps/resources/app/storefront-development/production-optimizations/page.mdx index 6440b32805..6ab3324297 100644 --- a/www/apps/resources/app/storefront-development/production-optimizations/page.mdx +++ b/www/apps/resources/app/storefront-development/production-optimizations/page.mdx @@ -138,9 +138,9 @@ const queryClient = new QueryClient({ staleTime: 1000 * 60 * 5, // 5 minutes }, }, -}); +}) -export default queryClient; +export default queryClient // product.ts // Used in components that are children of QueryClientProvider @@ -149,10 +149,10 @@ import { sdk } from "../lib/sdk" export const useProduct = (id: string) => { return useQuery(["product", id], async () => { - const response = await sdk.store.products.retrieve(id); - return response.data; - }); -}; + const response = await sdk.store.products.retrieve(id) + return response.data + }) +} export const useProductPrice = (id: string) => { return useQuery( @@ -160,14 +160,14 @@ export const useProductPrice = (id: string) => { async () => { const response = await sdk.store.products.retrieve(id, { fields: "*variants.calculated_price", - }); - return response.data.variants[0].price; + }) + return response.data.variants[0].price }, { staleTime: 0, // Always fetch fresh data } - ); -}; + ) +} ``` ### Invalidate Queries @@ -181,19 +181,19 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" import { sdk } from "../lib/sdk" export const useAddToCart = () => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useMutation( async (item) => { - await sdk.store.cart.createLineItem("cart_id", item); + await sdk.store.cart.createLineItem("cart_id", item) }, { onSuccess: () => { - queryClient.invalidateQueries(["cart"]); + queryClient.invalidateQueries(["cart"]) }, } - ); -}; + ) +} ``` --- @@ -209,7 +209,7 @@ For example, if you only need the product's `id`, `title`, and `variants.calcula ```ts const response = await sdk.store.products.retrieve("product_id", { fields: "*variants.calculated_price, id, title", -}); +}) ``` This query will return only the specified fields, reducing the response size and improving performance. @@ -221,11 +221,11 @@ const useProduct = (id: string, fields?: string) => { return useQuery({ queryKey: ["product", id, fields], queryFn: async () => { - const response = await sdk.store.products.retrieve(id, { fields }); - return response.data; + const response = await sdk.store.products.retrieve(id, { fields }) + return response.data }, - }); -}; + }) +} ``` Learn more about the `fields` parameter in the [Store API reference](!api!/store#select-fields-and-relations). @@ -366,7 +366,7 @@ export const addItemOptimistically = ( // Check if item already exists in cart const existingItemIndex = currentCart.items?.findIndex( - item => item.variant_id === newItem.variant_id + (item) => item.variant_id === newItem.variant_id ) let updatedItems: HttpTypes.StoreCartLineItem[] @@ -459,7 +459,7 @@ export const updateLineItemOptimistically = ( return null } - const updatedItems = (currentCart.items || []).map(item => { + const updatedItems = (currentCart.items || []).map((item) => { if (item.id === lineId) { return { ...item, @@ -513,7 +513,7 @@ export const removeLineItemOptimistically = ( return null } - const updatedItems = (currentCart.items || []).filter(item => item.id !== lineId) + const updatedItems = (currentCart.items || []).filter((item) => item.id !== lineId) const optimisticCart: OptimisticCart = { ...currentCart, @@ -633,7 +633,7 @@ export const createOptimisticCart = (region: HttpTypes.StoreRegion): OptimisticC export const getCurrentCart = (queryClient: QueryClient, fields?: string): HttpTypes.StoreCart | null => { return queryClient.getQueryData(queryKeys.cart.current(fields)) || queryClient.getQueriesData({ - predicate: queryKeys.cart.predicate + predicate: queryKeys.cart.predicate, })[0]?.[1] || null } ``` @@ -702,7 +702,7 @@ export const useCart = ({ fields }: { fields?: string } = {}) => { return useQuery({ queryKey: queryKeys.cart.current(fields), queryFn: () => retrieveCart({ fields }), - staleTime: 0 + staleTime: 0, }) } @@ -751,7 +751,7 @@ export const useUpdateCart = () => { mutationFn: updateCart, onSuccess: () => { queryClient.invalidateQueries({ predicate: queryKeys.cart.predicate }) - } + }, }) } @@ -906,7 +906,7 @@ export const useAddToCart = ({ fields }: { fields?: string } = {}) => { onSettled: (data) => { // Always refetch after error or success to ensure we have the latest data queryClient.invalidateQueries({ predicate: - (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined) + (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined), }) if (data) { queryClient.setQueryData(queryKeys.cart.current(fields), data) @@ -970,7 +970,7 @@ export const useUpdateLineItem = ({ fields }: { fields?: string } = {}) => { onMutate: async (variables) => { // Cancel any outgoing refetches await queryClient.cancelQueries({ - predicate: (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined) + predicate: (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined), }) // Snapshot the previous value @@ -993,7 +993,7 @@ export const useUpdateLineItem = ({ fields }: { fields?: string } = {}) => { onSettled: (data) => { // Always refetch after error or success to ensure we have the latest data queryClient.invalidateQueries({ predicate: - (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined) + (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined), }) if (data) { queryClient.setQueryData(queryKeys.cart.current(fields), data) @@ -1057,7 +1057,7 @@ export const useDeleteLineItem = ({ fields }: { fields?: string } = {}) => { onMutate: async (variables) => { // Cancel any outgoing refetches await queryClient.cancelQueries({ predicate: - (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined) + (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined), }) // Snapshot the previous value @@ -1080,7 +1080,7 @@ export const useDeleteLineItem = ({ fields }: { fields?: string } = {}) => { onSettled: (data) => { // Always refetch after error or success to ensure we have the latest data queryClient.invalidateQueries({ predicate: - (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined) + (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined), }) if (data) { queryClient.setQueryData(queryKeys.cart.current(fields), data) diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 66d58d3e41..8c0ac42e92 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -110,17 +110,17 @@ export const generatedEditDates = { "app/nextjs-starter/page.mdx": "2025-02-26T11:37:47.137Z", "app/recipes/b2b/page.mdx": "2025-05-20T07:51:40.718Z", "app/recipes/commerce-automation/page.mdx": "2025-05-20T07:51:40.719Z", - "app/recipes/digital-products/examples/standard/page.mdx": "2025-04-24T15:41:05.364Z", + "app/recipes/digital-products/examples/standard/page.mdx": "2025-10-21T10:31:35.296Z", "app/recipes/digital-products/page.mdx": "2025-05-20T07:51:40.719Z", "app/recipes/ecommerce/page.mdx": "2025-06-24T08:50:10.116Z", - "app/recipes/marketplace/examples/vendors/page.mdx": "2025-07-16T09:58:59.793Z", + "app/recipes/marketplace/examples/vendors/page.mdx": "2025-10-21T10:47:23.415Z", "app/recipes/marketplace/page.mdx": "2025-05-20T07:51:40.721Z", "app/recipes/multi-region-store/page.mdx": "2025-05-20T07:51:40.721Z", "app/recipes/omnichannel/page.mdx": "2025-05-20T07:51:40.722Z", "app/recipes/oms/page.mdx": "2025-05-20T07:51:40.722Z", "app/recipes/personalized-products/page.mdx": "2025-07-22T06:52:29.419Z", "app/recipes/pos/page.mdx": "2025-05-20T07:51:40.723Z", - "app/recipes/subscriptions/examples/standard/page.mdx": "2025-05-20T07:51:40.723Z", + "app/recipes/subscriptions/examples/standard/page.mdx": "2025-10-21T12:36:28.745Z", "app/recipes/subscriptions/page.mdx": "2025-05-20T07:51:40.723Z", "app/recipes/page.mdx": "2025-05-20T07:51:40.722Z", "app/service-factory-reference/methods/create/page.mdx": "2025-09-01T15:54:53.385Z", @@ -568,7 +568,7 @@ export const generatedEditDates = { "app/medusa-cli/commands/start/page.mdx": "2025-10-13T10:27:25.345Z", "app/medusa-cli/commands/telemtry/page.mdx": "2025-01-16T09:51:24.323Z", "app/medusa-cli/commands/user/page.mdx": "2025-09-01T15:36:38.978Z", - "app/recipes/marketplace/examples/restaurant-delivery/page.mdx": "2025-08-25T07:30:37.299Z", + "app/recipes/marketplace/examples/restaurant-delivery/page.mdx": "2025-10-21T12:04:46.051Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateCustomerGroup/page.mdx": "2024-12-09T13:21:33.569Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateReservation/page.mdx": "2025-04-11T09:04:47.498Z", "references/types/HttpTypes/interfaces/types.HttpTypes.AdminCustomerGroup/page.mdx": "2025-05-20T07:51:41.059Z", @@ -5520,7 +5520,7 @@ export const generatedEditDates = { "references/workflows/classes/workflows.WorkflowResponse/page.mdx": "2025-04-11T09:04:53.140Z", "references/workflows/interfaces/workflows.ApplyStepOptions/page.mdx": "2025-09-12T14:10:46.875Z", "references/workflows/types/workflows.WorkflowData/page.mdx": "2024-12-23T13:57:08.059Z", - "app/integrations/guides/resend/page.mdx": "2025-08-01T12:09:08.483Z", + "app/integrations/guides/resend/page.mdx": "2025-10-21T11:08:19.352Z", "references/api_key_models/variables/api_key_models.ApiKey/page.mdx": "2025-10-21T08:10:52.876Z", "references/cart/ICartModuleService/methods/cart.ICartModuleService.updateShippingMethods/page.mdx": "2025-10-21T08:10:43.389Z", "references/cart/interfaces/cart.UpdateShippingMethodDTO/page.mdx": "2025-06-25T10:11:35.052Z", @@ -5560,7 +5560,7 @@ 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-17T16:57:19.922Z", "references/types/DmlTypes/types/types.DmlTypes.RelationshipTypes/page.mdx": "2024-12-10T14:54:55.435Z", - "app/recipes/commerce-automation/restock-notification/page.mdx": "2025-09-29T15:35:49.320Z", + "app/recipes/commerce-automation/restock-notification/page.mdx": "2025-10-21T12:06:58.024Z", "app/integrations/guides/shipstation/page.mdx": "2025-05-20T07:51:40.717Z", "app/nextjs-starter/guides/customize-stripe/page.mdx": "2025-07-15T08:50:51.997Z", "references/core_flows/Cart/Workflows_Cart/functions/core_flows.Cart.Workflows_Cart.listShippingOptionsForCartWithPricingWorkflow/page.mdx": "2025-09-18T17:04:38.644Z", @@ -5722,7 +5722,7 @@ export const generatedEditDates = { "references/types/StockLocationTypes/interfaces/types.StockLocationTypes.FilterableStockLocationAddressProps/page.mdx": "2025-01-13T18:05:55.410Z", "references/types/StockLocationTypes/types/types.StockLocationTypes.UpdateStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.057Z", "references/types/StockLocationTypes/types/types.StockLocationTypes.UpsertStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.058Z", - "app/storefront-development/guides/express-checkout/page.mdx": "2025-05-20T07:51:40.724Z", + "app/storefront-development/guides/express-checkout/page.mdx": "2025-10-21T10:35:36.273Z", "app/commerce-modules/inventory/inventory-kit/page.mdx": "2025-05-20T07:51:40.708Z", "app/commerce-modules/api-key/workflows/page.mdx": "2025-01-09T13:41:46.573Z", "app/commerce-modules/api-key/js-sdk/page.mdx": "2025-01-09T12:04:39.787Z", @@ -6604,7 +6604,7 @@ export const generatedEditDates = { "references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.acquireLockStepId/page.mdx": "2025-09-15T09:52:14.218Z", "references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.releaseLockStepId/page.mdx": "2025-09-15T09:52:14.219Z", "references/core_flows/Locking/core_flows.Locking.Steps_Locking/page.mdx": "2025-09-15T09:52:14.217Z", - "app/integrations/guides/meilisearch/page.mdx": "2025-10-09T11:30:25.084Z", + "app/integrations/guides/meilisearch/page.mdx": "2025-10-21T10:51:27.358Z", "app/nextjs-starter/guides/storefront-returns/page.mdx": "2025-09-22T06:02:00.580Z", "references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.views/page.mdx": "2025-10-21T08:10:55.384Z", "app/data-model-repository-reference/methods/create/page.mdx": "2025-10-09T11:42:23.826Z",