@mercurjs/docs 2.3.8-canary.3 → 2.3.8-canary.4

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.
Files changed (113) hide show
  1. package/content/learn/architecture.mdx +166 -263
  2. package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
  3. package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
  4. package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
  5. package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
  6. package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
  7. package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
  8. package/content/platform/attribute/overview.mdx +37 -33
  9. package/content/platform/attribute/reference/data-models.mdx +53 -38
  10. package/content/platform/attribute/reference/events.mdx +47 -24
  11. package/content/platform/attribute/reference/links.mdx +98 -27
  12. package/content/platform/attribute/reference/service.mdx +71 -32
  13. package/content/platform/attribute/reference/workflows.mdx +158 -28
  14. package/content/platform/catalog/concepts/master-products.mdx +39 -37
  15. package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
  16. package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
  17. package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
  18. package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
  19. package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
  20. package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
  21. package/content/platform/catalog/overview.mdx +39 -35
  22. package/content/platform/catalog/reference/data-models.mdx +154 -43
  23. package/content/platform/catalog/reference/events.mdx +41 -25
  24. package/content/platform/catalog/reference/links.mdx +196 -28
  25. package/content/platform/catalog/reference/service.mdx +57 -35
  26. package/content/platform/catalog/reference/workflows.mdx +124 -30
  27. package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
  28. package/content/platform/commission/concepts/rule-matching.mdx +53 -63
  29. package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
  30. package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
  31. package/content/platform/commission/guides/create-a-rate.mdx +101 -53
  32. package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
  33. package/content/platform/commission/overview.mdx +43 -35
  34. package/content/platform/commission/reference/data-models.mdx +59 -45
  35. package/content/platform/commission/reference/events.mdx +28 -34
  36. package/content/platform/commission/reference/links.mdx +42 -33
  37. package/content/platform/commission/reference/service.mdx +103 -28
  38. package/content/platform/commission/reference/workflows.mdx +101 -27
  39. package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
  40. package/content/platform/offer/concepts/shipping.mdx +28 -29
  41. package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
  42. package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
  43. package/content/platform/offer/guides/create-an-offer.mdx +112 -60
  44. package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
  45. package/content/platform/offer/overview.mdx +39 -32
  46. package/content/platform/offer/reference/data-models.mdx +77 -47
  47. package/content/platform/offer/reference/events.mdx +44 -18
  48. package/content/platform/offer/reference/links.mdx +92 -40
  49. package/content/platform/offer/reference/service.mdx +66 -31
  50. package/content/platform/offer/reference/workflows.mdx +146 -37
  51. package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
  52. package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
  53. package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
  54. package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
  55. package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
  56. package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
  57. package/content/platform/order-group/overview.mdx +42 -32
  58. package/content/platform/order-group/reference/data-models.mdx +48 -31
  59. package/content/platform/order-group/reference/events.mdx +29 -17
  60. package/content/platform/order-group/reference/links.mdx +90 -29
  61. package/content/platform/order-group/reference/service.mdx +55 -31
  62. package/content/platform/order-group/reference/workflows.mdx +83 -24
  63. package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
  64. package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
  65. package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
  66. package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
  67. package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
  68. package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
  69. package/content/platform/payout/overview.mdx +43 -34
  70. package/content/platform/payout/reference/data-models.mdx +63 -36
  71. package/content/platform/payout/reference/events.mdx +56 -30
  72. package/content/platform/payout/reference/links.mdx +52 -22
  73. package/content/platform/payout/reference/service.mdx +72 -33
  74. package/content/platform/payout/reference/workflows.mdx +80 -19
  75. package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
  76. package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
  77. package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
  78. package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
  79. package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
  80. package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
  81. package/content/platform/product-edit/overview.mdx +41 -34
  82. package/content/platform/product-edit/reference/data-models.mdx +73 -47
  83. package/content/platform/product-edit/reference/events.mdx +39 -29
  84. package/content/platform/product-edit/reference/links.mdx +32 -24
  85. package/content/platform/product-edit/reference/service.mdx +45 -26
  86. package/content/platform/product-edit/reference/workflows.mdx +173 -31
  87. package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
  88. package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
  89. package/content/platform/review/concepts/the-review-model.mdx +52 -45
  90. package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
  91. package/content/platform/review/guides/create-a-review.mdx +109 -39
  92. package/content/platform/review/guides/moderate-a-review.mdx +83 -37
  93. package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
  94. package/content/platform/review/overview.mdx +43 -33
  95. package/content/platform/review/reference/data-models.mdx +41 -27
  96. package/content/platform/review/reference/events.mdx +55 -37
  97. package/content/platform/review/reference/links.mdx +80 -22
  98. package/content/platform/review/reference/service.mdx +61 -33
  99. package/content/platform/review/reference/workflows.mdx +80 -24
  100. package/content/platform/store/concepts/lifecycle.mdx +68 -44
  101. package/content/platform/store/concepts/store-entity.mdx +45 -27
  102. package/content/platform/store/concepts/team.mdx +56 -35
  103. package/content/platform/store/guides/create-a-store.mdx +102 -35
  104. package/content/platform/store/guides/manage-the-team.mdx +122 -32
  105. package/content/platform/store/guides/moderate-a-store.mdx +107 -38
  106. package/content/platform/store/overview.mdx +41 -34
  107. package/content/platform/store/reference/data-models.mdx +132 -57
  108. package/content/platform/store/reference/events.mdx +42 -17
  109. package/content/platform/store/reference/links.mdx +67 -49
  110. package/content/platform/store/reference/service.mdx +105 -33
  111. package/content/platform/store/reference/workflows.mdx +304 -24
  112. package/llms.txt +111 -111
  113. package/package.json +1 -1
