@mercurjs/docs 2.3.8-canary.3 → 2.3.9-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.
Files changed (120) hide show
  1. package/content/learn/architecture.mdx +166 -263
  2. package/content/learn/introduction.mdx +9 -1
  3. package/content/learn/use-cases.mdx +70 -0
  4. package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
  5. package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
  6. package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
  7. package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
  8. package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
  9. package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
  10. package/content/platform/attribute/overview.mdx +37 -33
  11. package/content/platform/attribute/reference/data-models.mdx +53 -38
  12. package/content/platform/attribute/reference/events.mdx +47 -24
  13. package/content/platform/attribute/reference/links.mdx +98 -27
  14. package/content/platform/attribute/reference/service.mdx +71 -32
  15. package/content/platform/attribute/reference/workflows.mdx +158 -28
  16. package/content/platform/catalog/concepts/master-products.mdx +39 -37
  17. package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
  18. package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
  19. package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
  20. package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
  21. package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
  22. package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
  23. package/content/platform/catalog/overview.mdx +39 -35
  24. package/content/platform/catalog/reference/data-models.mdx +154 -43
  25. package/content/platform/catalog/reference/events.mdx +41 -25
  26. package/content/platform/catalog/reference/links.mdx +196 -28
  27. package/content/platform/catalog/reference/service.mdx +57 -35
  28. package/content/platform/catalog/reference/workflows.mdx +124 -30
  29. package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
  30. package/content/platform/commission/concepts/rule-matching.mdx +53 -63
  31. package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
  32. package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
  33. package/content/platform/commission/guides/create-a-rate.mdx +101 -53
  34. package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
  35. package/content/platform/commission/overview.mdx +43 -35
  36. package/content/platform/commission/reference/data-models.mdx +59 -45
  37. package/content/platform/commission/reference/events.mdx +28 -34
  38. package/content/platform/commission/reference/links.mdx +42 -33
  39. package/content/platform/commission/reference/service.mdx +103 -28
  40. package/content/platform/commission/reference/workflows.mdx +101 -27
  41. package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
  42. package/content/platform/offer/concepts/shipping.mdx +28 -29
  43. package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
  44. package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
  45. package/content/platform/offer/guides/create-an-offer.mdx +112 -60
  46. package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
  47. package/content/platform/offer/overview.mdx +39 -32
  48. package/content/platform/offer/reference/data-models.mdx +77 -47
  49. package/content/platform/offer/reference/events.mdx +44 -18
  50. package/content/platform/offer/reference/links.mdx +92 -40
  51. package/content/platform/offer/reference/service.mdx +66 -31
  52. package/content/platform/offer/reference/workflows.mdx +146 -37
  53. package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
  54. package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
  55. package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
  56. package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
  57. package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
  58. package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
  59. package/content/platform/order-group/overview.mdx +42 -32
  60. package/content/platform/order-group/reference/data-models.mdx +48 -31
  61. package/content/platform/order-group/reference/events.mdx +29 -17
  62. package/content/platform/order-group/reference/links.mdx +90 -29
  63. package/content/platform/order-group/reference/service.mdx +55 -31
  64. package/content/platform/order-group/reference/workflows.mdx +83 -24
  65. package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
  66. package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
  67. package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
  68. package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
  69. package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
  70. package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
  71. package/content/platform/payout/overview.mdx +43 -34
  72. package/content/platform/payout/reference/data-models.mdx +63 -36
  73. package/content/platform/payout/reference/events.mdx +56 -30
  74. package/content/platform/payout/reference/links.mdx +52 -22
  75. package/content/platform/payout/reference/service.mdx +72 -33
  76. package/content/platform/payout/reference/workflows.mdx +80 -19
  77. package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
  78. package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
  79. package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
  80. package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
  81. package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
  82. package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
  83. package/content/platform/product-edit/overview.mdx +41 -34
  84. package/content/platform/product-edit/reference/data-models.mdx +73 -47
  85. package/content/platform/product-edit/reference/events.mdx +39 -29
  86. package/content/platform/product-edit/reference/links.mdx +32 -24
  87. package/content/platform/product-edit/reference/service.mdx +45 -26
  88. package/content/platform/product-edit/reference/workflows.mdx +173 -31
  89. package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
  90. package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
  91. package/content/platform/review/concepts/the-review-model.mdx +52 -45
  92. package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
  93. package/content/platform/review/guides/create-a-review.mdx +109 -39
  94. package/content/platform/review/guides/moderate-a-review.mdx +83 -37
  95. package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
  96. package/content/platform/review/overview.mdx +43 -33
  97. package/content/platform/review/reference/data-models.mdx +41 -27
  98. package/content/platform/review/reference/events.mdx +55 -37
  99. package/content/platform/review/reference/links.mdx +80 -22
  100. package/content/platform/review/reference/service.mdx +61 -33
  101. package/content/platform/review/reference/workflows.mdx +80 -24
  102. package/content/platform/store/concepts/lifecycle.mdx +68 -44
  103. package/content/platform/store/concepts/store-entity.mdx +45 -27
  104. package/content/platform/store/concepts/team.mdx +56 -35
  105. package/content/platform/store/guides/create-a-store.mdx +102 -35
  106. package/content/platform/store/guides/manage-the-team.mdx +122 -32
  107. package/content/platform/store/guides/moderate-a-store.mdx +107 -38
  108. package/content/platform/store/overview.mdx +41 -34
  109. package/content/platform/store/reference/data-models.mdx +132 -57
  110. package/content/platform/store/reference/events.mdx +42 -17
  111. package/content/platform/store/reference/links.mdx +67 -49
  112. package/content/platform/store/reference/service.mdx +105 -33
  113. package/content/platform/store/reference/workflows.mdx +304 -24
  114. package/content/references/api/conventions.mdx +20 -0
  115. package/content/resources/integrations/magento2.mdx +162 -0
  116. package/content/resources/integrations/overview.mdx +10 -0
  117. package/content/resources/integrations/stripe-connect.mdx +1 -1
  118. package/content/resources/tutorials/custom-api-route.mdx +3 -2
  119. package/llms.txt +113 -111
  120. package/package.json +1 -1
