@mercurjs/docs 2.2.1 → 2.3.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/home.mdx +107 -0
- package/content/learn/architecture.mdx +168 -83
- package/content/learn/introduction.mdx +161 -56
- package/content/learn/migration-to-2-0.mdx +187 -0
- package/content/learn/why-mercur.mdx +91 -0
- package/content/platform/attribute/concepts/attribute-types.mdx +59 -0
- package/content/platform/attribute/concepts/global-vs-inline.mdx +50 -0
- package/content/platform/attribute/concepts/variant-axes.mdx +61 -0
- package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +69 -0
- package/content/platform/attribute/guides/create-a-variant-axis.mdx +83 -0
- package/content/platform/attribute/guides/create-an-attribute.mdx +81 -0
- package/content/platform/attribute/overview.mdx +87 -0
- package/content/platform/attribute/reference/data-models.mdx +56 -0
- package/content/platform/attribute/reference/events.mdx +41 -0
- package/content/platform/attribute/reference/links.mdx +45 -0
- package/content/platform/attribute/reference/service.mdx +49 -0
- package/content/platform/attribute/reference/workflows.mdx +45 -0
- package/content/platform/catalog/concepts/master-products.mdx +54 -0
- package/content/platform/catalog/concepts/product-seller-allowlist.mdx +48 -0
- package/content/platform/catalog/concepts/status-lifecycle.mdx +58 -0
- package/content/platform/catalog/concepts/variants-categories-collections.mdx +49 -0
- package/content/platform/catalog/guides/allowlist-stores.mdx +59 -0
- package/content/platform/catalog/guides/create-a-master-product.mdx +70 -0
- package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +79 -0
- package/content/platform/catalog/overview.mdx +90 -0
- package/content/platform/catalog/reference/data-models.mdx +66 -0
- package/content/platform/catalog/reference/events.mdx +41 -0
- package/content/platform/catalog/reference/links.mdx +44 -0
- package/content/platform/catalog/reference/service.mdx +52 -0
- package/content/platform/catalog/reference/workflows.mdx +40 -0
- package/content/platform/commission/concepts/order-commission-lines.mdx +65 -0
- package/content/platform/commission/concepts/rule-matching.mdx +86 -0
- package/content/platform/commission/concepts/rules-and-rates.mdx +84 -0
- package/content/platform/commission/guides/batch-update-rules.mdx +58 -0
- package/content/platform/commission/guides/create-a-rate.mdx +72 -0
- package/content/platform/commission/guides/refresh-order-commission-lines.mdx +55 -0
- package/content/platform/commission/overview.mdx +86 -0
- package/content/platform/commission/reference/data-models.mdx +75 -0
- package/content/platform/commission/reference/events.mdx +58 -0
- package/content/platform/commission/reference/links.mdx +45 -0
- package/content/platform/commission/reference/service.mdx +51 -0
- package/content/platform/commission/reference/workflows.mdx +40 -0
- package/content/platform/offer/concepts/pricing-and-inventory.mdx +72 -0
- package/content/platform/offer/concepts/shipping.mdx +48 -0
- package/content/platform/offer/concepts/what-is-an-offer.mdx +65 -0
- package/content/platform/offer/guides/bulk-create-offers.mdx +84 -0
- package/content/platform/offer/guides/create-an-offer.mdx +83 -0
- package/content/platform/offer/guides/manage-offer-inventory.mdx +53 -0
- package/content/platform/offer/overview.mdx +84 -0
- package/content/platform/offer/reference/data-models.mdx +56 -0
- package/content/platform/offer/reference/events.mdx +39 -0
- package/content/platform/offer/reference/links.mdx +60 -0
- package/content/platform/offer/reference/service.mdx +53 -0
- package/content/platform/offer/reference/workflows.mdx +38 -0
- package/content/platform/order-group/concepts/computed-totals.mdx +59 -0
- package/content/platform/order-group/concepts/order-splitting.mdx +61 -0
- package/content/platform/order-group/concepts/the-order-group.mdx +64 -0
- package/content/platform/order-group/guides/list-order-groups.mdx +67 -0
- package/content/platform/order-group/guides/retrieve-an-order-group.mdx +63 -0
- package/content/platform/order-group/guides/split-a-cart.mdx +58 -0
- package/content/platform/order-group/overview.mdx +83 -0
- package/content/platform/order-group/reference/data-models.mdx +40 -0
- package/content/platform/order-group/reference/events.mdx +37 -0
- package/content/platform/order-group/reference/links.mdx +45 -0
- package/content/platform/order-group/reference/service.mdx +50 -0
- package/content/platform/order-group/reference/workflows.mdx +39 -0
- package/content/platform/payout/concepts/account-lifecycle.mdx +75 -0
- package/content/platform/payout/concepts/accounts-and-onboarding.mdx +70 -0
- package/content/platform/payout/concepts/payout-pipeline.mdx +99 -0
- package/content/platform/payout/guides/create-a-payout-account.mdx +59 -0
- package/content/platform/payout/guides/process-a-provider-webhook.mdx +74 -0
- package/content/platform/payout/guides/start-provider-onboarding.mdx +50 -0
- package/content/platform/payout/overview.mdx +86 -0
- package/content/platform/payout/reference/data-models.mdx +61 -0
- package/content/platform/payout/reference/events.mdx +48 -0
- package/content/platform/payout/reference/links.mdx +36 -0
- package/content/platform/payout/reference/service.mdx +53 -0
- package/content/platform/payout/reference/workflows.mdx +32 -0
- package/content/platform/product-edit/concepts/change-actions.mdx +66 -0
- package/content/platform/product-edit/concepts/change-pipeline.mdx +69 -0
- package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +66 -0
- package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +76 -0
- package/content/platform/product-edit/guides/edit-a-product.mdx +74 -0
- package/content/platform/product-edit/guides/request-a-revision.mdx +54 -0
- package/content/platform/product-edit/overview.mdx +85 -0
- package/content/platform/product-edit/reference/data-models.mdx +65 -0
- package/content/platform/product-edit/reference/events.mdx +47 -0
- package/content/platform/product-edit/reference/links.mdx +39 -0
- package/content/platform/product-edit/reference/service.mdx +43 -0
- package/content/platform/product-edit/reference/workflows.mdx +49 -0
- package/content/platform/review/concepts/product-vs-seller-reviews.mdx +63 -0
- package/content/platform/review/concepts/ratings-and-moderation.mdx +64 -0
- package/content/platform/review/concepts/the-review-model.mdx +56 -0
- package/content/platform/review/guides/compute-aggregate-ratings.mdx +57 -0
- package/content/platform/review/guides/create-a-review.mdx +55 -0
- package/content/platform/review/guides/moderate-a-review.mdx +58 -0
- package/content/platform/review/guides/respond-to-a-review.mdx +61 -0
- package/content/platform/review/overview.mdx +87 -0
- package/content/platform/review/reference/data-models.mdx +36 -0
- package/content/platform/review/reference/events.mdx +61 -0
- package/content/platform/review/reference/links.mdx +43 -0
- package/content/platform/review/reference/service.mdx +54 -0
- package/content/platform/review/reference/workflows.mdx +31 -0
- package/content/platform/store/concepts/lifecycle.mdx +62 -0
- package/content/platform/store/concepts/store-entity.mdx +53 -0
- package/content/platform/store/concepts/team.mdx +50 -0
- package/content/platform/store/guides/create-a-store.mdx +55 -0
- package/content/platform/store/guides/manage-the-team.mdx +55 -0
- package/content/platform/store/guides/moderate-a-store.mdx +59 -0
- package/content/platform/store/overview.mdx +86 -0
- package/content/platform/store/reference/data-models.mdx +89 -0
- package/content/platform/store/reference/events.mdx +43 -0
- package/content/platform/store/reference/links.mdx +71 -0
- package/content/platform/store/reference/service.mdx +51 -0
- package/content/platform/store/reference/workflows.mdx +35 -0
- package/content/references/api/admin/commission-rates/create-commission-rate.mdx +1 -1
- package/content/references/api/admin/commission-rates/list-commission-rates.mdx +2 -2
- package/content/references/api/admin/commission-rates/update-commission-rate.mdx +1 -1
- package/content/references/api/admin/offers/batch-create-offers.mdx +3 -3
- package/content/references/api/admin/order-groups/list-order-groups.mdx +1 -1
- package/content/references/api/admin/product-attributes/create-attribute-value.mdx +2 -2
- package/content/references/api/admin/product-attributes/create-product-attribute.mdx +1 -1
- package/content/references/api/admin/product-attributes/update-product-attribute.mdx +1 -1
- package/content/references/api/admin/product-changes/confirm-product-change.mdx +1 -1
- package/content/references/api/admin/products/batch-product-attributes.mdx +1 -1
- package/content/references/api/admin/products/create-product.mdx +1 -1
- package/content/references/api/admin/products/preview-product.mdx +1 -1
- package/content/references/api/admin.mdx +4 -5
- package/content/references/api/conventions.mdx +9 -7
- package/content/references/api/store/carts/add-line-item.mdx +1 -1
- package/content/references/api/store/offers/list-offers.mdx +1 -1
- package/content/references/api/store/order-groups/list-order-groups.mdx +1 -1
- package/content/references/api/store.mdx +4 -13
- package/content/references/api/vendor/members/accept-member-invite.mdx +1 -1
- package/content/references/api/vendor/offers/batch-create-offers.mdx +4 -0
- package/content/references/api/vendor/offers/batch-offer-inventory-items.mdx +2 -0
- package/content/references/api/vendor/offers/create-offer.mdx +13 -1
- package/content/references/api/vendor/offers/list-offers.mdx +4 -0
- package/content/references/api/vendor/offers/retrieve-offer.mdx +4 -0
- package/content/references/api/vendor/offers/update-offer.mdx +12 -0
- package/content/references/api/vendor/payout-accounts/create-onboarding.mdx +1 -1
- package/content/references/api/vendor/products/batch-product-attributes.mdx +1 -1
- package/content/references/api/vendor/products/create-product-variant.mdx +1 -1
- package/content/references/api/vendor/products/create-product.mdx +1 -1
- package/content/references/api/vendor/products/delete-product.mdx +1 -1
- package/content/references/api/vendor/products/update-product.mdx +1 -1
- package/content/references/api/vendor/sellers/create-seller.mdx +2 -2
- package/content/references/api/vendor/sellers/list-sellers.mdx +1 -1
- package/content/references/api/vendor.mdx +6 -5
- package/content/references/configuration.mdx +16 -33
- package/content/references/overview.mdx +34 -52
- package/content/references/panel-extensions/create-page.mdx +194 -0
- package/content/references/panel-extensions/custom-fields.mdx +256 -0
- package/content/references/panel-extensions/overview.mdx +102 -0
- package/content/references/panel-extensions/widgets.mdx +212 -0
- package/content/resources/ai/mcp.mdx +2 -2
- package/content/resources/ai/overview.mdx +21 -16
- package/content/resources/ai/skills.mdx +67 -0
- package/content/resources/best-practices/api-routes.mdx +55 -43
- package/content/resources/best-practices/custom-fields.mdx +116 -92
- package/content/resources/best-practices/frontend.mdx +62 -50
- package/content/resources/best-practices/module-links.mdx +48 -34
- package/content/resources/best-practices/modules.mdx +53 -27
- package/content/resources/best-practices/overview.mdx +45 -17
- package/content/resources/best-practices/subscribers-and-jobs.mdx +37 -24
- package/content/resources/best-practices/types.mdx +38 -23
- package/content/resources/best-practices/workflows.mdx +33 -21
- package/content/resources/customization/custom-fields.mdx +15 -15
- package/content/resources/customization/extend-a-workflow.mdx +7 -4
- package/content/resources/customization/extending-panels.mdx +55 -52
- package/content/resources/deployment/medusa-cloud.mdx +21 -20
- package/content/resources/deployment/self-host.mdx +123 -0
- package/content/resources/integrations/overview.mdx +38 -0
- package/content/resources/integrations/stripe-connect.mdx +39 -38
- package/content/resources/tutorials/add-a-block.mdx +25 -18
- package/content/resources/tutorials/add-a-widget.mdx +32 -23
- package/content/resources/tutorials/add-order-detail-button.mdx +33 -20
- package/content/resources/tutorials/attributes-and-variant-axes.mdx +28 -27
- package/content/resources/tutorials/build-a-block.mdx +26 -15
- package/content/resources/tutorials/custom-api-route.mdx +32 -20
- package/content/resources/tutorials/custom-panel-page.mdx +21 -12
- package/content/resources/tutorials/customize-navigation.mdx +30 -23
- package/content/resources/tutorials/extend-forms-and-tables.mdx +36 -28
- package/content/resources/tutorials/extend-onboarding.mdx +38 -35
- package/content/resources/tutorials/master-products-and-offers.mdx +28 -23
- package/content/telemetry.mdx +3 -3
- package/content/user-guide/admin/attributes/how-tos/create-an-attribute.mdx +64 -0
- package/content/user-guide/admin/attributes/how-tos/manage-possible-values.mdx +40 -0
- package/content/user-guide/admin/attributes/overview.mdx +22 -0
- package/content/user-guide/admin/commissions/how-tos/create-a-commission-rule.mdx +63 -0
- package/content/user-guide/admin/commissions/how-tos/edit-the-global-commission.mdx +48 -0
- package/content/user-guide/admin/commissions/how-tos/manage-a-commission-rule.mdx +45 -0
- package/content/user-guide/admin/commissions/overview.mdx +25 -0
- package/content/user-guide/admin/overview.mdx +20 -12
- package/content/user-guide/admin/product-requests/how-tos/review-a-new-product.mdx +58 -0
- package/content/user-guide/admin/product-requests/how-tos/review-a-product-edit.mdx +48 -0
- package/content/user-guide/admin/product-requests/overview.mdx +25 -0
- package/content/user-guide/vendor/offers/how-tos/create-an-offer.mdx +59 -0
- package/content/user-guide/vendor/offers/how-tos/update-prices-and-stock.mdx +40 -0
- package/content/user-guide/vendor/offers/overview.mdx +22 -0
- package/content/user-guide/vendor/onboarding.mdx +79 -0
- package/content/user-guide/vendor/orders/how-tos/fulfill-an-order.mdx +49 -0
- package/content/user-guide/vendor/orders/how-tos/mark-an-order-as-delivered.mdx +33 -0
- package/content/user-guide/vendor/orders/how-tos/process-a-return.mdx +42 -0
- package/content/user-guide/vendor/orders/how-tos/refund-an-order.mdx +38 -0
- package/content/user-guide/vendor/orders/how-tos/ship-an-order.mdx +40 -0
- package/content/user-guide/vendor/orders/overview.mdx +31 -0
- package/content/user-guide/vendor/overview.mdx +23 -12
- package/content/user-guide/vendor/products/how-tos/edit-a-product.mdx +44 -0
- package/content/user-guide/vendor/products/how-tos/submit-a-product.mdx +63 -0
- package/content/user-guide/vendor/products/overview.mdx +22 -0
- package/llms.txt +176 -142
- package/package.json +1 -1
- package/content/learn/concepts.mdx +0 -84
- package/content/learn/installation.mdx +0 -117
- package/content/learn/mirakl-alternative.mdx +0 -86
- package/content/migration/from-1-x-to-2-0.mdx +0 -152
- package/content/migration/from-2-0-to-2-1.mdx +0 -105
- package/content/migration/overview.mdx +0 -58
- package/content/references/api/store/search/search.mdx +0 -136
- package/content/references/modules/commission.mdx +0 -106
- package/content/references/modules/custom-fields.mdx +0 -45
- package/content/references/modules/media.mdx +0 -55
- package/content/references/modules/offer.mdx +0 -64
- package/content/references/modules/payout.mdx +0 -121
- package/content/references/modules/product-attribute.mdx +0 -111
- package/content/references/modules/product-edit.mdx +0 -80
- package/content/references/modules/search.mdx +0 -112
- package/content/references/modules/seller.mdx +0 -175
- package/content/references/panel-extension-api.mdx +0 -337
- package/content/references/workflows/cart/add-seller-shipping-method-to-cart.mdx +0 -48
- package/content/references/workflows/cart/complete-cart-with-split-orders.mdx +0 -36
- package/content/references/workflows/cart/list-seller-shipping-options-for-cart.mdx +0 -39
- package/content/references/workflows/cart/update-cart-seller-promotions.mdx +0 -42
- package/content/references/workflows/commission/batch-commission-rules.mdx +0 -49
- package/content/references/workflows/commission/create-commission-rates.mdx +0 -41
- package/content/references/workflows/commission/delete-commission-rates.mdx +0 -34
- package/content/references/workflows/commission/refresh-order-commission-lines.mdx +0 -30
- package/content/references/workflows/commission/update-commission-rates.mdx +0 -35
- package/content/references/workflows/media/set-category-images.mdx +0 -41
- package/content/references/workflows/media/set-collection-images.mdx +0 -41
- package/content/references/workflows/member/accept-member-invite.mdx +0 -33
- package/content/references/workflows/member/add-seller-member.mdx +0 -30
- package/content/references/workflows/member/create-member-invites.mdx +0 -34
- package/content/references/workflows/member/delete-member-invite.mdx +0 -24
- package/content/references/workflows/member/remove-seller-member.mdx +0 -28
- package/content/references/workflows/member/resend-member-invite.mdx +0 -28
- package/content/references/workflows/member/update-member-role.mdx +0 -28
- package/content/references/workflows/member/update-member.mdx +0 -35
- package/content/references/workflows/offer/batch-offer-inventory-items.mdx +0 -62
- package/content/references/workflows/offer/create-offers.mdx +0 -65
- package/content/references/workflows/offer/delete-offers.mdx +0 -35
- package/content/references/workflows/offer/update-offers.mdx +0 -53
- package/content/references/workflows/order/cancel-order-fulfillment.mdx +0 -34
- package/content/references/workflows/order/confirm-claim-request.mdx +0 -25
- package/content/references/workflows/order/confirm-exchange-request.mdx +0 -25
- package/content/references/workflows/order/confirm-order-edit-request.mdx +0 -25
- package/content/references/workflows/order/confirm-return-receive.mdx +0 -25
- package/content/references/workflows/order/create-order-fulfillment.mdx +0 -45
- package/content/references/workflows/order-group/get-order-group-detail.mdx +0 -29
- package/content/references/workflows/order-group/get-order-groups-list.mdx +0 -38
- package/content/references/workflows/overview.mdx +0 -72
- package/content/references/workflows/payout/create-onboarding.mdx +0 -36
- package/content/references/workflows/payout/create-payout-account.mdx +0 -33
- package/content/references/workflows/payout/create-payout.mdx +0 -30
- package/content/references/workflows/payout/process-payout-for-webhook.mdx +0 -34
- package/content/references/workflows/product/confirm-products.mdx +0 -48
- package/content/references/workflows/product/create-products.mdx +0 -63
- package/content/references/workflows/product/link-sellers-to-product-category.mdx +0 -40
- package/content/references/workflows/product/link-sellers-to-product.mdx +0 -40
- package/content/references/workflows/product/reject-product.mdx +0 -48
- package/content/references/workflows/product/request-product-change.mdx +0 -48
- package/content/references/workflows/product-attribute/add-product-attributes-to-product.mdx +0 -49
- package/content/references/workflows/product-attribute/create-and-link-product-attributes-to-product.mdx +0 -46
- package/content/references/workflows/product-attribute/create-product-attribute-values.mdx +0 -43
- package/content/references/workflows/product-attribute/create-product-attributes.mdx +0 -56
- package/content/references/workflows/product-attribute/delete-product-attribute-values.mdx +0 -30
- package/content/references/workflows/product-attribute/delete-product-attributes.mdx +0 -30
- package/content/references/workflows/product-attribute/remove-product-attributes-from-product.mdx +0 -29
- package/content/references/workflows/product-attribute/update-product-attribute-values.mdx +0 -44
- package/content/references/workflows/product-attribute/update-product-attributes-on-product.mdx +0 -41
- package/content/references/workflows/product-attribute/update-product-attributes.mdx +0 -48
- package/content/references/workflows/product-attribute/upsert-product-attribute-values.mdx +0 -43
- package/content/references/workflows/product-edit/auto-confirm-product-change.mdx +0 -39
- package/content/references/workflows/product-edit/cancel-product-change.mdx +0 -49
- package/content/references/workflows/product-edit/confirm-product-change.mdx +0 -57
- package/content/references/workflows/product-edit/create-product-change.mdx +0 -72
- package/content/references/workflows/product-edit/reject-product-change.mdx +0 -54
- package/content/references/workflows/product-edit/stage-product-change.mdx +0 -75
- package/content/references/workflows/seller/approve-seller.mdx +0 -36
- package/content/references/workflows/seller/create-seller-account.mdx +0 -59
- package/content/references/workflows/seller/create-seller-defaults.mdx +0 -22
- package/content/references/workflows/seller/create-sellers.mdx +0 -65
- package/content/references/workflows/seller/delete-seller-professional-details.mdx +0 -37
- package/content/references/workflows/seller/delete-sellers.mdx +0 -24
- package/content/references/workflows/seller/invite-seller.mdx +0 -28
- package/content/references/workflows/seller/suspend-seller.mdx +0 -37
- package/content/references/workflows/seller/terminate-seller.mdx +0 -37
- package/content/references/workflows/seller/unsuspend-seller.mdx +0 -36
- package/content/references/workflows/seller/unterminate-seller.mdx +0 -36
- package/content/references/workflows/seller/update-seller-address.mdx +0 -55
- package/content/references/workflows/seller/update-seller-payment-details.mdx +0 -52
- package/content/references/workflows/seller/update-seller-professional-details.mdx +0 -48
- package/content/references/workflows/seller/update-sellers.mdx +0 -57
- package/content/resources/ai/llms.mdx +0 -74
- package/content/resources/integrations/notifications.mdx +0 -39
- package/content/resources/integrations/search.mdx +0 -122
- package/content/resources/tutorials/configure-commissions.mdx +0 -127
- package/content/resources/tutorials/first-marketplace.mdx +0 -45
- package/content/resources/tutorials/handle-product-requests.mdx +0 -80
- package/content/resources/tutorials/import-export-products.mdx +0 -96
- package/content/resources/tutorials/seller-payouts-stripe.mdx +0 -89
- package/content/resources/tutorials/store-setup-checklist.mdx +0 -214
- package/content/tools/api-client.mdx +0 -155
- package/content/tools/cli.mdx +0 -196
- package/content/tools/dashboard-sdk.mdx +0 -35
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
|
|
2
|
+
title: "How-to Guides"
|
|
3
|
+
sidebarTitle: "Overview"
|
|
4
|
+
description: "How to build with Mercur: the layered architecture, the non-negotiable rules, and where each piece of logic belongs."
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
This section is a practical guide for developing on Mercur, written for both **human developers** and **AI coding agents**. It captures the conventions the codebase already follows so that new code reads as if it belongs, stays testable, and survives upgrades of the underlying Medusa framework.
|
|
7
8
|
|
|
8
9
|
<Note>
|
|
9
|
-
Mercur is a Medusa
|
|
10
|
+
Mercur is a marketplace platform built on Medusa. Every rule here is either a Medusa requirement or a Mercur convention that keeps the marketplace layer consistent. When Medusa's docs and this guide agree, follow both; when in doubt, mirror an existing module, workflow, or route in `packages/core`.
|
|
10
11
|
</Note>
|
|
11
12
|
|
|
12
13
|
## The layered architecture
|
|
@@ -29,7 +30,7 @@ graph TD
|
|
|
29
30
|
|
|
30
31
|
<CardGroup cols={2}>
|
|
31
32
|
<Card title="Module" icon="cube">
|
|
32
|
-
Owns one domain's data. Thin CRUD only
|
|
33
|
+
Owns one domain's data. Thin CRUD only, with no orchestration and no cross-module calls.
|
|
33
34
|
</Card>
|
|
34
35
|
<Card title="Workflow" icon="diagram-project">
|
|
35
36
|
Orchestrates a business operation across modules, step by step, with automatic rollback (compensation) on failure.
|
|
@@ -38,20 +39,20 @@ graph TD
|
|
|
38
39
|
A thin HTTP adapter: validate input, run a workflow (or query for reads), shape the response.
|
|
39
40
|
</Card>
|
|
40
41
|
<Card title="Frontend" icon="window">
|
|
41
|
-
Admin/Vendor panels and storefront. Talks to the API only through the typed SDK
|
|
42
|
+
Admin/Vendor panels and storefront. Talks to the API only through the typed SDK, never raw `fetch`.
|
|
42
43
|
</Card>
|
|
43
44
|
</CardGroup>
|
|
44
45
|
|
|
45
46
|
Why this shape matters:
|
|
46
47
|
|
|
47
|
-
- **Testability
|
|
48
|
-
- **Reusability
|
|
49
|
-
- **Upgrade safety
|
|
50
|
-
- **Rollback
|
|
48
|
+
- **Testability:** business logic lives in workflows, which can be run in isolation without an HTTP request.
|
|
49
|
+
- **Reusability:** a workflow can be called from a route, a subscriber, or a scheduled job.
|
|
50
|
+
- **Upgrade safety:** modules stay thin, so Medusa framework upgrades rarely touch your logic.
|
|
51
|
+
- **Rollback:** because mutations are workflow steps, a failure halfway through automatically undoes the earlier steps.
|
|
51
52
|
|
|
52
53
|
## The non-negotiables
|
|
53
54
|
|
|
54
|
-
These are hard rules. Breaking one produces code that looks like it works but silently violates the architecture
|
|
55
|
+
These are hard rules. Breaking one produces code that looks like it works but silently violates the architecture, with no rollback, broken upgrades, or data written outside a workflow.
|
|
55
56
|
|
|
56
57
|
<Warning>
|
|
57
58
|
**All mutations go through a workflow.** Never write to the database directly from an API route, a subscriber, or a scheduled job. Reads may query directly; writes must run a workflow so they get validation, compensation, and event emission.
|
|
@@ -92,24 +93,51 @@ When you're about to write a piece of logic, find the concern in this table befo
|
|
|
92
93
|
A quick mental test: *"Does this change data?"* → it must run inside a workflow. *"Does this react to a change?"* → it's a subscriber. *"Does this run on a schedule?"* → it's a job. *"Is this just reading and shaping data for a screen?"* → it's a route + Query. Everything else is either module CRUD or frontend.
|
|
93
94
|
</Tip>
|
|
94
95
|
|
|
95
|
-
##
|
|
96
|
+
## Guides
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
Follow these guides to build each layer the Mercur way.
|
|
99
|
+
|
|
100
|
+
### Server
|
|
98
101
|
|
|
99
102
|
<CardGroup cols={2}>
|
|
100
|
-
<Card title="
|
|
103
|
+
<Card title="Create a Module" href="/resources/best-practices/modules" icon="cube">
|
|
101
104
|
Thin CRUD, naming, and what must never live in a service.
|
|
102
105
|
</Card>
|
|
103
|
-
<Card title="
|
|
106
|
+
<Card title="Link Modules" href="/resources/best-practices/module-links" icon="link">
|
|
104
107
|
`defineLink`, link direction, and filtering by links.
|
|
105
108
|
</Card>
|
|
106
|
-
<Card title="
|
|
109
|
+
<Card title="Create a Workflow" href="/resources/best-practices/workflows" icon="diagram-project">
|
|
107
110
|
Composition constraints, steps, compensation, and the query engine.
|
|
108
111
|
</Card>
|
|
109
|
-
<Card title="API
|
|
112
|
+
<Card title="Create an API Route" href="/resources/best-practices/api-routes" icon="plug">
|
|
110
113
|
Thin adapters, Zod validation, middlewares as filters, `queryConfig`.
|
|
111
114
|
</Card>
|
|
112
|
-
<Card title="Subscribers &
|
|
115
|
+
<Card title="Subscribers & Jobs" href="/resources/best-practices/subscribers-and-jobs" icon="clock">
|
|
113
116
|
Event-driven side effects and scheduled work done safely.
|
|
114
117
|
</Card>
|
|
118
|
+
<Card title="Add a Custom Field" href="/resources/best-practices/custom-fields" icon="table-cells">
|
|
119
|
+
Attach data to an entity end-to-end, from core to the panels.
|
|
120
|
+
</Card>
|
|
121
|
+
</CardGroup>
|
|
122
|
+
|
|
123
|
+
### Panels
|
|
124
|
+
|
|
125
|
+
<CardGroup cols={2}>
|
|
126
|
+
<Card title="Extend the Panels" href="/resources/customization/extending-panels" icon="window">
|
|
127
|
+
The extension model shared by the Admin and Vendor panels.
|
|
128
|
+
</Card>
|
|
129
|
+
<Card title="Add a Widget" href="/resources/tutorials/add-a-widget" icon="puzzle-piece">
|
|
130
|
+
Inject a component into a built-in page zone.
|
|
131
|
+
</Card>
|
|
132
|
+
</CardGroup>
|
|
133
|
+
|
|
134
|
+
### Blocks
|
|
135
|
+
|
|
136
|
+
<CardGroup cols={2}>
|
|
137
|
+
<Card title="Add a Block" href="/resources/tutorials/add-a-block" icon="cubes">
|
|
138
|
+
Install a feature block into your project.
|
|
139
|
+
</Card>
|
|
140
|
+
<Card title="Build a Block" href="/resources/tutorials/build-a-block" icon="hammer">
|
|
141
|
+
Package your own feature as a distributable block.
|
|
142
|
+
</Card>
|
|
115
143
|
</CardGroup>
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
|
|
2
|
+
title: "How to React to Events and Schedule Jobs"
|
|
3
|
+
sidebarTitle: "Subscribers & Jobs"
|
|
4
|
+
description: "Run work outside the request cycle: react to events with subscribers, run periodic work with scheduled jobs, and always mutate through workflows."
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
Subscribers and scheduled jobs are the two ways work happens
|
|
7
|
+
Subscribers and scheduled jobs are the two ways work happens outside a request. Both follow the same core rule as everything else: they never mutate directly, they run a [workflow](/rc/resources/best-practices/workflows).
|
|
7
8
|
|
|
8
|
-
## Subscribers
|
|
9
|
+
## Subscribers: react to events
|
|
9
10
|
|
|
10
|
-
A subscriber listens for a domain event (emitted by a workflow via `emitEventStep`) and runs an asynchronous side effect
|
|
11
|
+
A subscriber listens for a domain event (emitted by a workflow via `emitEventStep`) and runs an asynchronous side effect, such as sending a notification, syncing a search index, or creating a link. It lives in `src/subscribers/` and exports a handler plus a `config` naming the event.
|
|
11
12
|
|
|
12
|
-
```ts
|
|
13
|
+
```ts apps/api/src/subscribers/brand-created.ts
|
|
13
14
|
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
|
|
14
15
|
|
|
15
16
|
export default async function brandCreatedHandler({
|
|
@@ -28,10 +29,12 @@ export const config: SubscriberConfig = {
|
|
|
28
29
|
### Fetch full data from `{ id }`
|
|
29
30
|
|
|
30
31
|
<Warning>
|
|
31
|
-
Event payloads carry
|
|
32
|
+
Event payloads carry ids, not entities. A subscriber receives `{ id }` (sometimes a couple of ids) and must fetch the full record it needs via Query. Never rely on a fat event payload. It goes stale and couples the emitter to every consumer's needs.
|
|
32
33
|
</Warning>
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Fetch what you need through Query, keyed by the id on the event.
|
|
36
|
+
|
|
37
|
+
```ts apps/api/src/subscribers/brand-created.ts
|
|
35
38
|
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
|
36
39
|
const { data: [brand] } = await query.graph({
|
|
37
40
|
entity: "brand",
|
|
@@ -42,9 +45,9 @@ const { data: [brand] } = await query.graph({
|
|
|
42
45
|
|
|
43
46
|
### Mutate via workflows, never directly
|
|
44
47
|
|
|
45
|
-
If the subscriber needs to change data, it runs a workflow
|
|
48
|
+
If the subscriber needs to change data, it runs a workflow, same as a route would. The subscriber is the trigger. The workflow is the work.
|
|
46
49
|
|
|
47
|
-
```ts
|
|
50
|
+
```ts apps/api/src/subscribers/brand-created.ts
|
|
48
51
|
await createBrandNotificationWorkflow(container).run({
|
|
49
52
|
input: { brand_id: brand.id },
|
|
50
53
|
})
|
|
@@ -53,10 +56,10 @@ await createBrandNotificationWorkflow(container).run({
|
|
|
53
56
|
### Log, don't throw
|
|
54
57
|
|
|
55
58
|
<Warning>
|
|
56
|
-
A subscriber runs detached from the request. Throwing
|
|
59
|
+
A subscriber runs detached from the request. Throwing does not surface to a user. It just fails silently or spams retries. Catch errors and log them (resolve the `logger`), then decide explicitly whether to rethrow for a retry or swallow.
|
|
57
60
|
</Warning>
|
|
58
61
|
|
|
59
|
-
```ts
|
|
62
|
+
```ts apps/api/src/subscribers/brand-created.ts
|
|
60
63
|
const logger = container.resolve("logger")
|
|
61
64
|
try {
|
|
62
65
|
await doWork()
|
|
@@ -67,20 +70,20 @@ try {
|
|
|
67
70
|
|
|
68
71
|
### Idempotency and loop guards
|
|
69
72
|
|
|
70
|
-
Events can be delivered more than once, and a subscriber that mutates data can re-trigger the very event it listens to.
|
|
73
|
+
Events can be delivered more than once, and a subscriber that mutates data can re-trigger the very event it listens to. You have two defences:
|
|
71
74
|
|
|
72
|
-
- **Idempotency
|
|
73
|
-
- **Loop guards
|
|
75
|
+
- **Idempotency:** make the handler safe to run twice. Check current state before acting (for example, "is this product already linked to a brand?" before creating the link), or clear the marker that triggered the work so a redelivered event finds nothing left to do.
|
|
76
|
+
- **Loop guards:** if handling event X causes a mutation that emits X again, gate on a condition that becomes false after the first run, or key off a marker you set. Never emit the same event unconditionally from its own subscriber.
|
|
74
77
|
|
|
75
78
|
<Tip>
|
|
76
79
|
A good idempotency check reads the current state through Query first and returns early if the work is already done. This makes redelivery harmless and removes the need for exactly-once guarantees.
|
|
77
80
|
</Tip>
|
|
78
81
|
|
|
79
|
-
## Scheduled jobs
|
|
82
|
+
## Scheduled jobs: periodic work
|
|
80
83
|
|
|
81
|
-
A scheduled job runs on a cron interval to do time-based work
|
|
84
|
+
A scheduled job runs on a cron interval to do time-based work, such as polling for records that became ready, reconciling drifted counters, or emitting a "settle now" event. It lives in `src/jobs/`, exports a handler taking the container, and a `config` with a `name` and a cron `schedule`.
|
|
82
85
|
|
|
83
|
-
```ts
|
|
86
|
+
```ts apps/api/src/jobs/deactivate-stale-brands.ts
|
|
84
87
|
import { MedusaContainer } from "@medusajs/framework/types"
|
|
85
88
|
|
|
86
89
|
export default async function deactivateStaleBrands(container: MedusaContainer) {
|
|
@@ -93,7 +96,7 @@ export default async function deactivateStaleBrands(container: MedusaContainer)
|
|
|
93
96
|
filters: { is_active: true /* + your staleness condition */ },
|
|
94
97
|
})
|
|
95
98
|
|
|
96
|
-
// pass all ids at once
|
|
99
|
+
// pass all ids at once, the workflow handles the batch, not the job
|
|
97
100
|
await deactivateBrandsWorkflow(container).run({
|
|
98
101
|
input: { ids: stale.map((b) => b.id) },
|
|
99
102
|
})
|
|
@@ -109,19 +112,21 @@ export const config = {
|
|
|
109
112
|
|
|
110
113
|
### When to use a job vs a subscriber
|
|
111
114
|
|
|
115
|
+
Pick the trigger that matches how the work starts.
|
|
116
|
+
|
|
112
117
|
| Trigger | Use |
|
|
113
118
|
| --- | --- |
|
|
114
119
|
| "Something happened" (a workflow emitted an event) | **Subscriber** |
|
|
115
120
|
| "It's time" / "poll for anything that became ready" | **Scheduled job** |
|
|
116
121
|
|
|
117
|
-
A time-based pipeline often combines both: a daily job finds records that became eligible and emits an event (say `brand.review_due`), and a
|
|
122
|
+
A time-based pipeline often combines both: a daily job finds records that became eligible and emits an event (say `brand.review_due`), and a subscriber turns each event into a workflow run. Polling for "what's ready" is the job. Reacting to each item is the subscriber.
|
|
118
123
|
|
|
119
124
|
### Job best practices
|
|
120
125
|
|
|
121
|
-
- **
|
|
122
|
-
- **Idempotent by design
|
|
123
|
-
- **Mutations run workflows
|
|
124
|
-
- **Log a summary
|
|
126
|
+
- **Batch and bound result sets:** a job that `SELECT`s an unbounded table will eventually time out. Page through with `LIMIT`/`OFFSET` or a cursor.
|
|
127
|
+
- **Idempotent by design:** a job re-runs on every tick, so it must only act on records still needing action (filter on the not-yet-processed state).
|
|
128
|
+
- **Mutations run workflows:** reads run Query, same as everywhere.
|
|
129
|
+
- **Log a summary:** report how many records processed each run so drift is visible.
|
|
125
130
|
|
|
126
131
|
## Checklist
|
|
127
132
|
|
|
@@ -131,3 +136,11 @@ A time-based pipeline often combines both: a daily job finds records that became
|
|
|
131
136
|
- Job exports `{ name, schedule }`; cron is correct (UTC).
|
|
132
137
|
- Job filters to records still needing work, batches large sets, and logs a summary.
|
|
133
138
|
- Neither a subscriber nor a job writes to the database outside a workflow.
|
|
139
|
+
|
|
140
|
+
## Next steps
|
|
141
|
+
|
|
142
|
+
<CardGroup cols={2}>
|
|
143
|
+
<Card title="Workflows" href="/rc/resources/best-practices/workflows">
|
|
144
|
+
Do all mutations through a workflow so subscribers and jobs stay thin triggers.
|
|
145
|
+
</Card>
|
|
146
|
+
</CardGroup>
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
|
|
2
|
+
title: "How to Use Shared Types"
|
|
3
|
+
sidebarTitle: "Use Shared Types"
|
|
4
|
+
description: "Type the panels against your own backend extensions by augmenting framework DTOs with declaration merging."
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
The panels are fully typed against the API through `@mercurjs/types` and the typed SDK. When you extend the backend
|
|
7
|
+
The panels are fully typed against the API through `@mercurjs/types` and the typed SDK. When you extend the backend with [custom fields](/rc/resources/best-practices/custom-fields), a [linked module](/rc/resources/best-practices/module-links), or an extra field on a DTO, those additions are not in the shipped types yet. You close the gap in the frontend with a small declaration-merging `.d.ts` file. Write it once, and every SDK call that returns the entity carries your field, typed.
|
|
7
8
|
|
|
8
9
|
<Warning>
|
|
9
|
-
|
|
10
|
+
Never use `any` to paper over a missing field. Casting a response to `any`, or to `as { custom_fields: … }` at each call site, throws away type-checking and has to be repeated everywhere. Augment the type once instead.
|
|
10
11
|
</Warning>
|
|
11
12
|
|
|
12
13
|
## The scenario: a custom field, typed end-to-end
|
|
13
14
|
|
|
14
|
-
Say you added a custom field on the backend
|
|
15
|
+
Say you added a custom field on the backend, such as `is_featured` on `product` (see [Custom fields](/rc/resources/best-practices/custom-fields)). The value now comes back from the API, but the panel's `ProductDTO` does not know about it, so `product.is_featured` is a type error.
|
|
15
16
|
|
|
16
17
|
Fix it in the panel with a declaration-merging file.
|
|
17
18
|
|
|
18
19
|
### Why merging works here
|
|
19
20
|
|
|
20
|
-
The `ProductDTO` the SDK returns ultimately resolves to Medusa's upstream `ProductDTO`, which is declared as an
|
|
21
|
+
The `ProductDTO` the SDK returns ultimately resolves to Medusa's upstream `ProductDTO`, which is declared as an `interface` in `@medusajs/types`. Interfaces are open, so you can merge into it with `declare module "@medusajs/types"`.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
Everything downstream refers back to that same interface: `@mercurjs/types`, the SDK response wrappers such as `AdminProductResponse` and list responses, and the panel hooks. Your added members appear in all of them at once. You augment in one place and every product-returning endpoint is typed.
|
|
23
24
|
|
|
24
25
|
### Add the `.d.ts` in the panel
|
|
25
26
|
|
|
26
|
-
Drop a declaration file anywhere under the panel's `src
|
|
27
|
+
Drop a declaration file anywhere under the panel's `src/`. It is picked up by the app's `tsconfig`.
|
|
27
28
|
|
|
28
|
-
```ts
|
|
29
|
+
```ts apps/vendor/src/types/custom-fields.d.ts
|
|
29
30
|
import "@medusajs/types"
|
|
30
31
|
|
|
31
32
|
declare module "@medusajs/types" {
|
|
@@ -38,17 +39,17 @@ declare module "@medusajs/types" {
|
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
<Warning>
|
|
41
|
-
|
|
42
|
+
Follow two rules, or the augmentation silently does nothing:
|
|
42
43
|
|
|
43
|
-
- The module name in `declare module "..."` must be the package that declares the interface you
|
|
44
|
-
- The file must be a module. Add an `import "@medusajs/types"
|
|
44
|
+
- The module name in `declare module "..."` must be the package that declares the interface you are merging into. Here that is `@medusajs/types`, the owner of `UpstreamProductDTO`, not `@mercurjs/types`, which only aliases it.
|
|
45
|
+
- The file must be a module. Add an `import "@medusajs/types"`, or a trailing `export {}`, so TypeScript treats it as one.
|
|
45
46
|
</Warning>
|
|
46
47
|
|
|
47
48
|
### Now the whole SDK is typed
|
|
48
49
|
|
|
49
50
|
With that one file in place, no cast is needed anywhere:
|
|
50
51
|
|
|
51
|
-
```ts
|
|
52
|
+
```ts Every product endpoint carries the field
|
|
52
53
|
const { products } = await sdk.vendor.products.query()
|
|
53
54
|
products[0].custom_fields?.is_featured // ✅ typed, no cast
|
|
54
55
|
|
|
@@ -57,16 +58,16 @@ product.custom_fields?.is_featured // ✅ typed everywhere ProductDTO flows
|
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
<Tip>
|
|
60
|
-
Types and runtime are separate concerns
|
|
61
|
+
Types and runtime are separate concerns. This `.d.ts` makes the field typed, but it only arrives if the fetch asks for it. Let the [custom-fields `link` / registry merge](/rc/resources/best-practices/custom-fields#the-extension-api-link-property) add the fields to the built-in panel fetches rather than hand-adding `+field.*`. The vendor product query in particular rejects arbitrary `*`-relation overrides.
|
|
61
62
|
</Tip>
|
|
62
63
|
|
|
63
64
|
## Linked data resolves the same way
|
|
64
65
|
|
|
65
|
-
The augmentation
|
|
66
|
+
The augmentation is not limited to a custom field's own value. It is how you make linked-module data typed too. When a [custom-fields config](/rc/resources/best-practices/custom-fields#the-extension-api-link-property) declares a `link`, the panel fetches that module's data alongside the entity (see [panel extensions](/references/panel-extensions/overview)). A `link: "brand"` merges `brand.*` into the built-in product fetch for you, with no hand-written field list.
|
|
66
67
|
|
|
67
|
-
Pair that one config line with a matching augmentation, and the linked data is both
|
|
68
|
+
Pair that one config line with a matching augmentation, and the linked data is both present at runtime and typed everywhere `ProductDTO` is imported.
|
|
68
69
|
|
|
69
|
-
```ts
|
|
70
|
+
```ts src/custom-fields/product.tsx: declare the link (runtime)
|
|
70
71
|
export default defineCustomFieldsConfig({
|
|
71
72
|
model: "product",
|
|
72
73
|
link: "brand", // brand.* is fetched with every product
|
|
@@ -78,7 +79,7 @@ export default defineCustomFieldsConfig({
|
|
|
78
79
|
})
|
|
79
80
|
```
|
|
80
81
|
|
|
81
|
-
```ts
|
|
82
|
+
```ts src/types/brand.d.ts: declare the shape (types)
|
|
82
83
|
import "@medusajs/types"
|
|
83
84
|
|
|
84
85
|
declare module "@medusajs/types" {
|
|
@@ -88,7 +89,7 @@ declare module "@medusajs/types" {
|
|
|
88
89
|
}
|
|
89
90
|
```
|
|
90
91
|
|
|
91
|
-
Now any code that imports `ProductDTO
|
|
92
|
+
Now any code that imports `ProductDTO`, whether a page, a hook, or a column renderer, sees `product.brand` resolved, with the data already fetched by the `link`:
|
|
92
93
|
|
|
93
94
|
```ts
|
|
94
95
|
const { product } = await sdk.vendor.products.$id.query({ $id: id })
|
|
@@ -96,7 +97,7 @@ product.brand?.name // ✅ present (via link) and typed (via augmentation)
|
|
|
96
97
|
```
|
|
97
98
|
|
|
98
99
|
<Tip>
|
|
99
|
-
The `link` does the fetching, the `.d.ts` does the typing
|
|
100
|
+
The `link` does the fetching, the `.d.ts` does the typing. You write each once, per model, and every product-returning endpoint in the panel is covered. This is the payoff of augmentation: register the relationship in one place, then consume it as a plain typed property everywhere.
|
|
100
101
|
</Tip>
|
|
101
102
|
|
|
102
103
|
## Where each type goes
|
|
@@ -108,13 +109,27 @@ product.brand?.name // ✅ present (via link) and typed (via augmentation)
|
|
|
108
109
|
| A brand-new Mercur/domain DTO | The domain folder in `@mercurjs/types`, re-exported from `index.ts` |
|
|
109
110
|
|
|
110
111
|
<Warning>
|
|
111
|
-
DTOs and enums the platform already ships (`ProductDTO`, `SellerStatus`, `MercurModules`, `HttpTypes`) are imported from `@mercurjs/types
|
|
112
|
+
DTOs and enums the platform already ships (`ProductDTO`, `SellerStatus`, `MercurModules`, `HttpTypes`) are imported from `@mercurjs/types`, never redeclared. In the dashboards, `HttpTypes` comes from `@mercurjs/types` too, which is what keeps request/response types aligned with Mercur's extended routes.
|
|
112
113
|
</Warning>
|
|
113
114
|
|
|
114
115
|
## Checklist
|
|
115
116
|
|
|
116
117
|
- No `any`, and no per-call-site casts for extended data.
|
|
117
|
-
- Backend additions typed in the panel via a `.d.ts` merging into the
|
|
118
|
-
- Augmentation files name the correct package and are real modules (`import
|
|
118
|
+
- Backend additions typed in the panel via a `.d.ts` merging into the framework interface that owns the DTO.
|
|
119
|
+
- Augmentation files name the correct package and are real modules (`import` / `export {}`).
|
|
119
120
|
- Extended fields are requested with `+field.*` so they actually arrive.
|
|
120
121
|
- Shipped types imported from `@mercurjs/types`; one-off shapes inferred from Zod.
|
|
122
|
+
|
|
123
|
+
## Next steps
|
|
124
|
+
|
|
125
|
+
<CardGroup cols={2}>
|
|
126
|
+
<Card title="Custom fields" href="/rc/resources/best-practices/custom-fields">
|
|
127
|
+
Add fields, rows, actions, and columns to a built-in model, and declare a `link`.
|
|
128
|
+
</Card>
|
|
129
|
+
<Card title="Module links" href="/rc/resources/best-practices/module-links">
|
|
130
|
+
Link a custom module to a built-in entity and fetch its data alongside.
|
|
131
|
+
</Card>
|
|
132
|
+
<Card title="Panel extensions" href="/references/panel-extensions/overview">
|
|
133
|
+
See how the panel fetches linked module data alongside the entity.
|
|
134
|
+
</Card>
|
|
135
|
+
</CardGroup>
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
|
|
2
|
+
title: "How to Create a Workflow"
|
|
3
|
+
sidebarTitle: "Create a Workflow"
|
|
4
|
+
description: "Coordinate a business operation across modules as workflow steps, each with automatic rollback, reusing built-in steps and reading through the query engine."
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
A workflow is the orchestration layer
|
|
7
|
+
A workflow is the orchestration layer. It coordinates a business operation across one or more modules as a series of **steps**, with automatic rollback (**compensation**) when any step fails. Every mutation in Mercur runs inside a workflow. This is the single most important rule in the [architecture](/rc/resources/best-practices/overview).
|
|
7
8
|
|
|
8
9
|
<Warning>
|
|
9
|
-
**All mutations go through a workflow.** API routes, subscribers, and scheduled jobs never write to the database directly
|
|
10
|
+
**All mutations go through a workflow.** API routes, subscribers, and scheduled jobs never write to the database directly. They run a workflow. That is what gives every mutation validation, event emission, and rollback.
|
|
10
11
|
</Warning>
|
|
11
12
|
|
|
12
13
|
<Note>
|
|
13
|
-
Workflows are a [Medusa framework primitive](https://docs.medusajs.com/learn/fundamentals/workflows). This page focuses on the constraints and conventions that trip people
|
|
14
|
+
Workflows are a [Medusa framework primitive](https://docs.medusajs.com/learn/fundamentals/workflows). This page focuses on the constraints and conventions that trip people, and agents, up.
|
|
14
15
|
</Note>
|
|
15
16
|
|
|
16
17
|
## The composition function is not normal JavaScript
|
|
17
18
|
|
|
18
|
-
The function you pass to `createWorkflow` is a **composition function**. It runs once at build time to wire steps together
|
|
19
|
+
The function you pass to `createWorkflow` is a **composition function**. It runs once at build time to wire steps together. It does **not** execute your business logic at request time. Because of that, it has hard constraints.
|
|
19
20
|
|
|
20
21
|
<Warning>
|
|
21
22
|
Inside a `createWorkflow` composition function you must **not**:
|
|
@@ -26,12 +27,12 @@ The function you pass to `createWorkflow` is a **composition function**. It runs
|
|
|
26
27
|
- use `new Date()`, `Math.random()`, or any non-deterministic call
|
|
27
28
|
- access properties of a step's output directly (e.g. `result.id`)
|
|
28
29
|
|
|
29
|
-
These run at composition time, not execution time, so they either do nothing useful or break replay
|
|
30
|
+
These run at composition time, not execution time, so they either do nothing useful or break replay and rollback.
|
|
30
31
|
</Warning>
|
|
31
32
|
|
|
32
33
|
Anything that looks like normal logic goes into a **step** (for side effects) or a **`transform`** (for shaping data between steps):
|
|
33
34
|
|
|
34
|
-
```ts
|
|
35
|
+
```ts src/workflows/create-brands.ts
|
|
35
36
|
import {
|
|
36
37
|
createWorkflow,
|
|
37
38
|
transform,
|
|
@@ -56,14 +57,14 @@ export const createBrandsWorkflow = createWorkflow(
|
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
<Tip>
|
|
59
|
-
Need a conditional or a computed value? Use `transform` to derive data, `when` to run a step conditionally, and put date
|
|
60
|
+
Need a conditional or a computed value? Use `transform` to derive data, `when` to run a step conditionally, and put date, random, or id generation **inside a step**. Never branch in the composition body itself.
|
|
60
61
|
</Tip>
|
|
61
62
|
|
|
62
|
-
## One mutation per step
|
|
63
|
+
## One mutation per step and compensation
|
|
63
64
|
|
|
64
|
-
A step is the unit of work and the unit of rollback. The rule: **each step performs a single mutation and defines how to undo it.** `createStep` takes an invoke function and a compensation function
|
|
65
|
+
A step is the unit of work and the unit of rollback. The rule: **each step performs a single mutation and defines how to undo it.** `createStep` takes an invoke function and a compensation function. The invoke returns a `StepResponse` whose second argument is the data the compensation needs.
|
|
65
66
|
|
|
66
|
-
```ts
|
|
67
|
+
```ts src/workflows/steps/create-brands.ts
|
|
67
68
|
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
|
68
69
|
import BrandModuleService from "../../modules/brand/service"
|
|
69
70
|
import { BRAND_MODULE } from "../../modules/brand"
|
|
@@ -86,7 +87,7 @@ export const createBrandsStep = createStep(
|
|
|
86
87
|
)
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
When a later step in the workflow throws, Medusa runs the compensation functions of the already-completed steps in reverse. The `createBrands` above is undone by `deleteBrands`. Splitting mutations one per step is what makes this reliable: a step that does two writes can only half-compensate.
|
|
90
91
|
|
|
91
92
|
## Reuse built-in steps
|
|
92
93
|
|
|
@@ -98,7 +99,7 @@ Don't hand-roll what the framework already ships. Medusa's `core-flows` exports
|
|
|
98
99
|
| Emit a domain event | `emitEventStep` |
|
|
99
100
|
| Call another workflow as a step | `otherWorkflow.runAsStep({ input })` |
|
|
100
101
|
|
|
101
|
-
```ts
|
|
102
|
+
```ts
|
|
102
103
|
import { emitEventStep } from "@medusajs/medusa/core-flows"
|
|
103
104
|
|
|
104
105
|
export const createBrandsWorkflow = createWorkflow(
|
|
@@ -118,12 +119,12 @@ export const createBrandsWorkflow = createWorkflow(
|
|
|
118
119
|
```
|
|
119
120
|
|
|
120
121
|
<Tip>
|
|
121
|
-
Prefer `runAsStep` over duplicating logic.
|
|
122
|
+
Prefer `runAsStep` over duplicating logic. When two workflows need the same sequence, extract it into its own workflow and call it as a step from both. You get one place to maintain, and correct compensation for free.
|
|
122
123
|
</Tip>
|
|
123
124
|
|
|
124
|
-
## Hooks
|
|
125
|
+
## Hooks let others extend your workflow
|
|
125
126
|
|
|
126
|
-
Expose extension points with `createHook` so consumers can inject behaviour
|
|
127
|
+
Expose extension points with `createHook` so consumers can inject behaviour such as validation or side effects without forking the workflow. Add a `validate` hook before the mutation and a `brandsCreated` hook after it:
|
|
127
128
|
|
|
128
129
|
```ts
|
|
129
130
|
const validate = createHook("validate", { input })
|
|
@@ -140,13 +141,13 @@ return new WorkflowResponse(brands, {
|
|
|
140
141
|
})
|
|
141
142
|
```
|
|
142
143
|
|
|
143
|
-
Consumers register a handler on the hook to run custom logic at that point. This is the sanctioned way to extend a workflow
|
|
144
|
+
Consumers register a handler on the hook to run custom logic at that point. This is the sanctioned way to extend a workflow. See [Extend a workflow](/rc/resources/customization/extend-a-workflow).
|
|
144
145
|
|
|
145
146
|
## The query engine
|
|
146
147
|
|
|
147
|
-
Reads inside a step
|
|
148
|
+
Reads inside a step, and anywhere else, go through **Query**, the graph engine that resolves data across modules and links. Resolve it from the container and call `query.graph`:
|
|
148
149
|
|
|
149
|
-
```ts
|
|
150
|
+
```ts
|
|
150
151
|
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
|
151
152
|
|
|
152
153
|
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
|
@@ -159,7 +160,7 @@ const { data: brands } = await query.graph({
|
|
|
159
160
|
```
|
|
160
161
|
|
|
161
162
|
<Warning>
|
|
162
|
-
Query is for **reads**. Never try to mutate through it, and never resolve another module's service inside a step to read its data
|
|
163
|
+
Query is for **reads**. Never try to mutate through it, and never resolve another module's service inside a step to read its data. Go through Query so module isolation and links are respected.
|
|
163
164
|
</Warning>
|
|
164
165
|
|
|
165
166
|
## Checklist for a workflow
|
|
@@ -171,3 +172,14 @@ const { data: brands } = await query.graph({
|
|
|
171
172
|
- Built-in steps (`createRemoteLinkStep`, `emitEventStep`) and `runAsStep` are reused instead of reimplemented.
|
|
172
173
|
- Reads use `query.graph`; no cross-module service calls.
|
|
173
174
|
- Extension points are exposed as hooks, not by forking.
|
|
175
|
+
|
|
176
|
+
## Next steps
|
|
177
|
+
|
|
178
|
+
<CardGroup cols={2}>
|
|
179
|
+
<Card title="Extend a workflow" href="/rc/resources/customization/extend-a-workflow">
|
|
180
|
+
Register handlers on a workflow's hooks to add behaviour without forking.
|
|
181
|
+
</Card>
|
|
182
|
+
<Card title="Best practices overview" href="/rc/resources/best-practices/overview">
|
|
183
|
+
See how workflows fit the wider Mercur architecture.
|
|
184
|
+
</Card>
|
|
185
|
+
</CardGroup>
|