@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.
- package/content/learn/architecture.mdx +166 -263
- package/content/learn/introduction.mdx +9 -1
- package/content/learn/use-cases.mdx +70 -0
- package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
- package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
- package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
- package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
- package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
- package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
- package/content/platform/attribute/overview.mdx +37 -33
- package/content/platform/attribute/reference/data-models.mdx +53 -38
- package/content/platform/attribute/reference/events.mdx +47 -24
- package/content/platform/attribute/reference/links.mdx +98 -27
- package/content/platform/attribute/reference/service.mdx +71 -32
- package/content/platform/attribute/reference/workflows.mdx +158 -28
- package/content/platform/catalog/concepts/master-products.mdx +39 -37
- package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
- package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
- package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
- package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
- package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
- package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
- package/content/platform/catalog/overview.mdx +39 -35
- package/content/platform/catalog/reference/data-models.mdx +154 -43
- package/content/platform/catalog/reference/events.mdx +41 -25
- package/content/platform/catalog/reference/links.mdx +196 -28
- package/content/platform/catalog/reference/service.mdx +57 -35
- package/content/platform/catalog/reference/workflows.mdx +124 -30
- package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
- package/content/platform/commission/concepts/rule-matching.mdx +53 -63
- package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
- package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
- package/content/platform/commission/guides/create-a-rate.mdx +101 -53
- package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
- package/content/platform/commission/overview.mdx +43 -35
- package/content/platform/commission/reference/data-models.mdx +59 -45
- package/content/platform/commission/reference/events.mdx +28 -34
- package/content/platform/commission/reference/links.mdx +42 -33
- package/content/platform/commission/reference/service.mdx +103 -28
- package/content/platform/commission/reference/workflows.mdx +101 -27
- package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
- package/content/platform/offer/concepts/shipping.mdx +28 -29
- package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
- package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
- package/content/platform/offer/guides/create-an-offer.mdx +112 -60
- package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
- package/content/platform/offer/overview.mdx +39 -32
- package/content/platform/offer/reference/data-models.mdx +77 -47
- package/content/platform/offer/reference/events.mdx +44 -18
- package/content/platform/offer/reference/links.mdx +92 -40
- package/content/platform/offer/reference/service.mdx +66 -31
- package/content/platform/offer/reference/workflows.mdx +146 -37
- package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
- package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
- package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
- package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
- package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
- package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
- package/content/platform/order-group/overview.mdx +42 -32
- package/content/platform/order-group/reference/data-models.mdx +48 -31
- package/content/platform/order-group/reference/events.mdx +29 -17
- package/content/platform/order-group/reference/links.mdx +90 -29
- package/content/platform/order-group/reference/service.mdx +55 -31
- package/content/platform/order-group/reference/workflows.mdx +83 -24
- package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
- package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
- package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
- package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
- package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
- package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
- package/content/platform/payout/overview.mdx +43 -34
- package/content/platform/payout/reference/data-models.mdx +63 -36
- package/content/platform/payout/reference/events.mdx +56 -30
- package/content/platform/payout/reference/links.mdx +52 -22
- package/content/platform/payout/reference/service.mdx +72 -33
- package/content/platform/payout/reference/workflows.mdx +80 -19
- package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
- package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
- package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
- package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
- package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
- package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
- package/content/platform/product-edit/overview.mdx +41 -34
- package/content/platform/product-edit/reference/data-models.mdx +73 -47
- package/content/platform/product-edit/reference/events.mdx +39 -29
- package/content/platform/product-edit/reference/links.mdx +32 -24
- package/content/platform/product-edit/reference/service.mdx +45 -26
- package/content/platform/product-edit/reference/workflows.mdx +173 -31
- package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
- package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
- package/content/platform/review/concepts/the-review-model.mdx +52 -45
- package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
- package/content/platform/review/guides/create-a-review.mdx +109 -39
- package/content/platform/review/guides/moderate-a-review.mdx +83 -37
- package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
- package/content/platform/review/overview.mdx +43 -33
- package/content/platform/review/reference/data-models.mdx +41 -27
- package/content/platform/review/reference/events.mdx +55 -37
- package/content/platform/review/reference/links.mdx +80 -22
- package/content/platform/review/reference/service.mdx +61 -33
- package/content/platform/review/reference/workflows.mdx +80 -24
- package/content/platform/store/concepts/lifecycle.mdx +68 -44
- package/content/platform/store/concepts/store-entity.mdx +45 -27
- package/content/platform/store/concepts/team.mdx +56 -35
- package/content/platform/store/guides/create-a-store.mdx +102 -35
- package/content/platform/store/guides/manage-the-team.mdx +122 -32
- package/content/platform/store/guides/moderate-a-store.mdx +107 -38
- package/content/platform/store/overview.mdx +41 -34
- package/content/platform/store/reference/data-models.mdx +132 -57
- package/content/platform/store/reference/events.mdx +42 -17
- package/content/platform/store/reference/links.mdx +67 -49
- package/content/platform/store/reference/service.mdx +105 -33
- package/content/platform/store/reference/workflows.mdx +304 -24
- package/content/references/api/conventions.mdx +20 -0
- package/content/resources/integrations/magento2.mdx +162 -0
- package/content/resources/integrations/overview.mdx +10 -0
- package/content/resources/integrations/stripe-connect.mdx +1 -1
- package/content/resources/tutorials/custom-api-route.mdx +3 -2
- package/llms.txt +113 -111
- package/package.json +1 -1
|
@@ -1,22 +1,59 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Attribute events"
|
|
3
3
|
sidebarTitle: "Events"
|
|
4
|
-
description: "Events
|
|
4
|
+
description: "Events the Product Attribute workflows emit, when they fire, and their payloads."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
The
|
|
8
|
-
Subscribe to them to run side effects, such as reindexing storefront filters,
|
|
9
|
-
syncing an external PIM, or kicking off follow-up workflows, instead of polling.
|
|
7
|
+
The catalog workflows emit 6 events as attributes and values change. Subscribe to them to reindex storefront facets, sync an external product information system, or start your own workflows. The event names are exported as `ProductAttributeWorkflowEvents` and `ProductAttributeValueWorkflowEvents` from `@mercurjs/core/workflows`.
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
## Attribute events
|
|
10
|
+
|
|
11
|
+
Each event fires once per attribute, with that attribute’s id as the payload.
|
|
12
|
+
|
|
13
|
+
| Event | Emitted when | Payload |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `product-attribute.created` | `createProductAttributesWorkflow` creates attributes | `{ id }` |
|
|
16
|
+
| `product-attribute.updated` | `updateProductAttributesWorkflow` updates attributes, including an inline rename from the batch workflow | `{ id }` |
|
|
17
|
+
| `product-attribute.deleted` | `deleteProductAttributesWorkflow` deletes attributes, including inline attributes removed from their product | `{ id }` |
|
|
18
|
+
|
|
19
|
+
## Value events
|
|
20
|
+
|
|
21
|
+
Each event fires once per value, with that value’s id as the payload.
|
|
22
|
+
|
|
23
|
+
| Event | Emitted when | Payload |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `product-attribute-value.created` | `createProductAttributeValuesWorkflow` creates values, including new values added to an inline axis | `{ id }` |
|
|
26
|
+
| `product-attribute-value.updated` | `updateProductAttributeValuesWorkflow` updates values | `{ id }` |
|
|
27
|
+
| `product-attribute-value.deleted` | `deleteProductAttributeValuesWorkflow` deletes values, including values removed from an inline axis | `{ id }` |
|
|
28
|
+
|
|
29
|
+
## What doesn’t emit
|
|
30
|
+
|
|
31
|
+
Some writes create or change records without a matching event:
|
|
32
|
+
|
|
33
|
+
- **Values created with an attribute**: `createProductAttributesWorkflow` emits only `product-attribute.created`, not a value event per value
|
|
34
|
+
- **Upserts**: `upsertProductAttributeValuesWorkflow` emits nothing
|
|
35
|
+
- **Attaching and detaching**: linking values to a product, `text` and `unit` values created on attach, and option changes on a product emit no attribute events
|
|
36
|
+
|
|
37
|
+
## Subscribe to an event
|
|
38
|
+
|
|
39
|
+
A subscriber receives one event per attribute. Resolve `query` to load the record by id.
|
|
40
|
+
|
|
41
|
+
```ts title="src/subscribers/product-attribute-created.ts"
|
|
12
42
|
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
|
|
43
|
+
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
|
13
44
|
|
|
14
|
-
export default async function
|
|
45
|
+
export default async function productAttributeCreated({
|
|
15
46
|
event,
|
|
16
47
|
container,
|
|
17
48
|
}: SubscriberArgs<{ id: string }>) {
|
|
18
|
-
const
|
|
19
|
-
|
|
49
|
+
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
|
50
|
+
const { data: [attribute] } = await query.graph({
|
|
51
|
+
entity: "product_attribute",
|
|
52
|
+
fields: ["id", "name", "type", "is_filterable", "values.name"],
|
|
53
|
+
filters: { id: event.data.id },
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
container.resolve("logger").info(`Index facet ${attribute.name}`)
|
|
20
57
|
}
|
|
21
58
|
|
|
22
59
|
export const config: SubscriberConfig = {
|
|
@@ -24,18 +61,4 @@ export const config: SubscriberConfig = {
|
|
|
24
61
|
}
|
|
25
62
|
```
|
|
26
63
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
| Event | Emitted when | Payload |
|
|
30
|
-
| --- | --- | --- |
|
|
31
|
-
| `product-attribute.created` | An attribute is created | `{ id }` |
|
|
32
|
-
| `product-attribute.updated` | An attribute changes | `{ id }` |
|
|
33
|
-
| `product-attribute.deleted` | An attribute is deleted | `{ id }` |
|
|
34
|
-
|
|
35
|
-
## Value events
|
|
36
|
-
|
|
37
|
-
| Event | Emitted when | Payload |
|
|
38
|
-
| --- | --- | --- |
|
|
39
|
-
| `product-attribute-value.created` | A value is created | `{ id }` |
|
|
40
|
-
| `product-attribute-value.updated` | A value changes | `{ id }` |
|
|
41
|
-
| `product-attribute-value.deleted` | A value is deleted | `{ id }` |
|
|
64
|
+
The subscriber loads the new attribute with its values. Swap the log line for a call to your search index.
|
|
@@ -1,45 +1,116 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Attribute links"
|
|
3
3
|
sidebarTitle: "Links"
|
|
4
|
-
description: "
|
|
4
|
+
description: "The five module links between the Product Attribute module and Medusa's Product module."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
**module links**. The Product Attribute module links into Medusa's product module
|
|
9
|
-
in several ways: to products, to categories, and, for variant axes, to product
|
|
10
|
-
options through mirror links. Once a link is defined, you retrieve related records
|
|
11
|
-
with `query.graph` using the link alias.
|
|
7
|
+
The Product Attribute module never references Medusa’s Product module directly. Five module links in `packages/core/src/links` connect them: two that store data in pivot tables and three read-only links resolved from a foreign key on one side. Resolve `query` from the container to read across them.
|
|
12
8
|
|
|
13
9
|
```ts
|
|
14
|
-
|
|
10
|
+
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
|
11
|
+
|
|
12
|
+
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Product to attribute value
|
|
16
|
+
|
|
17
|
+
The values a product holds, for every attribute type. This is the link the attachment workflows write.
|
|
18
|
+
|
|
19
|
+
| Property | Value |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Sides | `Product` ↔ `ProductAttributeValue` |
|
|
22
|
+
| Cardinality | Many-to-many |
|
|
23
|
+
| Storage | Pivot table `product_attribute_value_link` |
|
|
24
|
+
| Aliases | `product_attribute_values` on product, `products` on value |
|
|
25
|
+
| Defined in | `product-attribute-value-pivot-link.ts` |
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const { data: [product] } = await query.graph({
|
|
15
29
|
entity: "product",
|
|
16
|
-
fields: [
|
|
30
|
+
fields: [
|
|
31
|
+
"product_attribute_values.name",
|
|
32
|
+
"product_attribute_values.attribute.name",
|
|
33
|
+
],
|
|
34
|
+
filters: { id: "prod_01JD8Q1234567" },
|
|
17
35
|
})
|
|
18
36
|
```
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
The query returns each linked value with its parent attribute, which is how you read a product’s full attribute selection.
|
|
21
39
|
|
|
22
|
-
|
|
23
|
-
| --- | --- | --- |
|
|
24
|
-
| **Product → attribute** (`scoped_attributes`) | None | Read-only; a product's inline (product-scoped) attributes, resolved from `ProductAttribute.product_id` |
|
|
25
|
-
| **Product ↔ attribute value** | `product_attribute_value_link` | Many-to-many pivot; the values selected on a product |
|
|
40
|
+
## Product to scoped attributes
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
The inline attributes created on a product. The link is read-only and resolves `ProductAttribute.product_id` against `product.id`.
|
|
28
43
|
|
|
29
|
-
|
|
|
30
|
-
| --- | --- |
|
|
31
|
-
|
|
|
44
|
+
| Property | Value |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| Sides | `Product` → `ProductAttribute` |
|
|
47
|
+
| Cardinality | One-to-many |
|
|
48
|
+
| Storage | None, read-only |
|
|
49
|
+
| Alias | `scoped_attributes` on product |
|
|
50
|
+
| Defined in | `product-attribute-product-link.ts` |
|
|
32
51
|
|
|
33
|
-
|
|
52
|
+
```ts
|
|
53
|
+
const { data: [product] } = await query.graph({
|
|
54
|
+
entity: "product",
|
|
55
|
+
fields: ["scoped_attributes.name", "scoped_attributes.values.name"],
|
|
56
|
+
filters: { id: "prod_01JD8Q1234567" },
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Attribute to category
|
|
34
61
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
|
|
|
62
|
+
The product categories a global attribute is offered for. An attribute with no category links is offered for every product.
|
|
63
|
+
|
|
64
|
+
| Property | Value |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| Sides | `ProductAttribute` ↔ `ProductCategory` |
|
|
67
|
+
| Cardinality | Many-to-many |
|
|
68
|
+
| Storage | Pivot table `product_category_attribute` |
|
|
69
|
+
| Aliases | `categories` on attribute, `product_attributes` on category |
|
|
70
|
+
| Defined in | `product-attribute-category-link.ts` |
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const { data: attributes } = await query.graph({
|
|
74
|
+
entity: "product_attribute",
|
|
75
|
+
fields: ["id", "name", "categories.id", "categories.name"],
|
|
76
|
+
filters: { product_id: null },
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`createProductAttributesWorkflow` writes these links from `category_ids`. Write more yourself with the link service, using the keys `product_attribute_id` and `product_category_id`.
|
|
81
|
+
|
|
82
|
+
## Attribute to product option
|
|
83
|
+
|
|
84
|
+
The Medusa `ProductOption` that backs a variant axis. The link is read-only and keyed on `ProductAttribute.product_option_id`.
|
|
85
|
+
|
|
86
|
+
| Property | Value |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| Sides | `ProductAttribute` → `ProductOption` |
|
|
89
|
+
| Cardinality | One-to-one |
|
|
90
|
+
| Storage | None, read-only |
|
|
91
|
+
| Defined in | `product-attribute-option-mirror-link.ts` |
|
|
92
|
+
|
|
93
|
+
## Attribute value to option value
|
|
94
|
+
|
|
95
|
+
The Medusa `ProductOptionValue` that backs one value of a variant axis. The link is read-only and keyed on `ProductAttributeValue.product_option_value_id`.
|
|
96
|
+
|
|
97
|
+
| Property | Value |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| Sides | `ProductAttributeValue` → `ProductOptionValue` |
|
|
100
|
+
| Cardinality | One-to-one |
|
|
101
|
+
| Storage | None, read-only |
|
|
102
|
+
| Defined in | `product-attribute-value-option-value-mirror-link.ts` |
|
|
103
|
+
|
|
104
|
+
Core code reads the mirror through the foreign keys, not through these links. Do the same: read the ids from the attribute, then query the option.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const { data: [option] } = await query.graph({
|
|
108
|
+
entity: "product_option",
|
|
109
|
+
fields: ["id", "title", "is_exclusive", "values.id", "values.value"],
|
|
110
|
+
filters: { id: attribute.product_option_id },
|
|
111
|
+
})
|
|
112
|
+
```
|
|
39
113
|
|
|
40
114
|
<Note>
|
|
41
|
-
The mirror
|
|
42
|
-
scope link (`scoped_attributes`) are **read-only**. They're resolved from the
|
|
43
|
-
FK on the owning record and can't be written through the link itself. The
|
|
44
|
-
workflows keep the mirror in sync when axis attributes and values change.
|
|
115
|
+
The workflows keep the mirror in step. Never write `product_option_id` or `product_option_value_id` yourself, or the attribute and its option will drift apart.
|
|
45
116
|
</Note>
|
|
@@ -1,49 +1,88 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Product Attribute module service"
|
|
3
3
|
sidebarTitle: "Service"
|
|
4
|
-
description: "
|
|
4
|
+
description: "Resolve the Product Attribute module service, and the generated and overridden methods it exposes."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
The Product Attribute module
|
|
8
|
-
container to read and write records directly, without going through a workflow.
|
|
9
|
-
Use it inside custom services, subscribers, or scheduled jobs.
|
|
7
|
+
The Product Attribute module service reads and writes `ProductAttribute` and `ProductAttributeValue` records directly. Use it for reads in routes, subscribers, and scheduled jobs. For writes, prefer the [workflows](/platform/attribute/reference/workflows), because the service doesn’t emit events, update the mirror `ProductOption`, or link values to products.
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
## Resolve the service
|
|
10
|
+
|
|
11
|
+
Resolve the service from the container with `MercurModules.PRODUCT_ATTRIBUTE`, whose value is `"product_attribute"`.
|
|
12
|
+
|
|
13
|
+
```ts title="src/api/admin/catalog-import/facets/route.ts"
|
|
14
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
15
|
+
import { AttributeType, MercurModules } from "@mercurjs/types"
|
|
16
|
+
|
|
17
|
+
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
18
|
+
const attributeService = req.scope.resolve(MercurModules.PRODUCT_ATTRIBUTE)
|
|
13
19
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
const [attributes, count] =
|
|
21
|
+
await attributeService.listAndCountProductAttributes(
|
|
22
|
+
{ is_filterable: true, product_id: null },
|
|
23
|
+
{ relations: ["values"], order: { rank: "ASC" } }
|
|
24
|
+
)
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type: "single_select",
|
|
21
|
-
})
|
|
26
|
+
res.json({ attributes, count })
|
|
27
|
+
}
|
|
22
28
|
```
|
|
23
29
|
|
|
30
|
+
The route lists filterable global attributes in rank order, each with its values.
|
|
31
|
+
|
|
24
32
|
## Generated methods
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
`ProductAttribute`:
|
|
34
|
+
The service extends `MedusaService`, so each data model gets the standard methods. For `ProductAttribute`:
|
|
28
35
|
|
|
29
36
|
| Method | Description |
|
|
30
37
|
| --- | --- |
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `updateProductAttributes(data)` | Update one or more attributes |
|
|
36
|
-
| `deleteProductAttributes(ids)` |
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`listAndCountProductAttributeValues`, `updateProductAttributeValues`, and
|
|
41
|
-
|
|
38
|
+
| `retrieveProductAttribute(id, config?)` | Retrieve one attribute by id |
|
|
39
|
+
| `listProductAttributes(filters?, config?)` | List attributes. Overridden, see below |
|
|
40
|
+
| `listAndCountProductAttributes(filters?, config?)` | List attributes with a total count. Overridden, see below |
|
|
41
|
+
| `createProductAttributes(data)` | Create one or more attributes. Overridden, see below |
|
|
42
|
+
| `updateProductAttributes(data)` | Update one or more attributes by id. Overridden, see below |
|
|
43
|
+
| `deleteProductAttributes(ids)` | Hard-delete attributes and their values |
|
|
44
|
+
| `softDeleteProductAttributes(ids)` | Soft-delete attributes and their values |
|
|
45
|
+
| `restoreProductAttributes(ids)` | Restore soft-deleted attributes |
|
|
46
|
+
|
|
47
|
+
`ProductAttributeValue` gets the same set: `retrieveProductAttributeValue`, `listProductAttributeValues`, `listAndCountProductAttributeValues`, `createProductAttributeValues`, `updateProductAttributeValues`, `deleteProductAttributeValues`, `softDeleteProductAttributeValues`, and `restoreProductAttributeValues`.
|
|
48
|
+
|
|
49
|
+
## Overridden methods
|
|
50
|
+
|
|
51
|
+
Five methods add behavior on top of the generated ones.
|
|
52
|
+
|
|
53
|
+
### createProductAttributes
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
createProductAttributes(data: object | object[], sharedContext?: Context)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Derives a `handle` from `name` for any global attribute created without one. After creating, it seeds a `"true"` value (rank 0) and a `"false"` value (rank 1) for every `toggle` attribute. It doesn’t create any other values, so add them with `createProductAttributeValues`.
|
|
60
|
+
|
|
61
|
+
### updateProductAttributes
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
updateProductAttributes(data: object | object[], sharedContext?: Context)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Throws `INVALID_DATA` when an update sets a `type` that differs from the attribute’s current type. Every other field passes through.
|
|
68
|
+
|
|
69
|
+
### listProductAttributes and listAndCountProductAttributes
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
listProductAttributes(filters?, config?: FindConfig, sharedContext?: Context)
|
|
73
|
+
listAndCountProductAttributes(filters?, config?, sharedContext?)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
When `config.relations` or `config.select` asks for `values`, the service loads them in a separate query. Only `single_select`, `multi_select`, and `toggle` attributes get their values. `text` and `unit` attributes come back with `values: []`, because their values belong to individual products.
|
|
77
|
+
|
|
78
|
+
### createProductAttributeValues
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
createProductAttributeValues(data: object | object[], sharedContext?: Context)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Derives a `handle` from `name` for values of global `single_select` and `multi_select` attributes. Values of inline attributes and of `text`, `unit`, and `toggle` attributes keep a `null` handle.
|
|
42
85
|
|
|
43
86
|
<Warning>
|
|
44
|
-
|
|
45
|
-
side effects, such as creating an axis attribute, attaching attributes to a
|
|
46
|
-
product, or keeping the mirror `ProductOption` in sync. The service writes records
|
|
47
|
-
directly and does **not** emit events, maintain the option mirror, or run
|
|
48
|
-
compensation.
|
|
87
|
+
Writing `product_option_id` or `product_option_value_id` through the service doesn’t create or change any Medusa option. Use `createProductAttributesWorkflow` and the value workflows so the mirror stays correct.
|
|
49
88
|
</Warning>
|
|
@@ -1,45 +1,175 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Attribute workflows"
|
|
3
3
|
sidebarTitle: "Workflows"
|
|
4
|
-
description: "
|
|
4
|
+
description: "Every catalog and product attachment workflow in the Product Attribute domain, with inputs, hooks, and events."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
`@mercurjs/core/workflows` and run them against the Medusa container.
|
|
7
|
+
The Product Attribute domain exports 11 workflows from `@mercurjs/core/workflows`: 7 that manage the catalog and 4 that attach attributes to products. Run each one with `workflow(container).run({ input })`. When a step fails, the steps that already ran undo their work through their compensation functions.
|
|
9
8
|
|
|
10
9
|
## Catalog workflows
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
These workflows create, change, and delete attributes and values. They keep the mirror `ProductOption` of a variant axis in step.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
### createProductAttributesWorkflow
|
|
14
|
+
|
|
15
|
+
Creates attributes and their values. For each `multi_select` attribute with `is_variant_axis`, it first creates a global `ProductOption` (`is_exclusive: false`) titled with the attribute name, then links the values to its option values. Id `create-product-attributes`.
|
|
16
|
+
|
|
17
|
+
| Input | Type | Description |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `attributes` | `(CreateProductAttributeDTO & { category_ids?: string[] })[]` | Attributes to create, each with optional `values` |
|
|
20
|
+
| `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesCreated` hook |
|
|
21
|
+
|
|
22
|
+
- **Returns**: `ProductAttributeDTO[]`
|
|
23
|
+
- **Hooks**: `validate` with `{ input }`, `productAttributesCreated` with `{ attributes, additional_data }`
|
|
24
|
+
- **Events**: `product-attribute.created` per attribute
|
|
25
|
+
|
|
26
|
+
### updateProductAttributesWorkflow
|
|
27
|
+
|
|
28
|
+
Updates every attribute matching `selector`. When `update.name` is set, it also renames the mirror option of each axis attribute. Id `update-product-attributes`.
|
|
29
|
+
|
|
30
|
+
| Input | Type | Description |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `selector` | `Record<string, unknown>` | Filters for the attributes to update, such as `{ id }` |
|
|
33
|
+
| `update` | `UpdateProductAttributeDTO` | Fields to set. Changing `type` throws |
|
|
34
|
+
| `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesUpdated` hook |
|
|
35
|
+
|
|
36
|
+
- **Returns**: `ProductAttributeDTO[]`
|
|
37
|
+
- **Hooks**: `validate` with `{ input }`, `productAttributesUpdated` with `{ attributes, additional_data }`
|
|
38
|
+
- **Events**: `product-attribute.updated` per attribute
|
|
39
|
+
|
|
40
|
+
### deleteProductAttributesWorkflow
|
|
41
|
+
|
|
42
|
+
Soft-deletes attributes and their values, then deletes the mirror options of axis attributes. It throws `NOT_ALLOWED` if any value of an attribute is linked to a product. Id `delete-product-attributes`.
|
|
43
|
+
|
|
44
|
+
| Input | Type | Description |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `ids` | `string[]` | Attribute ids |
|
|
47
|
+
| `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesDeleted` hook |
|
|
48
|
+
|
|
49
|
+
- **Returns**: nothing
|
|
50
|
+
- **Hooks**: `validate` with `{ input }`, `productAttributesDeleted` with `{ ids, additional_data }`
|
|
51
|
+
- **Events**: `product-attribute.deleted` per id
|
|
52
|
+
|
|
53
|
+
### createProductAttributeValuesWorkflow
|
|
54
|
+
|
|
55
|
+
Adds values to one attribute. For an axis, it first adds the names to the mirror option and stores each new option value’s id. Id `create-product-attribute-values`.
|
|
56
|
+
|
|
57
|
+
| Input | Type | Description |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `attribute_id` | `string` | The parent attribute |
|
|
60
|
+
| `values` | `CreateProductAttributeValueDTO[]` | Values to create, each with at least a `name` |
|
|
61
|
+
| `additional_data` | `Record<string, unknown>` | Passed to the `productAttributeValuesCreated` hook |
|
|
62
|
+
|
|
63
|
+
- **Returns**: `ProductAttributeValueDTO[]`
|
|
64
|
+
- **Hooks**: `validate` with `{ input }`, `productAttributeValuesCreated` with `{ values, additional_data }`
|
|
65
|
+
- **Events**: `product-attribute-value.created` per value
|
|
66
|
+
|
|
67
|
+
### updateProductAttributeValuesWorkflow
|
|
68
|
+
|
|
69
|
+
Updates every value matching `selector`. When the values belong to exactly one axis attribute, it rewrites that option’s values from the attribute’s value names. Id `update-product-attribute-values`.
|
|
70
|
+
|
|
71
|
+
| Input | Type | Description |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `selector` | `Record<string, unknown>` | Filters for the values to update |
|
|
74
|
+
| `update` | `UpdateProductAttributeValueDTO` | Fields to set |
|
|
75
|
+
| `additional_data` | `Record<string, unknown>` | Passed to the `productAttributeValuesUpdated` hook |
|
|
76
|
+
|
|
77
|
+
- **Returns**: `ProductAttributeValueDTO[]`
|
|
78
|
+
- **Hooks**: `validate` with `{ input }`, `productAttributeValuesUpdated` with `{ values, additional_data }`
|
|
79
|
+
- **Events**: `product-attribute-value.updated` per value
|
|
80
|
+
|
|
81
|
+
### deleteProductAttributeValuesWorkflow
|
|
82
|
+
|
|
83
|
+
Soft-deletes values. When they belong to exactly one axis attribute, it rewrites that option’s values from the remaining value names. Id `delete-product-attribute-values`.
|
|
84
|
+
|
|
85
|
+
| Input | Type | Description |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `ids` | `string[]` | Value ids |
|
|
88
|
+
| `additional_data` | `Record<string, unknown>` | Passed to the `productAttributeValuesDeleted` hook |
|
|
89
|
+
|
|
90
|
+
- **Returns**: nothing
|
|
91
|
+
- **Hooks**: `validate` with `{ input }`, `productAttributeValuesDeleted` with `{ ids, additional_data }`
|
|
92
|
+
- **Events**: `product-attribute-value.deleted` per id
|
|
93
|
+
|
|
94
|
+
### upsertProductAttributeValuesWorkflow
|
|
95
|
+
|
|
96
|
+
Creates values without an `id` and updates values with one, for a single attribute. For an axis, it adds any new names to the mirror option first. Id `upsert-product-attribute-values`.
|
|
97
|
+
|
|
98
|
+
| Input | Type | Description |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `attribute_id` | `string` | The parent attribute |
|
|
101
|
+
| `values` | `UpsertProductAttributeValueDTO[]` | Values to create or update, matched by `id` |
|
|
102
|
+
|
|
103
|
+
- **Returns**: `ProductAttributeValueDTO[]`, created values first
|
|
104
|
+
- **Hooks**: none
|
|
105
|
+
- **Events**: none
|
|
106
|
+
|
|
107
|
+
## Product attachment workflows
|
|
108
|
+
|
|
109
|
+
These workflows attach attributes to products, write the `product_attribute_value_link` pivot, and attach or detach axis options on the product. Their input types are `ProductAttributeBatchAdd` and `ProductAttributeBatchUpdate` from `@mercurjs/types`.
|
|
110
|
+
|
|
111
|
+
### createAndLinkProductAttributesToProductWorkflow
|
|
112
|
+
|
|
113
|
+
The batch engine. It runs the remove, add, and update workflows below in that order, skipping any list that’s empty. Id `create-and-link-product-attributes-to-product`.
|
|
114
|
+
|
|
115
|
+
| Input | Type | Description |
|
|
15
116
|
| --- | --- | --- |
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `deleteProductAttributeValuesWorkflow` | `{ ids[] }` | Delete values |
|
|
22
|
-
| `upsertProductAttributeValuesWorkflow` | `{ attribute_id, values[] }` | Create or update an attribute's values in one call |
|
|
117
|
+
| `product_id` | `string` | The product to change |
|
|
118
|
+
| `add` | `ProductAttributeBatchAdd[]` | Attributes to attach, existing or inline |
|
|
119
|
+
| `remove` | `string[]` | Attribute ids to detach |
|
|
120
|
+
| `update` | `ProductAttributeBatchUpdate[]` | Selections to change |
|
|
121
|
+
| `additional_data` | `Record<string, unknown>` | Passed to the `productAttributesLinked` hook |
|
|
23
122
|
|
|
24
|
-
|
|
123
|
+
- **Returns**: nothing
|
|
124
|
+
- **Hooks**: `validate` with `{ input }`, `productAttributesLinked` with `{ product_id, additional_data }`
|
|
125
|
+
- **Events**: none of its own. Nested catalog workflows emit theirs
|
|
25
126
|
|
|
26
|
-
|
|
127
|
+
### addProductAttributesToProductWorkflow
|
|
27
128
|
|
|
28
|
-
|
|
129
|
+
Attaches attributes. It creates inline attributes and their exclusive options, creates `text` and `unit` values, attaches axis options with the selected subset, and links every value to the product. Id `add-product-attributes-to-product`.
|
|
130
|
+
|
|
131
|
+
| Input | Type | Description |
|
|
29
132
|
| --- | --- | --- |
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
|
|
33
|
-
|
|
133
|
+
| `product_id` | `string` | The product to change |
|
|
134
|
+
| `add` | `ProductAttributeBatchAdd[]` | Attributes to attach |
|
|
135
|
+
|
|
136
|
+
The input can also be an array of `{ product_id, add }` objects to attach to several products in one run. It has no hooks and emits no events.
|
|
137
|
+
|
|
138
|
+
### removeProductAttributesFromProductWorkflow
|
|
139
|
+
|
|
140
|
+
Detaches attributes. It throws `NOT_ALLOWED` for a required attribute unless the id is also in `readd`. It then detaches global axis options, drops the value links, and deletes inline attributes through `deleteProductAttributesWorkflow`. Id `remove-product-attributes-from-product`.
|
|
141
|
+
|
|
142
|
+
| Input | Type | Description |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| `product_id` | `string` | The product to change |
|
|
145
|
+
| `remove` | `string[]` | Attribute ids to detach |
|
|
146
|
+
| `readd` | `string[]` | Ids removed and re-added in the same batch, skipped by the required check |
|
|
147
|
+
|
|
148
|
+
### updateProductAttributesOnProductWorkflow
|
|
149
|
+
|
|
150
|
+
Changes selections. It resolves `value_ids` into an add and remove delta, moves global axis option values, swaps `text`, `unit`, and `toggle` values, adds or deletes values of an inline axis, and renames inline attributes from `title`. Id `update-product-attributes-on-product`.
|
|
151
|
+
|
|
152
|
+
| Input | Type | Description |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| `product_id` | `string` | The product to change |
|
|
155
|
+
| `update` | `ProductAttributeBatchUpdate[]` | Selections to change |
|
|
34
156
|
|
|
35
157
|
<Note>
|
|
36
|
-
`
|
|
37
|
-
product attribute batch endpoint; it composes the three single-purpose
|
|
38
|
-
attachment workflows. See
|
|
39
|
-
[Attach attributes to a product](/platform/attribute/guides/attach-attributes-to-a-product).
|
|
158
|
+
Changes made by these workflows land on the product right away. To route a store’s attribute edits through review, run `productEditUpdateAttributesWorkflow` from the [Product edits](/platform/product-edit/overview) domain, which applies the batch engine once the change is confirmed.
|
|
40
159
|
</Note>
|
|
41
160
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
161
|
+
## Steps
|
|
162
|
+
|
|
163
|
+
The domain also exports its steps for use in your own workflows:
|
|
164
|
+
|
|
165
|
+
| Step | Id | Compensation |
|
|
166
|
+
| --- | --- | --- |
|
|
167
|
+
| `createProductAttributesStep` | `pa-create-product-attributes` | Deletes the created attributes |
|
|
168
|
+
| `updateProductAttributesStep` | `pa-update-product-attributes` | Restores previous field values |
|
|
169
|
+
| `deleteProductAttributesStep` | `pa-delete-product-attributes` | Restores the soft-deleted attributes |
|
|
170
|
+
| `createProductAttributeValuesStep` | `pa-create-product-attribute-values` | Deletes the created values |
|
|
171
|
+
| `updateProductAttributeValuesStep` | `pa-update-product-attribute-values` | Restores previous field values |
|
|
172
|
+
| `deleteProductAttributeValuesStep` | `pa-delete-product-attribute-values` | Restores the soft-deleted values |
|
|
173
|
+
| `detachProductOptionValuesFromProductStep` | `pa-detach-product-option-values-from-product` | Re-attaches the option values |
|
|
174
|
+
| `validateProductAttributesNotLinkedStep` | `pa-validate-attributes-not-linked` | None |
|
|
175
|
+
| `validateProductAttributesNotRequiredStep` | `pa-validate-attributes-not-required` | None |
|