@@ -1,87 +1,97 @@
1
1
  ---
2
- title: "Review"
2
+ title: "Reviews"
3
3
  sidebarTitle: "Overview"
4
- description: "Collect, moderate, and surface customer ratings for products and sellers."
4
+ description: "Let customers rate the products and stores they bought from, then moderate, answer, and average those ratings."
5
5
  ---
6
6
 
7
- Use Mercur to let customers rate the products they bought and the stores they
8
- bought from.
7
+ Use Mercur to let customers rate the products they bought and the stores that sold them.
9
8
 
10
- The Review domain captures a single rating model that points at either a product
11
- or a seller. It ties each review back to the order that earned it, and it moves
12
- through a moderation lifecycle before it goes public. Stores can respond to their
13
- reviews, and aggregate ratings roll up onto public seller and product pages. All
14
- of it is exposed directly through the Admin, Vendor, and Store APIs.
9
+ A review is one customer’s rating of one target: a master product or a store. Each review is tied to the order that earned it, starts as `pending`, and waits for an operator to publish or reject it. The Review module ships inside `@mercurjs/core`, so it’s registered with the rest of the marketplace modules and needs no extra install. It connects to products, stores, orders, and customers through module links.
10
+
11
+ <img className="block dark:hidden" src="/images/figures/review-overview-light.svg" alt="Behind, createReviewWorkflow, which writes every review. In front, the review table: order #1001 backs a published seller review of ACME and a pending product review of the linen shirt, beside a rejected review from order #998." />
12
+ <img className="hidden dark:block" src="/images/figures/review-overview-dark.svg" alt="Behind, createReviewWorkflow, which writes every review. In front, the review table: order #1001 backs a published seller review of ACME and a pending product review of the linen shirt, beside a rejected review from order #998." />
15
13
 
16
14
  <Note>
17
- **One model, two targets.** A single `Review` entity (id prefix `rev`) covers
18
- both product and seller reviews. A `reference` discriminator decides which one
19
- a given review is about. There is no separate product-review or seller-review
20
- table.
15
+ The panels call the target a store. The code calls the same record a `Seller`, so a store review has `reference: "seller"`. Reviews use the id prefix `rev` and resolve with `MercurModules.REVIEW`.
21
16
  </Note>
22
17
 
23
18
  ## Key features
24
19
 