@@ -1,66 +1,177 @@
1
1
  ---
2
- title: "Data models"
2
+ title: "Catalog data models"
3
3
  sidebarTitle: "Data models"
4
- description: "The Medusa Product models the catalog is built on, plus Mercur's marketplace layer."
4
+ description: "Every field on the Medusa Product module models the shared catalog is built on, and the relations Mercur adds."
5
5
  ---
6
6
 
7
- The Catalog domain is owned by **Medusa's Product module**, with a marketplace
8
- layer added by Mercur. This reference lists the models at the Mercur-relevant
9
- level. It doesn't restate every Medusa product field. For the full module, see
10
- the [Medusa Product module](https://docs.medusajs.com/resources/commerce-modules/product).
7
+ The catalog's data models belong to Medusa's Product module, version 2.21. Mercur adds no columns to them. It adds relations through [module links](/platform/catalog/reference/links), listed under each model. Every model also has `created_at`, `updated_at`, and `deleted_at` timestamps that Medusa manages.
11
8
 
12
9
  ## Product
13
10
 
14
- Table `product`, id prefix `prod`. The shared master product, not owned by any
15
- store. The marketplace-relevant fields:
11
+ The shared master product. Table `product`, id prefix `prod`.
16
12
 
17
- | Field | Type | Notes |
13
+ | Field | Type | Description |
18
14
  | --- | --- | --- |
19
- | `id` | text | Primary key |
20
- | `title` | text | Searchable |
21
- | `handle` | text | Unique |
22
- | `subtitle` / `description` | text | Nullable |
23
- | `status` | enum | `ProductStatus`: `draft` / `proposed` / `published` / `rejected`, default `draft` |
24
- | `thumbnail` | text | Nullable |
25
- | `metadata` | json | Nullable |
15
+ | `id` | `text` | Primary key |
16
+ | `title` | `text` | Display title, searchable and translatable |
17
+ | `handle` | `text` | URL handle, unique among non-deleted products |
18
+ | `subtitle` | `text`, nullable | Subtitle, searchable and translatable |
19
+ | `description` | `text`, nullable | Description, searchable and translatable |
20
+ | `is_giftcard` | `boolean` | Whether the product is a gift card. Default `false` |
21
+ | `status` | `enum` | `draft`, `proposed`, `published`, or `rejected`. Default `draft` |
22
+ | `thumbnail` | `text`, nullable | Thumbnail image URL |
23
+ | `weight` | `float`, nullable | Weight |
24
+ | `length` | `float`, nullable | Length |
25
+ | `height` | `float`, nullable | Height |
26
+ | `width` | `float`, nullable | Width |
27
+ | `origin_country` | `text`, nullable | Country of origin |
28
+ | `hs_code` | `text`, nullable | Harmonized System code |
29
+ | `mid_code` | `text`, nullable | Manufacturer identification code |
30
+ | `material` | `text`, nullable | Material, translatable |
31
+ | `discountable` | `boolean` | Whether promotions can apply. Default `true` |
32
+ | `external_id` | `text`, nullable | Id in an external system |
33
+ | `metadata` | `json`, nullable | Custom key-value data |
34
+
35
+ Native relations:
36
+
37
+ - **`variants`**: `ProductVariant[]`, deleted with the product
38
+ - **`options`**: `ProductOption[]` through the `ProductProductOption` pivot
39
+ - **`images`**: `ProductImage[]`, deleted with the product
40
+ - **`categories`**: `ProductCategory[]` through `product_category_product`
41
+ - **`collection`**: `ProductCollection`, nullable
42
+ - **`type`**: `ProductType`, nullable
43
+ - **`tags`**: `ProductTag[]` through `product_tags`
44
+
45
+ Mercur relations:
46
+
47
+ - **`sellers`**: `Seller[]`, the store allowlist in `product_seller`
48
+ - **`product_attribute_values`**: attribute values attached to the product
49
+ - **`scoped_attributes`**: product-scoped attributes, read-only
50
+ - **`changes`**: `ProductChange[]`, the audit and edit history, read-only
51
+ - **`reviews`**: `Review[]`
26
52
 
27
- Relations used by the marketplace: `variants`, `options`, `categories`,
28
- `collection`, `tags`, `type`, `images` (native Medusa), plus Mercur's `sellers`
29
- (the allowlist), `offers`, `scoped_attributes`, and `changes` (audit history).
53
+ ## ProductVariant
30
54
 
31
- ## product_seller
55
+ One purchasable configuration of a product. Table `product_variant`, id prefix `variant`.
32
56
 
33
- The **allowlist**: a many-to-many link table between `Product` and `Seller`
34
- controlling which stores may sell a master product.
57
+ | Field | Type | Description |
58
+ | --- | --- | --- |
59
+ | `id` | `text` | Primary key |
60
+ | `title` | `text` | Display title, searchable and translatable |
61
+ | `sku` | `text`, nullable | Catalog SKU, unique among non-deleted variants |
62
+ | `barcode` | `text`, nullable | Barcode, unique |
63
+ | `ean` | `text`, nullable | EAN, unique |
64
+ | `upc` | `text`, nullable | UPC, unique |
65
+ | `allow_backorder` | `boolean` | Default `false` |
66
+ | `manage_inventory` | `boolean` | Model default `true`; Mercur's create workflow sends `false` |
67
+ | `hs_code` | `text`, nullable | Harmonized System code |
68
+ | `origin_country` | `text`, nullable | Country of origin |
69
+ | `mid_code` | `text`, nullable | Manufacturer identification code |
70
+ | `material` | `text`, nullable | Material, translatable |
71
+ | `weight` | `float`, nullable | Weight |
72
+ | `length` | `float`, nullable | Length |
73
+ | `height` | `float`, nullable | Height |
74
+ | `width` | `float`, nullable | Width |
75
+ | `thumbnail` | `text`, nullable | Variant thumbnail URL |
76
+ | `variant_rank` | `number`, nullable | Sort order. Default `0` |
77
+ | `metadata` | `json`, nullable | Custom key-value data |
78
+
79
+ Relations: `product` (nullable), `options` (`ProductOptionValue[]` through `product_variant_option`), and `images` (`ProductImage[]`). Mercur links offers to variants through `offer.variant_id`. Each store's price and stock live on the [offer](/platform/offer/reference/data-models), not here.
80
+
81
+ ## ProductOption
82
+
83
+ An axis that variants vary on. Table `product_option`, id prefix `opt`.
84
+
85
+ | Field | Type | Description |
86
+ | --- | --- | --- |
87
+ | `id` | `text` | Primary key |
88
+ | `title` | `text` | Option name, such as `Size` |
89
+ | `is_exclusive` | `boolean` | `true` when the option belongs to one product. Default `false` |
90
+ | `metadata` | `json`, nullable | Custom key-value data |
35
91
 
36
- | Column | References |
37
- | --- | --- |
38
- | `product_id` | `product.id` |
39
- | `seller_id` | `seller.id` |
92
+ Relations: `products` and `values` (`ProductOptionValue[]`). A variant-axis attribute mirrors one option through `product_attribute.product_option_id`.
40
93
 
41
- A store on this table can create an [offer](/platform/offer/overview) against the
42
- product; a store not on it cannot.
94
+ ## ProductOptionValue
43
95
 
44
- ## ProductVariant
96
+ One value of an option. Table `product_option_value`, id prefix `optval`.
97
+
98
+ | Field | Type | Description |
99
+ | --- | --- | --- |
100
+ | `id` | `text` | Primary key |
101
+ | `value` | `text` | The value, such as `M` |
102
+ | `rank` | `number`, nullable | Sort order |
103
+ | `metadata` | `json`, nullable | Custom key-value data |
104
+
105
+ Relations: `option` (nullable), `variants`, and `product_options`.
45
106
 
46
- Table `product_variant`, id prefix `variant`. A purchasable configuration of a
47
- master product, generated from its options and variant-axis attributes. Holds the
48
- SKU-level catalog structure. Offer-scoped price and inventory live on the offer,
49
- not here.
107
+ ## ProductImage
108
+
109
+ A product image. Table `image`, id prefix `img`.
110
+
111
+ | Field | Type | Description |
112
+ | --- | --- | --- |
113
+ | `id` | `text` | Primary key |
114
+ | `url` | `text` | Image URL |
115
+ | `rank` | `number` | Sort order. Default `0` |
116
+ | `metadata` | `json`, nullable | Custom key-value data |
117
+
118
+ Relations: `product` and `variants`.
50
119
 
51
120
  ## ProductCategory
52
121
 
53
- Table `product_category`, id prefix `pcat`. Hierarchical, nestable
54
- classification. Participates in the marketplace layer through the
55
- `category_seller` allowlist and the `media_images` link.
122
+ A node in the category tree. Table `product_category`, id prefix `pcat`.
123
+
124
+ | Field | Type | Description |
125
+ | --- | --- | --- |
126
+ | `id` | `text` | Primary key |
127
+ | `name` | `text` | Display name, searchable and translatable |
128
+ | `description` | `text` | Description. Default `""` |
129
+ | `handle` | `text` | URL handle, unique |
130
+ | `mpath` | `text` | Materialized path of ancestor ids |
131
+ | `is_active` | `boolean` | Whether the category is live. Default `false` |
132
+ | `is_internal` | `boolean` | Hidden from the storefront. Default `false` |
133
+ | `rank` | `number` | Sort order among siblings. Default `0` |
134
+ | `external_id` | `text`, nullable | Id in an external system |
135
+ | `metadata` | `json`, nullable | Custom key-value data |
136
+
137
+ Relations: `parent_category` (nullable), `category_children` (deleted with the parent), and `products`. Mercur adds `sellers`, `media_images`, and the product attribute link.
56
138
 
57
139
  ## ProductCollection
58
140
 
59
- Table `product_collection`, id prefix `pcol`. Flat, curated grouping of products,
60
- with a `media_images` link for collection artwork.
141
+ A flat group of products. Table `product_collection`, id prefix `pcol`.
142
+
143
+ | Field | Type | Description |
144
+ | --- | --- | --- |
145
+ | `id` | `text` | Primary key |
146
+ | `title` | `text` | Display title, searchable and translatable |
147
+ | `handle` | `text` | URL handle, unique |
148
+ | `external_id` | `text`, nullable | Id in an external system |
149
+ | `metadata` | `json`, nullable | Custom key-value data |
150
+
151
+ Relations: `products`. Mercur adds `media_images`.
152
+
153
+ ## ProductTag
154
+
155
+ A free-form label. Table `product_tag`, id prefix `ptag`.
156
+
157
+ | Field | Type | Description |
158
+ | --- | --- | --- |
159
+ | `id` | `text` | Primary key |
160
+ | `value` | `text` | The label, unique |
161
+ | `external_id` | `text`, nullable | Id in an external system |
162
+ | `metadata` | `json`, nullable | Custom key-value data |
163
+
164
+ Relations: `products`.
165
+
166
+ ## ProductType
167
+
168
+ A single classification per product. Table `product_type`, id prefix `ptyp`.
169
+
170
+ | Field | Type | Description |
171
+ | --- | --- | --- |
172
+ | `id` | `text` | Primary key |
173
+ | `value` | `text` | The type name, unique |
174
+ | `external_id` | `text`, nullable | Id in an external system |
175
+ | `metadata` | `json`, nullable | Custom key-value data |
61
176
 
62
- <Note>
63
- `ProductTag` and `ProductType` round out the native grouping models: free-form
64
- labels and a single type classification per product, respectively. They carry
65
- no Mercur-specific columns.
66
- </Note>
177
+ Relations: `products`.
@@ -1,41 +1,57 @@
1
1
  ---
2
- title: "Event reference"
2
+ title: "Catalog events"
3
3
  sidebarTitle: "Events"
4
- description: "Events emitted by the Catalog domain, for subscribers and side effects."
4
+ description: "The events Mercur's product workflows emit, their payloads, and how to subscribe to them."
5
5
  ---
6
6
 
7
- The Catalog domain emits events as products are created and reviewed. Subscribe
8
- to them to run side effects instead of polling, such as sending notifications,
9
- syncing external systems, or reindexing search.
7
+ Mercur's product workflows emit 4 events as products are created and reviewed. The names are exported as `ProductWorkflowEvents` from `@mercurjs/core/workflows`. Subscribe to them to notify stores, sync external systems, or reindex search.
10
8
 
11
- ```ts title="src/subscribers/product-published.ts"
9
+ ## Product events
10
+
11
+ Each event is emitted once per product. Payloads come from the `emitEventStep` calls in `packages/core/src/workflows/product/workflows`.
12
+
13
+ | Event | Constant | Emitted when | Payload |
14
+ | --- | --- | --- | --- |
15
+ | `product.created` | `CREATED` | `createProductsWorkflow` creates a product | `{ id }` |
16
+ | `product.published` | `PUBLISHED` | `confirmProductsWorkflow` publishes a product | `{ id, internal_note? }` |
17
+ | `product.rejected` | `REJECTED` | `rejectProductWorkflow` rejects a product | `{ id, message? }` |
18
+ | `product.change-requested` | `CHANGE_REQUESTED` | `requestProductChangeWorkflow` asks for a revision | `{ id, message?, actor_id? }` |
19
+
20
+ <Warning>
21
+ Mercur's `createProductsWorkflow` runs Medusa's `createProductsWorkflow` as a step, which also emits `product.created` with `{ id }`. Expect 2 `product.created` events per product and make handlers idempotent.
22
+ </Warning>
23
+
24
+ ## Subscribe to an event
25
+
26
+ Write a subscriber that listens for one of the events. This one logs the rejection reason for each rejected product.
27
+
28
+ ```ts title="src/subscribers/product-rejected.ts"
12
29
  import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
30
+ import { ProductWorkflowEvents } from "@mercurjs/core/workflows"
13
31
 
14
- export default async function productPublishedHandler({
32
+ export default async function productRejected({
15
33
  event,
16
34
  container,
17
- }: SubscriberArgs<{ id: string }>) {
18
- const productId = event.data.id
19
- // ...reindex search, notify the seller, etc.
35
+ }: SubscriberArgs<{ id: string; message?: string }>) {
36
+ const logger = container.resolve("logger")
37
+ const reason = event.data.message ?? "no reason given"
38
+
39
+ logger.info(`Product ${event.data.id} rejected: ${reason}`)
20
40
  }
21
41
 
22
42
  export const config: SubscriberConfig = {
23
- event: "product.published",
43
+ event: ProductWorkflowEvents.REJECTED,
24
44
  }
25
45
  ```
26
46
 
27
- ## Product events
47
+ Medusa calls the handler once per rejected product with the payload above.
48
+
49
+ ## Events from Medusa
50
+
51
+ Medusa's own product workflows emit their events alongside Mercur's. These matter for the catalog:
52
+
53
+ - **`product.updated`**: from Medusa's `updateProductsWorkflow`, which `assignProductsToCategoryWorkflow` and the admin product update both run
54
+ - **`product.deleted`**: from Medusa's `deleteProductsWorkflow`
55
+ - **`product-variant.created`**: from `createProductVariantsWorkflow`, which Mercur's create workflow runs for `variants`
28
56
 
29
- | Event | Emitted when | Payload |
30
- | --- | --- | --- |
31
- | `product.created` | A master product is created | `{ id }` |
32
- | `product.published` | A proposed product is approved (`→ published`) | `{ id, internal_note? }` |
33
- | `product.rejected` | A proposed product is rejected | `{ id, message? }` |
34
- | `product.change-requested` | A revision is requested on a proposed product | `{ id, message?, actor_id? }` |
35
-
36
- <Note>
37
- These are the marketplace lifecycle events emitted by Mercur's product
38
- workflows. Medusa's Product module also emits its own native events (e.g.
39
- `product.updated`, `product-variant.created`) for lower-level changes. See the
40
- [Medusa events reference](https://docs.medusajs.com/resources/events-reference).
41
- </Note>
57
+ See the [Medusa events reference](https://docs.medusajs.com/resources/references/events) for the full list. Edits that stores submit through the product edit pipeline emit `product-change.*` events, listed in the [product edit events](/platform/product-edit/reference/events).
@@ -1,44 +1,212 @@
1
1
  ---
2
- title: "Links to other modules"
2
+ title: "Catalog module links"
3
3
  sidebarTitle: "Links"
4
- description: "How the catalog links to sellers, offers, attributes, and media."
4
+ description: "Every Mercur link on Product module models: both sides, cardinality, source file, and how to query it."
5
5
  ---
6
6
 
7
- Modules in Mercur never reference each other directly. They connect through
8
- **module links**. The Catalog (Medusa's `Product` module) is wired into the
9
- marketplace layer with a set of links defined in Mercur core. Once a link is
10
- defined, you retrieve related records with `query.graph` using the link alias.
7
+ Mercur connects Medusa's Product module to its own modules through module links defined in `packages/core/src/links`. Read across a link with `query.graph` and the link's alias. Read-only links have no pivot table: they resolve from a foreign key column on one side, and you change them by updating that column.
8
+
9
+ Resolve `query` from the container before you run the examples:
10
+
11
+ ```ts
12
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
13
+
14
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
15
+ ```
16
+
17
+ ## Product and Seller
18
+
19
+ The store allowlist. A product with no rows is open to every store.
20
+
21
+ - **Sides**: `Product` (many) and `Seller` (many)
22
+ - **Storage**: pivot table `product_seller` with `product_id` and `seller_id`
23
+ - **Alias on product**: `sellers`
24
+ - **File**: `product-seller-link.ts`
25
+
26
+ ```ts
27
+ const { data } = await query.graph({
28
+ entity: "product",
29
+ fields: ["id", "sellers.id", "sellers.name"],
30
+ filters: { id: "prod_01JD8Q1234567" },
31
+ })
32
+ ```
33
+
34
+ Write it with `linkSellersToProductWorkflow` or the `seller_ids` input of `createProductsWorkflow`.
35
+
36
+ ## ProductCategory and Seller
37
+
38
+ Links stores to categories. No shipped route filters products by it.
39
+
40
+ - **Sides**: `ProductCategory` (many) and `Seller` (many)
41
+ - **Storage**: pivot table generated by Medusa
42
+ - **Alias on category**: `sellers`
43
+ - **File**: `category-seller-link.ts`
44
+
45
+ ```ts
46
+ const { data } = await query.graph({
47
+ entity: "product_category",
48
+ fields: ["id", "sellers.id"],
49
+ filters: { id: "pcat_01JD8Q1234567" },
50
+ })
51
+ ```
52
+
53
+ Write it with `linkSellersToProductCategoryWorkflow`.
54
+
55
+ ## Offer and Product
56
+
57
+ Each offer points at the master product it sells.
58
+
59
+ - **Sides**: `Offer` (one) to `Product` (one)
60
+ - **Storage**: read-only, through `offer.product_id`
61
+ - **Alias on offer**: `product`
62
+ - **File**: `offer-product-link.ts`
63
+
64
+ ```ts
65
+ const { data } = await query.graph({
66
+ entity: "offer",
67
+ fields: ["id", "product.id", "product.title"],
68
+ filters: { product_id: "prod_01JD8Q1234567" },
69
+ })
70
+ ```
71
+
72
+ To list a product's offers, query `offer` filtered by `product_id`, as above.
73
+
74
+ ## Offer and ProductVariant
75
+
76
+ Each offer sells exactly one variant.
77
+
78
+ - **Sides**: `Offer` (one) to `ProductVariant` (one)
79
+ - **Storage**: read-only, through `offer.variant_id`
80
+ - **Alias on offer**: `product_variant`
81
+ - **File**: `offer-variant-link.ts`
82
+
83
+ ```ts
84
+ const { data } = await query.graph({
85
+ entity: "offer",
86
+ fields: ["id", "product_variant.title", "product_variant.sku"],
87
+ filters: { variant_id: "variant_01JD8Q1234567" },
88
+ })
89
+ ```
90
+
91
+ ## Product and ProductChange
92
+
93
+ The audit and edit history of a product, including every `PRODUCT_ADD` and `STATUS_CHANGE` record.
94
+
95
+ - **Sides**: `Product` (one) to `ProductChange` (many)
96
+ - **Storage**: read-only, through `product_change.product_id`
97
+ - **Alias on product**: `changes`
98
+ - **File**: `product-change-link.ts`
11
99
 
12
100
  ```ts
13
- const { data: products } = await query.graph({
101
+ const { data } = await query.graph({
14
102
  entity: "product",
15
- fields: ["id", "title", "sellers.*", "offers.*", "changes.*"],
103
+ fields: ["id", "changes.status", "changes.created_by"],
104
+ filters: { id: "prod_01JD8Q1234567" },
16
105
  })
17
106
  ```
18
107
 
19
- ## Marketplace
108
+ ## Product and ProductAttributeValue
20
109
 
21
- | Linked module | Relationship |
22
- | --- | --- |
23
- | **Seller** | Many-to-many allowlist (`product_seller`) that controls which stores may sell a master product. Categories carry the same via `category_seller`. |
24
- | **Offer** | A product has many offers (`offer.product_id`, read-only). Offers are how a store sells against the master product. |
25
- | **Product change** | A product has many change records (`product.changes`, read-only): the immutable submission and review audit trail. |
110
+ The attribute values attached to a product.
26
111
 
27
- ## Attributes
112
+ - **Sides**: `Product` (many) and `ProductAttributeValue` (many)
113
+ - **Storage**: pivot table `product_attribute_value_link`
114
+ - **Alias on product**: `product_attribute_values`
115
+ - **File**: `product-attribute-value-pivot-link.ts`
28
116
 
29
- | Linked module | Relationship |
30
- | --- | --- |
31
- | **Product attribute** | Product-scoped attributes resolve under `product.scoped_attributes` (read-only). Categories link attributes through `product_category_attribute`. |
117
+ ```ts
118
+ const { data } = await query.graph({
119
+ entity: "product",
120
+ fields: [
121
+ "product_attribute_values.name",
122
+ "product_attribute_values.attribute.handle",
123
+ ],
124
+ filters: { id: "prod_01JD8Q1234567" },
125
+ })
126
+ ```
127
+
128
+ ## Product and scoped ProductAttribute
129
+
130
+ Attributes created inline for one product.
131
+
132
+ - **Sides**: `Product` (one) to `ProductAttribute` (many)
133
+ - **Storage**: read-only, through `product_attribute.product_id`
134
+ - **Alias on product**: `scoped_attributes`
135
+ - **File**: `product-attribute-product-link.ts`
136
+
137
+ ```ts
138
+ const { data } = await query.graph({
139
+ entity: "product",
140
+ fields: ["scoped_attributes.name", "scoped_attributes.values.name"],
141
+ filters: { id: "prod_01JD8Q1234567" },
142
+ })
143
+ ```
144
+
145
+ ## ProductAttribute and ProductCategory
146
+
147
+ Assigns attributes to categories.
148
+
149
+ - **Sides**: `ProductAttribute` (many) and `ProductCategory` (many)
150
+ - **Storage**: pivot table `product_category_attribute`
151
+ - **Alias on attribute**: `categories`
152
+ - **File**: `product-attribute-category-link.ts`
153
+
154
+ ```ts
155
+ const { data } = await query.graph({
156
+ entity: "product_attribute",
157
+ fields: ["id", "categories.id", "categories.name"],
158
+ })
159
+ ```
160
+
161
+ ## Attribute option mirrors
32
162
 
33
- ## Media
163
+ A variant-axis attribute mirrors a native option, and each of its values mirrors an option value. Both links are one-to-one and read-only:
34
164
 
35
- | Linked module | Relationship |
36
- | --- | --- |
37
- | **Media** | Categories and collections link their artwork through the `media_images` alias (a `MediaImage` list). |
165
+ - **`ProductAttribute` to `ProductOption`**: through `product_attribute.product_option_id`, in `product-attribute-option-mirror-link.ts`
166
+ - **`ProductAttributeValue` to `ProductOptionValue`**: through `product_attribute_value.product_option_value_id`, in `product-attribute-value-option-value-mirror-link.ts`
167
+
168
+ Read the ids from the attribute side:
169
+
170
+ ```ts
171
+ const { data } = await query.graph({
172
+ entity: "product_attribute",
173
+ fields: ["id", "product_option_id", "values.product_option_value_id"],
174
+ filters: { is_variant_axis: true },
175
+ })
176
+ ```
177
+
178
+ ## Product and Review
179
+
180
+ Customer reviews of a product.
181
+
182
+ - **Sides**: `Product` (one) to `Review` (many)
183
+ - **Storage**: pivot table generated by Medusa
184
+ - **Alias on product**: `reviews`
185
+ - **File**: `product-review.ts`
186
+
187
+ ```ts
188
+ const { data } = await query.graph({
189
+ entity: "product",
190
+ fields: ["id", "reviews.id"],
191
+ filters: { id: "prod_01JD8Q1234567" },
192
+ })
193
+ ```
194
+
195
+ ## ProductCategory and ProductCollection images
196
+
197
+ Categories and collections own gallery and icon images from Mercur's media module.
198
+
199
+ - **Sides**: `ProductCategory` (one) to `MediaImage` (many), and `ProductCollection` (one) to `MediaImage` (many)
200
+ - **Alias**: `media_images` on both
201
+ - **Files**: `media-product-category-link.ts` and `media-product-collection-link.ts`
202
+
203
+ ```ts
204
+ const { data } = await query.graph({
205
+ entity: "product_category",
206
+ fields: ["id", "media_images.url", "media_images.is_thumbnail"],
207
+ })
208
+ ```
38
209
 
39
- <Note>
40
- Read-only links (Offer, Product change, scoped attributes) are resolved from
41
- the field on the owning record and can't be written through the link itself.
42
- The `media_images` alias is deliberately **not** the bare `images` alias. That
43
- would shadow the native `Product.images` relation and break product queries.
44
- </Note>
210
+ <Warning>
211
+ Don't define a link with the alias `images` on a category or collection. Both models live in the Product module, and that alias shadows `Product.images`, which breaks every product query.
212
+ </Warning>
@@ -1,52 +1,74 @@
1
1
  ---
2
- title: "Service reference"
2
+ title: "Product module service"
3
3
  sidebarTitle: "Service"
4
- description: "Medusa's Product module service: methods for working with records directly."
4
+ description: "Resolve Medusa's Product module service with Modules.PRODUCT and call its methods for catalog records."
5
5
  ---
6
6
 
7
- The catalog is backed by **Medusa's Product module**, not a Mercur-specific one.
8
- Resolve its service from the container with the `Modules.PRODUCT` key to read and
9
- write products, variants, categories, and collections directly, without going
10
- through a workflow. Use it inside custom services, subscribers, or scheduled jobs.
7
+ The catalog uses Medusa's Product module service. Mercur adds no catalog service of its own. Resolve it with `Modules.PRODUCT` to read and write products, variants, options, categories, collections, tags, and types from a subscriber, a scheduled job, or a custom step.
8
+
9
+ ## Resolve the service
10
+
11
+ The service key comes from `@medusajs/framework/utils`, not `MercurModules`:
11
12
 
12
13
  ```ts
13
14
  import { Modules } from "@medusajs/framework/utils"
14
15
 
15
- const productModuleService = container.resolve(Modules.PRODUCT)
16
+ const productService = container.resolve(Modules.PRODUCT)
16
17
 
17
- const [products, count] = await productModuleService.listAndCountProducts({
18
- status: "published",
19
- })
18
+ const [products, count] = await productService.listAndCountProducts(
19
+ { status: "published" },
20
+ { take: 20, select: ["id", "title"] }
21
+ )
20
22
  ```
21
23
 
22
- <Note>
23
- Because this is Medusa's own module, the service key is `Modules.PRODUCT` from
24
- `@medusajs/framework/utils`, **not** a `MercurModules` key. Mercur adds its
25
- marketplace behavior (allowlist, review lifecycle, audit trail) as
26
- [workflows](/platform/catalog/reference/workflows) layered on top of this
27
- service.
28
- </Note>
24
+ `listAndCountProducts` returns a page of products and the total count.
25
+
26
+ <Warning>
27
+ The service writes rows directly. It doesn't link stores, record `ProductChange` entries, or emit Mercur's `product.*` events. Use the [catalog workflows](/platform/catalog/reference/workflows) for creates and reviews, and `query.graph` to read linked data such as `sellers`.
28
+ </Warning>
29
29
 
30
- ## Generated methods
30
+ ## Product methods
31
31
 
32
- Each data model gets a standard set of auto-generated methods. For `Product`:
32
+ These methods act on `Product`:
33
33
 
34
- | Method | Description |
34
+ | Method | Signature |
35
35
  | --- | --- |
36
- | `createProducts(data)` | Create one or more products |
37
- | `retrieveProduct(id, config?)` | Retrieve a product by id |
38
- | `listProducts(filters?, config?)` | List products matching filters |
39
- | `listAndCountProducts(filters?, config?)` | List products with a total count |
40
- | `updateProducts(data)` | Update one or more products |
41
- | `deleteProducts(ids)` | Delete one or more products |
36
+ | `retrieveProduct` | `(productId: string, config?) => Promise<ProductDTO>` |
37
+ | `listProducts` | `(filters?, config?) => Promise<ProductDTO[]>` |
38
+ | `listAndCountProducts` | `(filters?, config?) => Promise<[ProductDTO[], number]>` |
39
+ | `createProducts` | `(data: CreateProductDTO \| CreateProductDTO[]) => Promise<ProductDTO \| ProductDTO[]>` |
40
+ | `upsertProducts` | `(data: UpsertProductDTO \| UpsertProductDTO[]) => Promise<ProductDTO \| ProductDTO[]>` |
41
+ | `updateProducts` | `(id: string, data) => Promise<ProductDTO>` or `(selector, data) => Promise<ProductDTO[]>` |
42
+ | `deleteProducts` | `(productIds: string[]) => Promise<void>` |
43
+ | `softDeleteProducts` | `(productIds: string[], config?) => Promise<Record<string, string[]> \| void>` |
44
+ | `restoreProducts` | `(productIds: string[], config?) => Promise<Record<string, string[]> \| void>` |
42
45
 
43
- The same set exists for every model in the module, such as `ProductVariant`,
44
- `ProductCategory`, `ProductCollection`, `ProductTag`, and `ProductType` (e.g.
45
- `createProductVariants`, `listProductCategories`, `updateProductCollections`).
46
+ `filters` takes `FilterableProductProps`, such as `id`, `status`, `handle`, or `q`. `config` takes `select`, `relations`, `skip`, `take`, and `order`.
46
47
 
47
- <Warning>
48
- Prefer [workflows](/platform/catalog/reference/workflows) for anything with
49
- side effects, such as submissions, review transitions, and allowlist changes. The
50
- service writes records directly and does **not** run the marketplace layer,
51
- emit Mercur's product events, or record the audit trail.
52
- </Warning>
48
+ ## Methods for the other models
49
+
50
+ The same method set exists for each model in the module, named after it:
51
+
52
+ | Model | Example methods |
53
+ | --- | --- |
54
+ | `ProductVariant` | `retrieveProductVariant`, `listProductVariants`, `createProductVariants`, `updateProductVariants` |
55
+ | `ProductOption` | `listProductOptions`, `createProductOptions`, `upsertProductOptions`, `deleteProductOptions` |
56
+ | `ProductOptionValue` | `listProductOptionValues`, `createProductOptionValues`, `updateProductOptionValues` |
57
+ | `ProductCategory` | `retrieveProductCategory`, `listProductCategories`, `createProductCategories` |
58
+ | `ProductCollection` | `retrieveProductCollection`, `listProductCollections`, `updateProductCollections` |
59
+ | `ProductTag` | `listProductTags`, `createProductTags`, `upsertProductTags`, `deleteProductTags` |
60
+ | `ProductType` | `listProductTypes`, `createProductTypes`, `upsertProductTypes`, `deleteProductTypes` |
61
+
62
+ ## Option and image methods
63
+
64
+ The service also has methods that manage shared options and variant images:
65
+
66
+ | Method | Does |
67
+ | --- | --- |
68
+ | `addProductOptionToProduct` | Attaches an existing option to a product, from `{ product_id, product_option_id }` or an array |
69
+ | `removeProductOptionFromProduct` | Detaches an option from a product |
70
+ | `updateProductOptionValuesOnProduct` | Changes which values of an option a product uses |
71
+ | `addImageToVariant` | Links a product image to a variant |
72
+ | `removeImageFromVariant` | Unlinks a product image from a variant |
73
+
74
+ Mercur's attribute workflows create and attach the options behind variant-axis attributes. Prefer those workflows over the option methods, so each attribute stays in sync with the option it mirrors.