25
- - **Product & seller reviews:** one rating model, discriminated by a `reference` field.
26
- - **Order-backed:** every review is linked to the order that earned it, with one review per target per order.
27
- - **Moderation lifecycle:** reviews start `pending` and are moderated to `published` or `rejected`.
28
- - **Store responses:** a store can attach a single public response to each of its reviews.
29
- - **Aggregate ratings:** average ratings computed per product and per seller for storefront display.
30
- - **Customer notes:** an optional free-text note alongside the numeric rating.
20
+ The Review module covers a review from submission to the number on a storefront:
21
+
22
+ - **One model, two targets**: a single `Review` record rates a product or a store, chosen by its `reference` field
23
+ - **Order-backed**: the order must belong to the customer, and each order can review a given target once
24
+ - **Moderation**: reviews start `pending`, and an operator sets them to `published` or `rejected`
25
+ - **Store responses**: a store answers a review once through `seller_note`
26
+ - **Rating helpers**: service methods compute average ratings per product or per store at query time
27
+ - **Compensation**: every review workflow rolls its write back if a later step fails
28
+
29
+ ## How it works
30
+
31
+ A review moves through four stages:
32
+
33
+ 1. A customer calls `POST /store/reviews` with an order they own, which runs `createReviewWorkflow`
34
+ 2. The workflow validates the order, creates the review as `pending`, and links it to its customer, order, and target
35
+ 3. An operator publishes or rejects it with `updateReviewWorkflow`, and the store answers it with `respondReviewWorkflow`
36
+ 4. Your own storefront route reads published reviews and averages them with the service’s rating helpers
37
+
38
+ <Warning>
39
+ Core ships no public endpoint that lists a product’s or a store’s published reviews, and no route returns an average rating. `GET /store/reviews` returns only the signed-in customer’s own reviews. Add a route for storefront display, as shown in [Compute aggregate ratings](/platform/review/guides/compute-aggregate-ratings).
40
+ </Warning>
31
41
 
32
42
  ## Get started
33
43
 
34
- Learn how the domain fits together.
44
+ Learn how reviews, their targets, and their statuses fit together.
35
45
 
36
46
  <CardGroup cols={2}>
37
47
  <Card title="The review model" icon="star" href="/platform/review/concepts/the-review-model">
38
- The single review entity, its rating, notes, and status.
48
+ The fields of a review: rating, notes, status, and display id.
39
49
  </Card>
40
50
  <Card title="Product vs seller reviews" icon="tags" href="/platform/review/concepts/product-vs-seller-reviews">
41
- The reference discriminator and the links that anchor each review.
51
+ How `reference` picks the target and which links anchor each review.
42
52
  </Card>
43
53
  <Card title="Ratings & moderation" icon="gauge" href="/platform/review/concepts/ratings-and-moderation">
44
- The status lifecycle, store responses, and aggregate ratings.
54
+ Status changes, store responses, and how averages are computed.
45
55
  </Card>
46
56
  </CardGroup>
47
57
 
48
58
  ## Examples
49
59
 
50
- Build against the Review domain in your own code.
60
+ Build against reviews in your own server code.
51
61
 
52
62
  <CardGroup cols={2}>
53
63
  <Card title="Create a review" icon="plus" href="/platform/review/guides/create-a-review">
54
- Run `createReviewWorkflow` from a route or script.
64
+ Run `createReviewWorkflow` from a custom route.
55
65
  </Card>
56
66
  <Card title="Moderate a review" icon="gavel" href="/platform/review/guides/moderate-a-review">
57
67
  Publish, reject, and delete reviews in code.
58
68
  </Card>
59
69
  <Card title="Respond to a review" icon="reply" href="/platform/review/guides/respond-to-a-review">
60
- Attach a store's response with `respondReviewWorkflow`.
70
+ Attach a store’s answer with `respondReviewWorkflow`.
61
71
  </Card>
62
72
  <Card title="Aggregate ratings" icon="calculator" href="/platform/review/guides/compute-aggregate-ratings">
63
- Compute average ratings for products and sellers.
73
+ Serve average ratings for products and stores.
64
74
  </Card>
65
75
  </CardGroup>
66
76
 
67
77
  ## Resources
68
78
 
69
- Data models, links, workflows, service methods, and events for the Review domain.
79
+ The data model, links, workflows, service methods, and events behind reviews.
70
80
 
71
81
  <CardGroup cols={2}>
72
82
  <Card title="Data models" icon="table" href="/platform/review/reference/data-models">
73
- The `Review` entity and its fields.
83
+ The `Review` model, its fields, and its indexes.
74
84
  </Card>
75
85
  <Card title="Links" icon="link" href="/platform/review/reference/links">
76
- How reviews link to products, sellers, orders, and customers.
86
+ The four links to products, sellers, orders, and customers.
77
87
  </Card>
78
88
  <Card title="Workflows" icon="diagram-project" href="/platform/review/reference/workflows">
79
- Create, update, respond, and delete workflows.
89
+ Create, update, respond, and delete workflows with their steps.
80
90
  </Card>
81
91
  <Card title="Service" icon="gear" href="/platform/review/reference/service">
82
- Module service methods, including aggregate-rating helpers.
92
+ Generated methods and the rating helpers.
83
93
  </Card>
84
94
  <Card title="Events" icon="bell" href="/platform/review/reference/events">
85
- Running side effects as reviews change.
95
+ The mutation events the module emits.
86
96
  </Card>
87
97
  </CardGroup>
@@ -1,36 +1,50 @@
1
1
  ---
2
- title: "Data models"
2
+ title: "Review data model"
3
3
  sidebarTitle: "Data models"
4
- description: "The data models owned by the Review domain."
4
+ description: "Every field and index of the Review model in the Review module."
5
5
  ---
6
6
 
7
- The Review domain is owned by the **Review module**. This reference lists its
8
- data model and fields.
7
+ The Review module owns one data model, `Review`. It’s defined in `packages/core/src/modules/review/models/review.ts` and stored in the `review` table. Resolve the module with `MercurModules.REVIEW`.
9
8
 
10
9
  ## Review
11
10
 
12
- Table `review`, id prefix `rev`. A customer's rating of a single product or
13
- seller.
11
+ A customer’s rating of one product or one seller. Table `review`, id prefix `rev`.
14
12
 
15
- | Field | Type | Notes |
13
+ | Field | Type | Description |
16
14
  | --- | --- | --- |
17
- | `id` | text | Primary key |
18
- | `display_id` | serial | Human-readable auto-incrementing number |
19
- | `reference` | enum | `product` or `seller`, the review's target type |
20
- | `rating` | integer | The numeric score |
21
- | `customer_note` | text | Nullable, searchable. The customer's note |
22
- | `seller_note` | text | Nullable, searchable. The store's single response |
23
- | `status` | enum | `pending`, `published`, or `rejected`; default `pending` |
24
- | `created_at` | dateTime | Set on creation |
25
- | `updated_at` | dateTime | Updated on change |
26
- | `deleted_at` | dateTime | Nullable; set by soft delete |
27
-
28
- The target record itself is not a column on this model. It's resolved through a
29
- module link chosen by the `reference` value. See the
30
- [Links reference](/platform/review/reference/links).
31
-
32
- <Note>
33
- The `review` table is indexed on `deleted_at` for soft-delete filtering.
34
- Deleting a review through the workflow is a soft delete. The row is retained
35
- and can be restored.
36
- </Note>
15
+ | `id` | `text` | Primary key with the prefix `rev`, such as `rev_01JD8Q1234567` |
16
+ | `display_id` | `autoincrement` | Serial number for display, stored as a Postgres `serial` |
17
+ | `reference` | `enum` | The target type: `product` or `seller`. Required |
18
+ | `rating` | `number` | The score, stored as an `integer`. Required, no range constraint in the model |
19
+ | `customer_note` | `text` | The customer’s text. Nullable, searchable |
20
+ | `seller_note` | `text` | The store’s answer. Nullable, searchable |
21
+ | `status` | `enum` | `pending`, `published`, or `rejected`. Default `pending` |
22
+ | `created_at` | `timestamptz` | Set on creation |
23
+ | `updated_at` | `timestamptz` | Set on every change |
24
+ | `deleted_at` | `timestamptz` | Nullable. Set by a soft delete |
25
+
26
+ The model has no column for its target, order, or customer. Those live in [module links](/platform/review/reference/links), and `reference` tells you whether to follow `product` or `seller`.
27
+
28
+ ### Indexes
29
+
30
+ Two indexes speed up the lists the APIs run:
31
+
32
+ | Index | Columns | Condition |
33
+ | --- | --- | --- |
34
+ | `IDX_review_deleted_at` | `deleted_at` | `deleted_at IS NULL` |
35
+ | `IDX_review_reference_status_created_at` | `reference`, `status`, `created_at` | `deleted_at IS NULL` |
36
+
37
+ ### Relations
38
+
39
+ The model defines no relations of its own. Through links, a review exposes these `query.graph` fields:
40
+
41
+ | Field | Target | Set when |
42
+ | --- | --- | --- |
43
+ | `product` | Product | `reference` is `product` |
44
+ | `seller` | Seller | `reference` is `seller` |
45
+ | `order` | Order | Always |
46
+ | `customer` | Customer | Always |
47
+
48
+ ### Types
49
+
50
+ `@mercurjs/types` exports `ReviewDTO`, `ReviewReference` (`"product" | "seller"`), and `ReviewStatus` (`"pending" | "published" | "rejected"`) for this model.
@@ -1,61 +1,79 @@
1
1
  ---
2
- title: "Event reference"
2
+ title: "Review events"
3
3
  sidebarTitle: "Events"
4
- description: "Running side effects as reviews are created, moderated, and answered."
4
+ description: "The mutation events the Review module service emits, and how to subscribe to them."
5
5
  ---
6
6
 
7
- The Review workflows handle validation, links, moderation, and compensation. They
8
- do **not** currently emit their own domain events. There is no `review.created`
9
- or `review.published` event to subscribe to today.
7
+ Mercur defines no review events of its own, and the review workflows don’t call `emitEventStep`. The Review module service emits Medusa’s standard mutation events instead, because every method `MedusaService` generates publishes an event when it changes a `Review` row. Subscribe to these to notify a store, refresh a search index, or recompute a cached rating.
10
8
 
11
- To run side effects when a review changes, wrap the review workflows in your own
12
- route or workflow and run the follow-up logic there, or emit your own event and
13
- subscribe to it.
9
+ ## Event list
14
10
 
15
- ## Emit your own event
11
+ Event names follow Medusa’s `<module>.<model>.<action>` pattern, with the module and the model both named `review`:
16
12
 
17
- Emit an event alongside the workflow, then handle it in a subscriber:
13
+ | Event | Emitted when | Payload |
14
+ | --- | --- | --- |
15
+ | `review.review.created` | `createReviewWorkflow` or `createReviews` creates a review | `{ id }` |
16
+ | `review.review.updated` | `updateReviewWorkflow`, `respondReviewWorkflow`, or `updateReviews` changes a review | `{ id }` |
17
+ | `review.review.deleted` | `deleteReviewWorkflow` or `softDeleteReviews` soft deletes a review, or `deleteReviews` removes one | `{ id }` |
18
+ | `review.review.restored` | `restoreReviews` restores a soft deleted review, including a delete rollback | `{ id }` |
18
19
 
19
- ```ts title="src/api/custom/route.ts"
20
- import { Modules } from "@medusajs/framework/utils"
21
- import { createReviewWorkflow } from "@mercurjs/core/workflows"
20
+ The payload holds only the review id. Load the review in the subscriber when you need its fields or links.
22
21
 
23
- export async function POST(req, res) {
24
- const { result } = await createReviewWorkflow(req.scope).run({
25
- input: req.body,
26
- })
22
+ <Note>
23
+ `review.review.updated` doesn’t say which field changed. A moderation, a customer edit, and a store answer all emit the same event, so compare against your own state when you need to tell them apart.
24
+ </Note>
27
25
 
28
- const eventBus = req.scope.resolve(Modules.EVENT_BUS)
29
- await eventBus.emit({ name: "review.created", data: { id: result.id } })
26
+ ## Subscribe to an event
30
27
 
31
- res.status(201).json({ review: result })
32
- }
33
- ```
28
+ Create a subscriber that loads the review and its links:
34
29
 
35
30
  ```ts title="src/subscribers/review-created.ts"
36
31
  import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
32
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
37
33
 
38
- export default async function reviewCreatedHandler({
34
+ export default async function reviewCreated({
39
35
  event,
40
36
  container,
41
37
  }: SubscriberArgs<{ id: string }>) {
42
- const reviewId = event.data.id
43
- // ...notify the store, update a search index, etc.
44
- }
38
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
39
+ const { data: [review] } = await query.graph({
40
+ entity: "review",
41
+ fields: ["rating", "reference", "seller.email", "product.title"],
42
+ filters: { id: event.data.id },
43
+ })
45
44
 
46
- export const config: SubscriberConfig = {
47
- event: "review.created",
45
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
46
+ logger.info(`Review ${event.data.id} rated ${review.rating}`)
48
47
  }
48
+
49
+ export const config: SubscriberConfig = { event: "review.review.created" }
49
50
  ```
50
51
 
51
- <Note>
52
- Because the module ships no events of its own, the event name in the example
53
- above is one **you** define. Keep it consistent across the emit site and the
54
- subscriber.
55
- </Note>
52
+ This logs each new review. Replace the log with your side effect, such as a notification to `review.seller.email` for a store review.
53
+
54
+ ## Emit a named event
55
+
56
+ To give a moderation step its own event, wrap `updateReviewWorkflow` in your own workflow and add Medusa’s `emitEventStep`:
56
57
 
57
- ## React without an event
58
+ ```ts title="src/workflows/publish-review.ts"
59
+ import {
60
+ createWorkflow,
61
+ WorkflowResponse,
62
+ } from "@medusajs/framework/workflows-sdk"
63
+ import { emitEventStep } from "@medusajs/medusa/core-flows"
64
+ import { updateReviewWorkflow } from "@mercurjs/core/workflows"
65
+
66
+ export const publishReviewWorkflow = createWorkflow(
67
+ "publish-review",
68
+ (input: { id: string }) => {
69
+ const review = updateReviewWorkflow.runAsStep({
70
+ input: { id: input.id, status: "published" },
71
+ })
72
+ emitEventStep({ eventName: "review.published", data: { id: input.id } })
73
+
74
+ return new WorkflowResponse(review)
75
+ }
76
+ )
77
+ ```
58
78
 
59
- For side effects that must run transactionally with the review change, add a step
60
- to your own workflow that wraps the review workflow, rather than relying on an
61
- event. Events are handled asynchronously and outside the workflow's compensation.
79
+ `review.published` is a name you define, so subscribe to the same string.
@@ -1,43 +1,101 @@
1
1
  ---
2
- title: "Links to other modules"
2
+ title: "Review links"
3
3
  sidebarTitle: "Links"
4
- description: "How the Review domain links to products, sellers, orders, and customers."
4
+ description: "The four module links that connect reviews to products, sellers, orders, and customers."
5
5
  ---
6
6
 
7
- Modules in Mercur never reference each other directly. They connect through
8
- **module links**. A review carries no foreign keys to its target on the model
9
- itself. Instead, four links anchor each review to the rest of the marketplace.
10
- Once a link is defined, you retrieve related records with `query.graph` using the
11
- link alias.
7
+ A review stores no foreign keys. Four module links in `packages/core/src/links/` connect it to the records around it, and `query.graph` follows them by alias. Every link is a list link on the other module’s side: one product, seller, order, or customer has many reviews, and each review has one of each it’s linked to.
12
8
 
13
9
  ```ts
14
10
  const { data: reviews } = await query.graph({
15
11
  entity: "review",
16
- fields: ["id", "rating", "product.*", "seller.*"],
12
+ fields: ["id", "rating", "product.title", "seller.name", "order.id"],
13
+ filters: { status: "published" },
17
14
  })
18
15
  ```
19
16
 
20
- ## Targets
17
+ This loads published reviews with their target and order. Only one of `product` and `seller` is set on each review.
21
18
 
22
- | Linked module | Relationship |
19
+ ## Product and review
20
+
21
+ Connects a master product to its product reviews. Written by `createReviewWorkflow` through the `create-remote-links` step when `reference` is `product`.
22
+
23
+ | Property | Value |
23
24
  | --- | --- |
24
- | **Product** | A product has many reviews (`product_product_review_review`). Written when `reference` is `product`. |
25
- | **Seller** | A seller has many reviews (`seller_seller_review_review`). Written when `reference` is `seller`. |
25
+ | File | `product-review.ts` |
26
+ | Sides | `Modules.PRODUCT` `product` and `MercurModules.REVIEW` `review` |
27
+ | Cardinality | One product, many reviews |
28
+ | Link table | `product_product_review_review` |
29
+ | Aliases | `review.product`, `product.reviews` |
26
30
 
27
- A review is linked to **either** a product or a seller, never both. The
28
- `reference` field on the review decides which link is created.
31
+ ```ts
32
+ const { data: [product] } = await query.graph({
33
+ entity: "product",
34
+ fields: ["title", "reviews.rating", "reviews.status"],
35
+ filters: { id: "prod_01JD8Q1234567" },
36
+ })
37
+ ```
38
+
39
+ ## Seller and review
29
40
 
30
- ## Provenance
41
+ Connects a seller to its store reviews. Written by `createReviewWorkflow` through the `create-remote-links` step when `reference` is `seller`. The Vendor API scopes reviews to a store through this link.
31
42
 
32
- | Linked module | Relationship |
43
+ | Property | Value |
33
44
  | --- | --- |
34
- | **Order** | Each review is linked to the order that earned it (`order_order_review_review`). |
35
- | **Customer** | Each review is linked to the customer who wrote it (`customer_customer_review_review`). |
45
+ | File | `seller-review.ts` |
46
+ | Sides | `MercurModules.SELLER` `seller` and `MercurModules.REVIEW` `review` |
47
+ | Cardinality | One seller, many reviews |
48
+ | Link table | `seller_seller_review_review` |
49
+ | Aliases | `review.seller`, `seller.reviews` |
36
50
 
37
- These two links are written for every review and back the validation that a
38
- customer can review a target only once per order.
51
+ ```ts
52
+ const { data: [seller] } = await query.graph({
53
+ entity: "seller",
54
+ fields: ["name", "reviews.rating", "reviews.seller_note"],
55
+ filters: { id: "sel_01JD8Q1234567" },
56
+ })
57
+ ```
58
+
59
+ ## Order and review
60
+
61
+ Connects a review to the order that earned it. Written for every review by the `create-review` step. `createReviewWorkflow` reads it to reject a second review of the same target from one order.
62
+
63
+ | Property | Value |
64
+ | --- | --- |
65
+ | File | `order-review.ts` |
66
+ | Sides | `Modules.ORDER` `order` and `MercurModules.REVIEW` `review` |
67
+ | Cardinality | One order, many reviews |
68
+ | Link table | `order_order_review_review` |
69
+ | Aliases | `review.order`, `order.reviews` |
70
+
71
+ ```ts
72
+ const { data: [order] } = await query.graph({
73
+ entity: "order",
74
+ fields: ["display_id", "reviews.reference", "reviews.rating"],
75
+ filters: { id: "order_01JD8Q1234567" },
76
+ })
77
+ ```
78
+
79
+ ## Customer and review
80
+
81
+ Connects a review to the customer who wrote it. Written for every review by the `create-review` step. The Store API limits a customer to their own reviews through this link.
82
+
83
+ | Property | Value |
84
+ | --- | --- |
85
+ | File | `customer-review.ts` |
86
+ | Sides | `Modules.CUSTOMER` `customer` and `MercurModules.REVIEW` `review` |
87
+ | Cardinality | One customer, many reviews |
88
+ | Link table | `customer_customer_review_review` |
89
+ | Aliases | `review.customer`, `customer.reviews` |
90
+
91
+ ```ts
92
+ const { data: [customer] } = await query.graph({
93
+ entity: "customer",
94
+ fields: ["email", "reviews.id", "reviews.status"],
95
+ filters: { id: "cus_01JD8Q1234567" },
96
+ })
97
+ ```
39
98
 
40
99
  <Note>
41
- All four links are list links (`isList: true`) on the owning side. A product,
42
- seller, order, or customer has many reviews.
100
+ `deleteReviewWorkflow` soft deletes the review but keeps all of its link rows. The service’s rating helpers join through the product and seller link tables, so they still count a soft deleted review.
43
101
  </Note>
@@ -1,54 +1,82 @@
1
1
  ---
2
- title: "Service reference"
2
+ title: "Review module service"
3
3
  sidebarTitle: "Service"
4
- description: "The Review module service: CRUD methods and aggregate-rating helpers."
4
+ description: "Resolve the Review module service, its generated CRUD methods, and its three rating helpers."
5
5
  ---
6
6
 
7
- The Review module exposes a service you can resolve from the Medusa container to
8
- read and write records directly, without going through a workflow. Use it inside
9
- custom services, subscribers, scheduled jobs, or route handlers.
7
+ The Review module service, `ReviewModuleService`, reads and writes `Review` records directly and computes average ratings. Use it in subscribers, scheduled jobs, and routes when you don’t need a workflow’s validation, links, or compensation.
8
+
9
+ ## Resolve the service
10
+
11
+ Resolve the service from the Medusa container with `MercurModules.REVIEW`, which is the string `"review"`:
10
12
 
11
13
  ```ts
12
14
  import { MercurModules } from "@mercurjs/types"
13
15
 
14
- const reviewModuleService = container.resolve(MercurModules.REVIEW)
16
+ const reviewService = container.resolve(MercurModules.REVIEW)
15
17
 
16
- const [reviews, count] = await reviewModuleService.listAndCountReviews({
17
- status: "published",
18
- })
18
+ const [reviews, count] = await reviewService.listAndCountReviews(
19
+ { status: "published" },
20
+ { order: { created_at: "DESC" }, take: 20 }
21
+ )
19
22
  ```
20
23
 
24
+ This lists the 20 newest published reviews and the total count. Filters apply to the review’s own fields, so you can’t filter by product or seller here: use `query.graph` from the linked side, as shown in the [Links reference](/platform/review/reference/links).
25
+
26
+ <Warning>
27
+ Writing through the service skips the workflow checks. `createReviews` doesn’t validate the order, reject duplicates, or write any link, and `updateReviews` doesn’t stop a second store answer.
28
+ </Warning>
29
+
21
30
  ## Generated methods
22
31
 
23
- The `Review` model gets a standard set of auto-generated methods:
32
+ `MedusaService` generates these methods for the `Review` model:
24
33
 
25
34
  | Method | Description |
26
35
  | --- | --- |
27
- | `createReviews(data)` | Create one or more reviews |
28
- | `retrieveReview(id, config?)` | Retrieve a review by id |
29
- | `listReviews(filters?, config?)` | List reviews matching filters |
30
- | `listAndCountReviews(filters?, config?)` | List reviews with a total count |
31
- | `updateReviews(data)` | Update one or more reviews |
32
- | `deleteReviews(ids)` | Delete one or more reviews |
33
- | `softDeleteReviews(ids)` | Soft-delete reviews (restorable) |
34
- | `restoreReviews(ids)` | Restore soft-deleted reviews |
36
+ | `retrieveReview(id, config?)` | Retrieve one review. Throws when it doesn’t exist |
37
+ | `listReviews(filters?, config?)` | List reviews |
38
+ | `listAndCountReviews(filters?, config?)` | List reviews and the total count |
39
+ | `createReviews(data)` | Create one review or an array of reviews |
40
+ | `updateReviews(data)` | Update by `id`, or by `{ selector, data }` |
41
+ | `deleteReviews(ids)` | Delete reviews permanently |
42
+ | `softDeleteReviews(ids)` | Set `deleted_at` on reviews |
43
+ | `restoreReviews(ids)` | Clear `deleted_at` on reviews |
35
44
 
36
- ## Aggregate-rating methods
45
+ Each write emits a mutation event, listed in the [Events reference](/platform/review/reference/events).
37
46
 
38
- The service adds three helpers that compute average ratings on demand:
47
+ ## getAvgRating
39
48
 
40
- | Method | Description |
41
- | --- | --- |
42
- | `getAvgRating(type, id)` | Average rating for one `"product"` or `"seller"`; `null` when it has no reviews |
43
- | `getProductsWithRating(fields)` | Product records with an average `rating` joined in |
44
- | `getSellersWithRating(fields)` | Seller records with an average `rating` joined in |
49
+ Returns the average rating of one product or one seller.
50
+
51
+ ```ts
52
+ getAvgRating(
53
+ type: "seller" | "product",
54
+ id: string
55
+ ): Promise<string | null>
56
+ ```
45
57
 
46
- See [Compute aggregate ratings](/platform/review/guides/compute-aggregate-ratings)
47
- for usage.
58
+ The result is the Postgres `AVG` of `rating` as a numeric string, such as `"4.5000000000000000"`, or `null` when the target has no linked reviews. It counts every linked review whatever its `status`, including soft deleted ones.
48
59
 
49
- <Warning>
50
- Prefer [workflows](/platform/review/reference/workflows) for anything with side
51
- effects (creation with its validation and links, responses, moderation). The
52
- service writes records directly and does **not** run the create-flow validation
53
- or manage the target/order/customer links.
54
- </Warning>
60
+ ## getProductsWithRating
61
+
62
+ Returns every product with its average rating.
63
+
64
+ ```ts
65
+ getProductsWithRating(fields: string[]): Promise<Record<string, unknown>[]>
66
+ ```
67
+
68
+ `fields` are column names of the `product` table, such as `["id", "title"]`. Each row gains a `rating` numeric string, or `null`. The query left joins the `product_product_review_review` link table and groups by product, with no filters, pagination, or `deleted_at` checks.
69
+
70
+ ## getSellersWithRating
71
+
72
+ Returns every seller with its average rating.
73
+
74
+ ```ts
75
+ getSellersWithRating(fields: string[]): Promise<Record<string, unknown>[]>
76
+ ```
77
+
78
+ `fields` are column names of the `seller` table, such as `["id", "name"]`. It works like `getProductsWithRating`, joining through `seller_seller_review_review`.
79
+
80
+ <Note>
81
+ The return types above describe the rows. The source declares no explicit return type for the two list helpers, so TypeScript infers Knex’s result type.
82
+ </Note>