@doswiftly/storefront-operations 20.2.0 → 21.0.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/AGENTS.md CHANGED
@@ -27,10 +27,10 @@ consumer's `codegen.ts` references this package's `.graphql` files as
27
27
  live in the consumer's repo.
28
28
 
29
29
  <!-- AUTOGEN:STATS:BEGIN — auto-regenerated, do not edit by hand -->
30
- - **Schema version**: 20.2.0
30
+ - **Schema version**: 21.0.1
31
31
  - **Queries**: 52
32
32
  - **Mutations**: 44
33
- - **Fragments**: 104
33
+ - **Fragments**: 105
34
34
  <!-- AUTOGEN:STATS:END -->
35
35
 
36
36
  ## Loading order
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 21.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 65c7f72: Fixed: cart, order, and customer address reads now include `buildingNumber` and `flatNumber`. The previous release added both fields to the schema and inputs (you could send them), but the SDK's `MailingAddress` selection did not request them back — they always came back missing on `cart.shippingAddress` / `cart.billingAddress`, order addresses, the saved address book (`getAddresses()`), and `customer.defaultAddress`. The generated types now carry both fields as well.
8
+
9
+ Documentation: rewrote the SDK README to match the current API.
10
+
11
+ The README (shown on npm and GitHub) previously described setup patterns from older releases. It now documents the current surface:
12
+ - **Quick start** based on `createStorefrontAuthRoute` (one route file mounting `login` / `refresh` / `logout` / `whoami`), `getInitialAuth()` server seeding, and `<StorefrontProvider>` with automatic session refresh.
13
+ - **Cart** chapter covering the capability model (composite `cart-id` cookie carrying the cart access secret, `cartSecretMiddleware`), the full `useCartManager` checkout lifecycle (per-operation recovery table, tagged-union `status`, lifecycle callbacks, `initialCartId`), `<CartManagerProvider>` / `useCartManagerContext()`, completion + payment session creation, discovery queries, `merge` / `downgradeOnLogout` / `recoveryRedeem`, and the framework-agnostic recovery runner.
14
+ - **Authentication** chapter documenting the session cookie set, proactive + reactive refresh, the BFF sign-in flow, `AuthClient` methods (including `refreshSession()` and `getAddresses()`), and reverse-proxy origin validators.
15
+ - Reference sections for the middleware pipeline order, debug logging (including the remote debug transport), runtime schema enums, cookie contract constants, format hooks, pre-built components (including payment instrument tiles), and server-side helpers (`readCartCredentials`, `serverCartSecretMiddleware`).
16
+ - A **Deprecated** section listing the legacy cart store (`createCartStore` / `CartProvider` / `useCartStore`), `AuthClient.refreshToken()` / `useRefreshToken`, and `ShopCurrencyData` with their replacements.
17
+
18
+ Also cleaned up developer-facing JSDoc across the package (clients, middleware, stores, hooks, pre-built components): comments and IDE hover text are now plain technical English, and the `getBrowserDataForPayment` example no longer suggests passing browser data to `createPayment` (its input is `{ orderId, returnUrl?, cancelUrl? }`). No runtime changes.
19
+
20
+ `@doswiftly/storefront-operations` is version-synced; no code changes.
21
+
22
+ ## 21.0.0
23
+
24
+ ### Major Changes
25
+
26
+ - a539021: Product attribute fields renamed and split by purpose (breaking)
27
+
28
+ `Product.attributes` now returns the product's **stored custom-field values** — merchant-managed metadata such as manufacturer, licence or material — as `[EntityAttributeField!]!`. Only values marked visible are returned; pass the optional `namespace` argument to fetch a single group.
29
+
30
+ The configurator field **definitions** that previously lived under `Product.attributes` have moved to **`Product.configuratorFields(filter: { filledBy: ... })`**, returning `[ConfiguratorField!]!`.
31
+
32
+ Renames:
33
+ - Type `ProductAttributeDefinition` → `ConfiguratorField`, with field renames `fillingMode` → `filledBy`, `billingMode` → `pricingMode`, `isRequired` → `required`, `displayOrder` → `sortOrder`.
34
+ - Type `ProductAttributeOption` → `ConfiguratorOption`.
35
+ - Input `ProductAttributeFilterInput` → `ConfiguratorFieldFilterInput` (its `fillingMode` argument → `filledBy`).
36
+
37
+ Removed from the public contract: `Product.attributeSetId`; the internal `scopeProductId` and `taxClassId` on configurator fields; and the raw `linkedVariantId` on a configurator option (read `linkedVariant.id` instead).
38
+
39
+ Migration: rename `attributes(filter: { fillingMode })` selections to `configuratorFields(filter: { filledBy })` and spread `...ConfiguratorField`; use the new `attributes` field (optional `namespace`) to read product metadata values.
40
+
3
41
  ## 20.2.0
4
42
 
5
43
  ### Minor Changes
package/README.md CHANGED
@@ -170,7 +170,7 @@ full executable body of each operation.
170
170
  | Operation | Description |
171
171
  | --- | --- |
172
172
  | `Product` | Fetches a single product by `id` or `handle` (URL-friendly identifier). Pass either — whichever is provided wins; if both are missing, returns null. Returns null if the product is not storefront-accessible (must be `ACTIVE` status with `PUBLIC` or `BUNDLE_ONLY` visibility). |
173
- | `ProductConfigurator` | Fetches a product together with its filtered attribute definitions, optimized for the configurator UI (e.g. customer-facing text fields, finishing options, scoped variants). `fillingMode: "CUSTOMER"` returns only customer-facing attributes; pass `"BOTH"` to also include attributes shared with the merchant admin. Single round-trip — saves a separate `attributes` query. |
173
+ | `ProductConfigurator` | Fetches a product together with its configurator fields, optimized for the product-page configurator UI. filledBy CUSTOMER returns only the fields a shopper edits; pass BOTH to also include seller-prefilled fields. Single round-trip. |
174
174
  | `Products` | Paginated product list (Relay Connection, default page size 20, max 100). The `query` argument supports a structured search syntax — `tag:summer`, `vendor:foo`, `product_type:shirts`, `variants.price:>10`, plus `AND`/`OR`/`NOT` — falling back to free-text title/content search. The `filters[]` array uses multi-filter logic: same field name appears multiple times → OR; different fields → AND. Sort: `RELEVANCE`, `TITLE`, `PRICE`, `NEWEST`, `OLDEST`, `BEST_SELLING`. The response includes a `filters` block for faceted navigation (counts per filterable attribute value). |
175
175
  | `ProductSearch` | Full-text product search — `$query` is required. Functionally equivalent to `Products` with `$query` set, minus the `sortKey` argument (search defaults to relevance ranking). Use for the search results page; combine with `filters[]` for guided refinement. |
176
176
  | `SearchSuggestions` | Type-ahead suggestions for the storefront search input. Returns up to `$limit` matching products (hard cap 50) plus up to 5 styled query suggestions with `<mark>` tags around matched spans. Polish-language aware (handles morphology in suggestions). Run on each keystroke (debounce 200-300ms). The `$query` is capped at 100 characters server-side. |
@@ -638,13 +638,19 @@ full executable body of each operation.
638
638
  | --- | --- | --- |
639
639
  | `UrlRedirect` | `UrlRedirect` | Single legacy `path` → new `target` redirect entry. Use on the server / edge to issue 301 redirects for migrated routes. |
640
640
 
641
- #### Product Configurator (per-product attributes)
641
+ #### Product Attributes (stored metadata values)
642
642
 
643
643
  | Fragment | On Type | Description |
644
644
  | --- | --- | --- |
645
- | `LinkedVariantSummary` | `LinkedVariantSummary` | Slim variant snapshot linked from a configurator option when an attribute option (e.g. "256 GB" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ProductAttributeOption.linkedVariant`. |
646
- | `ProductAttributeOption` | `ProductAttributeOption` | One option (choice) within a configurator attribute — value, label, sort order, optional color hex, surcharge (amount + type), default flag, optional linked variant for variant-mapped options. |
647
- | `ProductAttributeDefinition` | `ProductAttributeDefinition` | Configurator attribute definition — name, type, fillingMode (CUSTOMER / MERCHANT / BOTH), billingMode, tax class, validation bounds, options (for choice-type attributes), required + visibility flags. Returned by `product.attributes(filter)` for the configurator UI. |
645
+ | `AttributeValue` | `EntityAttributeField` | A stored custom-field value on a product (or variant / customer / order) - merchant-managed metadata exposed via the `attributes` field. Parse `value` (a JSON string) according to `type`. |
646
+
647
+ #### Product Configurator (per-product fields)
648
+
649
+ | Fragment | On Type | Description |
650
+ | --- | --- | --- |
651
+ | `LinkedVariantSummary` | `LinkedVariantSummary` | Slim variant snapshot linked from a configurator choice — when an option (e.g. "256 GB" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ConfiguratorOption.linkedVariant`. |
652
+ | `ConfiguratorOption` | `ConfiguratorOption` | One selectable choice within a configurator field - value, label, sort order, optional colour, surcharge (amount + type), default flag, and the linked stocked variant for component-style choices. |
653
+ | `ConfiguratorField` | `ConfiguratorField` | A configurator field on the product page - label, input type, who fills it (filledBy), pricing behaviour (pricingMode), validation bounds, required + visibility flags, and selectable options for choice-type fields. |
648
654
  <!-- AUTOGEN:FRAGMENTS:END -->
649
655
 
650
656
  For full operation signatures with typed variables, GraphQL bodies and
package/fragments.graphql CHANGED
@@ -155,7 +155,9 @@ fragment ProductBase on Product {
155
155
  stockTotal
156
156
  type
157
157
  visibility
158
- attributeSetId
158
+ attributes {
159
+ ...AttributeValue
160
+ }
159
161
  createdAt
160
162
  updatedAt
161
163
  }
@@ -1632,10 +1634,26 @@ fragment UrlRedirect on UrlRedirect {
1632
1634
  }
1633
1635
 
1634
1636
  # ============================================
1635
- # Product Configurator (per-product attributes)
1637
+ # Product Attributes (stored metadata values)
1638
+ # ============================================
1639
+
1640
+ # A stored custom-field value on a product (or variant / customer / order) - merchant-managed metadata exposed via the `attributes` field. Parse `value` (a JSON string) according to `type`.
1641
+ fragment AttributeValue on EntityAttributeField {
1642
+ id
1643
+ definitionId
1644
+ handle
1645
+ name
1646
+ namespace
1647
+ type
1648
+ value
1649
+ isVisibleOverride
1650
+ }
1651
+
1652
+ # ============================================
1653
+ # Product Configurator (per-product fields)
1636
1654
  # ============================================
1637
1655
 
1638
- # Slim variant snapshot linked from a configurator option — when an attribute option (e.g. "256 GB" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ProductAttributeOption.linkedVariant`.
1656
+ # Slim variant snapshot linked from a configurator choice — when an option (e.g. "256 GB" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ConfiguratorOption.linkedVariant`.
1639
1657
  fragment LinkedVariantSummary on LinkedVariantSummary {
1640
1658
  id
1641
1659
  productId
@@ -1646,8 +1664,8 @@ fragment LinkedVariantSummary on LinkedVariantSummary {
1646
1664
  trackQuantity
1647
1665
  }
1648
1666
 
1649
- # One option (choice) within a configurator attribute value, label, sort order, optional color hex, surcharge (amount + type), default flag, optional linked variant for variant-mapped options.
1650
- fragment ProductAttributeOption on ProductAttributeOption {
1667
+ # One selectable choice within a configurator field - value, label, sort order, optional colour, surcharge (amount + type), default flag, and the linked stocked variant for component-style choices.
1668
+ fragment ConfiguratorOption on ConfiguratorOption {
1651
1669
  id
1652
1670
  value
1653
1671
  label
@@ -1656,29 +1674,26 @@ fragment ProductAttributeOption on ProductAttributeOption {
1656
1674
  surchargeAmount
1657
1675
  surchargeType
1658
1676
  isDefault
1659
- linkedVariantId
1660
1677
  linkedVariant {
1661
1678
  ...LinkedVariantSummary
1662
1679
  }
1663
1680
  }
1664
1681
 
1665
- # Configurator attribute definition name, type, fillingMode (CUSTOMER / MERCHANT / BOTH), billingMode, tax class, validation bounds, options (for choice-type attributes), required + visibility flags. Returned by `product.attributes(filter)` for the configurator UI.
1666
- fragment ProductAttributeDefinition on ProductAttributeDefinition {
1682
+ # A configurator field on the product page - label, input type, who fills it (filledBy), pricing behaviour (pricingMode), validation bounds, required + visibility flags, and selectable options for choice-type fields.
1683
+ fragment ConfiguratorField on ConfiguratorField {
1667
1684
  id
1668
1685
  name
1669
1686
  handle
1670
1687
  description
1671
1688
  type
1672
- fillingMode
1673
- billingMode
1674
- taxClassId
1675
- scopeProductId
1676
- isRequired
1689
+ filledBy
1690
+ pricingMode
1691
+ required
1677
1692
  isVisible
1678
- displayOrder
1693
+ sortOrder
1679
1694
  minValue
1680
1695
  maxValue
1681
1696
  options {
1682
- ...ProductAttributeOption
1697
+ ...ConfiguratorOption
1683
1698
  }
1684
1699
  }
package/llms-full.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # DoSwiftly Storefront Operations — Full Reference
2
2
 
3
- > Schema version: **20.2.0**
4
- > 52 queries · 44 mutations · 104 fragments
3
+ > Schema version: **21.0.1**
4
+ > 52 queries · 44 mutations · 105 fragments
5
5
 
6
6
  Auto-generated from `.graphql` source files. Do not edit by hand — this file is
7
7
  regenerated on every release to match the published schema.
@@ -81,21 +81,21 @@ query Product($id: ID, $handle: String) {
81
81
 
82
82
  **Section**: Products
83
83
 
84
- **Description**: Fetches a product together with its filtered attribute definitions, optimized for the configurator UI (e.g. customer-facing text fields, finishing options, scoped variants). `fillingMode: "CUSTOMER"` returns only customer-facing attributes; pass `"BOTH"` to also include attributes shared with the merchant admin. Single round-trip — saves a separate `attributes` query.
84
+ **Description**: Fetches a product together with its configurator fields, optimized for the product-page configurator UI. filledBy CUSTOMER returns only the fields a shopper edits; pass BOTH to also include seller-prefilled fields. Single round-trip.
85
85
 
86
86
  **Variables**:
87
87
  - `$handle`: `String!`
88
- - `$fillingMode`: `AttributeFillingMode` *(default: `CUSTOMER`)*
88
+ - `$filledBy`: `AttributeFillingMode` *(default: `CUSTOMER`)*
89
89
 
90
- **Fragments used**: `ProductAttributeDefinition`, `ProductFull`
90
+ **Fragments used**: `ConfiguratorField`, `ProductFull`
91
91
 
92
92
  **GraphQL**:
93
93
  ```graphql
94
- query ProductConfigurator($handle: String!, $fillingMode: AttributeFillingMode = CUSTOMER) {
94
+ query ProductConfigurator($handle: String!, $filledBy: AttributeFillingMode = CUSTOMER) {
95
95
  product(handle: $handle) {
96
96
  ...ProductFull
97
- attributes(filter: {fillingMode: $fillingMode}) {
98
- ...ProductAttributeDefinition
97
+ configuratorFields(filter: {filledBy: $filledBy}) {
98
+ ...ConfiguratorField
99
99
  }
100
100
  }
101
101
  }
@@ -2788,7 +2788,7 @@ fragment ProductCard on Product {
2788
2788
 
2789
2789
  **Description**: `ProductCard` plus textual content (description, descriptionHtml), stock total, type, visibility flags, and timestamps. Use when you need full product copy but not the heavy variants/images lists. Sweet spot for blog post embeds, related-product cards with description.
2790
2790
 
2791
- **Uses fragments**: `ProductCard`
2791
+ **Uses fragments**: `AttributeValue`, `ProductCard`
2792
2792
 
2793
2793
  **GraphQL**:
2794
2794
  ```graphql
@@ -2799,7 +2799,9 @@ fragment ProductBase on Product {
2799
2799
  stockTotal
2800
2800
  type
2801
2801
  visibility
2802
- attributeSetId
2802
+ attributes {
2803
+ ...AttributeValue
2804
+ }
2803
2805
  createdAt
2804
2806
  updatedAt
2805
2807
  }
@@ -5080,11 +5082,31 @@ fragment UrlRedirect on UrlRedirect {
5080
5082
  }
5081
5083
  ```
5082
5084
 
5085
+ ### Fragment: `AttributeValue` on `EntityAttributeField`
5086
+
5087
+ **Section**: Product Attributes (stored metadata values)
5088
+
5089
+ **Description**: A stored custom-field value on a product (or variant / customer / order) - merchant-managed metadata exposed via the `attributes` field. Parse `value` (a JSON string) according to `type`.
5090
+
5091
+ **GraphQL**:
5092
+ ```graphql
5093
+ fragment AttributeValue on EntityAttributeField {
5094
+ id
5095
+ definitionId
5096
+ handle
5097
+ name
5098
+ namespace
5099
+ type
5100
+ value
5101
+ isVisibleOverride
5102
+ }
5103
+ ```
5104
+
5083
5105
  ### Fragment: `LinkedVariantSummary` on `LinkedVariantSummary`
5084
5106
 
5085
- **Section**: Product Configurator (per-product attributes)
5107
+ **Section**: Product Configurator (per-product fields)
5086
5108
 
5087
- **Description**: Slim variant snapshot linked from a configurator option — when an attribute option (e.g. "256 GB" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ProductAttributeOption.linkedVariant`.
5109
+ **Description**: Slim variant snapshot linked from a configurator choice — when an option (e.g. "256 GB" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ConfiguratorOption.linkedVariant`.
5088
5110
 
5089
5111
  **GraphQL**:
5090
5112
  ```graphql
@@ -5099,17 +5121,17 @@ fragment LinkedVariantSummary on LinkedVariantSummary {
5099
5121
  }
5100
5122
  ```
5101
5123
 
5102
- ### Fragment: `ProductAttributeOption` on `ProductAttributeOption`
5124
+ ### Fragment: `ConfiguratorOption` on `ConfiguratorOption`
5103
5125
 
5104
- **Section**: Product Configurator (per-product attributes)
5126
+ **Section**: Product Configurator (per-product fields)
5105
5127
 
5106
- **Description**: One option (choice) within a configurator attribute value, label, sort order, optional color hex, surcharge (amount + type), default flag, optional linked variant for variant-mapped options.
5128
+ **Description**: One selectable choice within a configurator field - value, label, sort order, optional colour, surcharge (amount + type), default flag, and the linked stocked variant for component-style choices.
5107
5129
 
5108
5130
  **Uses fragments**: `LinkedVariantSummary`
5109
5131
 
5110
5132
  **GraphQL**:
5111
5133
  ```graphql
5112
- fragment ProductAttributeOption on ProductAttributeOption {
5134
+ fragment ConfiguratorOption on ConfiguratorOption {
5113
5135
  id
5114
5136
  value
5115
5137
  label
@@ -5118,40 +5140,37 @@ fragment ProductAttributeOption on ProductAttributeOption {
5118
5140
  surchargeAmount
5119
5141
  surchargeType
5120
5142
  isDefault
5121
- linkedVariantId
5122
5143
  linkedVariant {
5123
5144
  ...LinkedVariantSummary
5124
5145
  }
5125
5146
  }
5126
5147
  ```
5127
5148
 
5128
- ### Fragment: `ProductAttributeDefinition` on `ProductAttributeDefinition`
5149
+ ### Fragment: `ConfiguratorField` on `ConfiguratorField`
5129
5150
 
5130
- **Section**: Product Configurator (per-product attributes)
5151
+ **Section**: Product Configurator (per-product fields)
5131
5152
 
5132
- **Description**: Configurator attribute definition name, type, fillingMode (CUSTOMER / MERCHANT / BOTH), billingMode, tax class, validation bounds, options (for choice-type attributes), required + visibility flags. Returned by `product.attributes(filter)` for the configurator UI.
5153
+ **Description**: A configurator field on the product page - label, input type, who fills it (filledBy), pricing behaviour (pricingMode), validation bounds, required + visibility flags, and selectable options for choice-type fields.
5133
5154
 
5134
- **Uses fragments**: `ProductAttributeOption`
5155
+ **Uses fragments**: `ConfiguratorOption`
5135
5156
 
5136
5157
  **GraphQL**:
5137
5158
  ```graphql
5138
- fragment ProductAttributeDefinition on ProductAttributeDefinition {
5159
+ fragment ConfiguratorField on ConfiguratorField {
5139
5160
  id
5140
5161
  name
5141
5162
  handle
5142
5163
  description
5143
5164
  type
5144
- fillingMode
5145
- billingMode
5146
- taxClassId
5147
- scopeProductId
5148
- isRequired
5165
+ filledBy
5166
+ pricingMode
5167
+ required
5149
5168
  isVisible
5150
- displayOrder
5169
+ sortOrder
5151
5170
  minValue
5152
5171
  maxValue
5153
5172
  options {
5154
- ...ProductAttributeOption
5173
+ ...ConfiguratorOption
5155
5174
  }
5156
5175
  }
5157
5176
  ```
package/operations.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "20.2.0",
2
+ "schemaVersion": "21.0.1",
3
3
  "queries": [
4
4
  {
5
5
  "name": "Shop",
@@ -49,7 +49,7 @@
49
49
  "name": "ProductConfigurator",
50
50
  "kind": "query",
51
51
  "section": "Products",
52
- "description": "Fetches a product together with its filtered attribute definitions, optimized for the configurator UI (e.g. customer-facing text fields, finishing options, scoped variants). `fillingMode: \"CUSTOMER\"` returns only customer-facing attributes; pass `\"BOTH\"` to also include attributes shared with the merchant admin. Single round-trip — saves a separate `attributes` query.",
52
+ "description": "Fetches a product together with its configurator fields, optimized for the product-page configurator UI. filledBy CUSTOMER returns only the fields a shopper edits; pass BOTH to also include seller-prefilled fields. Single round-trip.",
53
53
  "variables": [
54
54
  {
55
55
  "name": "handle",
@@ -57,16 +57,16 @@
57
57
  "defaultValue": null
58
58
  },
59
59
  {
60
- "name": "fillingMode",
60
+ "name": "filledBy",
61
61
  "type": "AttributeFillingMode",
62
62
  "defaultValue": "CUSTOMER"
63
63
  }
64
64
  ],
65
65
  "fragmentRefs": [
66
- "ProductAttributeDefinition",
66
+ "ConfiguratorField",
67
67
  "ProductFull"
68
68
  ],
69
- "body": "query ProductConfigurator($handle: String!, $fillingMode: AttributeFillingMode = CUSTOMER) {\n product(handle: $handle) {\n ...ProductFull\n attributes(filter: {fillingMode: $fillingMode}) {\n ...ProductAttributeDefinition\n }\n }\n}"
69
+ "body": "query ProductConfigurator($handle: String!, $filledBy: AttributeFillingMode = CUSTOMER) {\n product(handle: $handle) {\n ...ProductFull\n configuratorFields(filter: {filledBy: $filledBy}) {\n ...ConfiguratorField\n }\n }\n}"
70
70
  },
71
71
  {
72
72
  "name": "Products",
@@ -2054,9 +2054,10 @@
2054
2054
  "description": "`ProductCard` plus textual content (description, descriptionHtml), stock total, type, visibility flags, and timestamps. Use when you need full product copy but not the heavy variants/images lists. Sweet spot for blog post embeds, related-product cards with description.",
2055
2055
  "variables": [],
2056
2056
  "fragmentRefs": [
2057
+ "AttributeValue",
2057
2058
  "ProductCard"
2058
2059
  ],
2059
- "body": "fragment ProductBase on Product {\n ...ProductCard\n description\n descriptionHtml\n stockTotal\n type\n visibility\n attributeSetId\n createdAt\n updatedAt\n}",
2060
+ "body": "fragment ProductBase on Product {\n ...ProductCard\n description\n descriptionHtml\n stockTotal\n type\n visibility\n attributes {\n ...AttributeValue\n }\n createdAt\n updatedAt\n}",
2060
2061
  "onType": "Product"
2061
2062
  },
2062
2063
  {
@@ -3109,39 +3110,49 @@
3109
3110
  "body": "fragment UrlRedirect on UrlRedirect {\n path\n target\n}",
3110
3111
  "onType": "UrlRedirect"
3111
3112
  },
3113
+ {
3114
+ "name": "AttributeValue",
3115
+ "kind": "fragment",
3116
+ "section": "Product Attributes (stored metadata values)",
3117
+ "description": "A stored custom-field value on a product (or variant / customer / order) - merchant-managed metadata exposed via the `attributes` field. Parse `value` (a JSON string) according to `type`.",
3118
+ "variables": [],
3119
+ "fragmentRefs": [],
3120
+ "body": "fragment AttributeValue on EntityAttributeField {\n id\n definitionId\n handle\n name\n namespace\n type\n value\n isVisibleOverride\n}",
3121
+ "onType": "EntityAttributeField"
3122
+ },
3112
3123
  {
3113
3124
  "name": "LinkedVariantSummary",
3114
3125
  "kind": "fragment",
3115
- "section": "Product Configurator (per-product attributes)",
3116
- "description": "Slim variant snapshot linked from a configurator option — when an attribute option (e.g. \"256 GB\" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ProductAttributeOption.linkedVariant`.",
3126
+ "section": "Product Configurator (per-product fields)",
3127
+ "description": "Slim variant snapshot linked from a configurator choice — when an option (e.g. \"256 GB\" storage) maps to a specific variant, this fragment exposes that variant's id, title, sku, stock flags. Spread inside `ConfiguratorOption.linkedVariant`.",
3117
3128
  "variables": [],
3118
3129
  "fragmentRefs": [],
3119
3130
  "body": "fragment LinkedVariantSummary on LinkedVariantSummary {\n id\n productId\n title\n sku\n availableStock\n isAvailable\n trackQuantity\n}",
3120
3131
  "onType": "LinkedVariantSummary"
3121
3132
  },
3122
3133
  {
3123
- "name": "ProductAttributeOption",
3134
+ "name": "ConfiguratorOption",
3124
3135
  "kind": "fragment",
3125
- "section": "Product Configurator (per-product attributes)",
3126
- "description": "One option (choice) within a configurator attribute value, label, sort order, optional color hex, surcharge (amount + type), default flag, optional linked variant for variant-mapped options.",
3136
+ "section": "Product Configurator (per-product fields)",
3137
+ "description": "One selectable choice within a configurator field - value, label, sort order, optional colour, surcharge (amount + type), default flag, and the linked stocked variant for component-style choices.",
3127
3138
  "variables": [],
3128
3139
  "fragmentRefs": [
3129
3140
  "LinkedVariantSummary"
3130
3141
  ],
3131
- "body": "fragment ProductAttributeOption on ProductAttributeOption {\n id\n value\n label\n sortOrder\n colorHex\n surchargeAmount\n surchargeType\n isDefault\n linkedVariantId\n linkedVariant {\n ...LinkedVariantSummary\n }\n}",
3132
- "onType": "ProductAttributeOption"
3142
+ "body": "fragment ConfiguratorOption on ConfiguratorOption {\n id\n value\n label\n sortOrder\n colorHex\n surchargeAmount\n surchargeType\n isDefault\n linkedVariant {\n ...LinkedVariantSummary\n }\n}",
3143
+ "onType": "ConfiguratorOption"
3133
3144
  },
3134
3145
  {
3135
- "name": "ProductAttributeDefinition",
3146
+ "name": "ConfiguratorField",
3136
3147
  "kind": "fragment",
3137
- "section": "Product Configurator (per-product attributes)",
3138
- "description": "Configurator attribute definition name, type, fillingMode (CUSTOMER / MERCHANT / BOTH), billingMode, tax class, validation bounds, options (for choice-type attributes), required + visibility flags. Returned by `product.attributes(filter)` for the configurator UI.",
3148
+ "section": "Product Configurator (per-product fields)",
3149
+ "description": "A configurator field on the product page - label, input type, who fills it (filledBy), pricing behaviour (pricingMode), validation bounds, required + visibility flags, and selectable options for choice-type fields.",
3139
3150
  "variables": [],
3140
3151
  "fragmentRefs": [
3141
- "ProductAttributeOption"
3152
+ "ConfiguratorOption"
3142
3153
  ],
3143
- "body": "fragment ProductAttributeDefinition on ProductAttributeDefinition {\n id\n name\n handle\n description\n type\n fillingMode\n billingMode\n taxClassId\n scopeProductId\n isRequired\n isVisible\n displayOrder\n minValue\n maxValue\n options {\n ...ProductAttributeOption\n }\n}",
3144
- "onType": "ProductAttributeDefinition"
3154
+ "body": "fragment ConfiguratorField on ConfiguratorField {\n id\n name\n handle\n description\n type\n filledBy\n pricingMode\n required\n isVisible\n sortOrder\n minValue\n maxValue\n options {\n ...ConfiguratorOption\n }\n}",
3155
+ "onType": "ConfiguratorField"
3145
3156
  }
3146
3157
  ]
3147
3158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-operations",
3
- "version": "20.2.0",
3
+ "version": "21.0.1",
4
4
  "description": "GraphQL operations for DoSwiftly Storefront - SSOT from backend",
5
5
  "homepage": "https://doswiftly.pl",
6
6
  "publishConfig": {
package/queries.graphql CHANGED
@@ -32,12 +32,12 @@ query Product($id: ID, $handle: String) {
32
32
  }
33
33
  }
34
34
 
35
- # Fetches a product together with its filtered attribute definitions, optimized for the configurator UI (e.g. customer-facing text fields, finishing options, scoped variants). `fillingMode: "CUSTOMER"` returns only customer-facing attributes; pass `"BOTH"` to also include attributes shared with the merchant admin. Single round-trip — saves a separate `attributes` query.
36
- query ProductConfigurator($handle: String!, $fillingMode: AttributeFillingMode = CUSTOMER) {
35
+ # Fetches a product together with its configurator fields, optimized for the product-page configurator UI. filledBy CUSTOMER returns only the fields a shopper edits; pass BOTH to also include seller-prefilled fields. Single round-trip.
36
+ query ProductConfigurator($handle: String!, $filledBy: AttributeFillingMode = CUSTOMER) {
37
37
  product(handle: $handle) {
38
38
  ...ProductFull
39
- attributes(filter: { fillingMode: $fillingMode }) {
40
- ...ProductAttributeDefinition
39
+ configuratorFields(filter: { filledBy: $filledBy }) {
40
+ ...ConfiguratorField
41
41
  }
42
42
  }
43
43
  }
package/schema.graphql CHANGED
@@ -44,7 +44,7 @@ type Attribute {
44
44
  }
45
45
 
46
46
  """
47
- How attribute surcharge affects pricing: BUNDLED (in unitPrice) or SEPARATE_LINE (own OrderItem)
47
+ How an option surcharge is billed: BUNDLED (added into the product price — a single line) or SEPARATE_LINE (shown as its own order line, which may carry its own tax rate).
48
48
  """
49
49
  enum AttributeBillingMode {
50
50
  BUNDLED
@@ -89,7 +89,7 @@ type AttributeDefinition {
89
89
  }
90
90
 
91
91
  """
92
- Who fills the attribute value: MERCHANT (admin on product), CUSTOMER (in cart), or BOTH
92
+ Who provides the value: MERCHANT (set by the seller — product metadata, not shown as an editable field), CUSTOMER (entered or chosen by the shopper in the configurator), or BOTH (seller sets a default the shopper can change).
93
93
  """
94
94
  enum AttributeFillingMode {
95
95
  BOTH
@@ -2167,6 +2167,106 @@ enum CompensationType {
2167
2167
  STORE_CREDIT
2168
2168
  }
2169
2169
 
2170
+ """
2171
+ A single field of a product configurator — an input the shopper sees on the product page.
2172
+ """
2173
+ type ConfiguratorField {
2174
+ """Optional help text shown beneath the field."""
2175
+ description: String
2176
+
2177
+ """
2178
+ Who provides the value — CUSTOMER (the shopper fills it in) or BOTH (seller sets a default the shopper can change). Seller-only metadata fields are not returned here.
2179
+ """
2180
+ filledBy: AttributeFillingMode!
2181
+
2182
+ """URL-friendly key for the field."""
2183
+ handle: String!
2184
+
2185
+ """Stable identifier of this field."""
2186
+ id: ID!
2187
+
2188
+ """Whether this field should be shown on the storefront."""
2189
+ isVisible: Boolean!
2190
+
2191
+ """Upper bound — maximum value (NUMBER) or maximum length (TEXT)."""
2192
+ maxValue: Float
2193
+
2194
+ """Lower bound — minimum value (NUMBER) or minimum length (TEXT)."""
2195
+ minValue: Float
2196
+
2197
+ """Field label shown to the shopper (e.g. "Finish")."""
2198
+ name: String!
2199
+
2200
+ """
2201
+ Selectable choices for SELECT / RADIO / CHECKBOX fields; empty for free-input types (TEXT, NUMBER, …).
2202
+ """
2203
+ options: [ConfiguratorOption!]!
2204
+
2205
+ """
2206
+ If a choice adds cost, how it is billed — BUNDLED (folded into the product price) or SEPARATE_LINE (its own order line). Null when the field has no price impact.
2207
+ """
2208
+ pricingMode: AttributeBillingMode
2209
+
2210
+ """
2211
+ Whether the shopper must provide a value before adding the product to the cart.
2212
+ """
2213
+ required: Boolean!
2214
+
2215
+ """Order in which to render this field."""
2216
+ sortOrder: Int!
2217
+
2218
+ """
2219
+ Input type — controls how to render the field (TEXT, TEXTAREA, SELECT, RADIO, CHECKBOX, NUMBER, COLOR, …).
2220
+ """
2221
+ type: AttributeType!
2222
+ }
2223
+
2224
+ """Filter for the configuratorFields query."""
2225
+ input ConfiguratorFieldFilterInput {
2226
+ """
2227
+ Return only fields with this filledBy value. The storefront configurator typically requests CUSTOMER + BOTH (the fields a shopper can edit).
2228
+ """
2229
+ filledBy: AttributeFillingMode
2230
+ }
2231
+
2232
+ """
2233
+ A selectable choice within a product configurator field (e.g. a size, finish or add-on).
2234
+ """
2235
+ type ConfiguratorOption {
2236
+ """Hex colour for swatch rendering (COLOR fields)."""
2237
+ colorHex: String
2238
+
2239
+ """Stable identifier of this choice."""
2240
+ id: ID!
2241
+
2242
+ """Whether this choice is pre-selected by default."""
2243
+ isDefault: Boolean!
2244
+
2245
+ """Human-readable label shown to the shopper."""
2246
+ label: String!
2247
+
2248
+ """
2249
+ The stocked variant this choice maps to (used for inventory-backed components). Null when the choice maps to no variant or the variant no longer exists.
2250
+ """
2251
+ linkedVariant: LinkedVariantSummary
2252
+
2253
+ """Order in which to render this choice."""
2254
+ sortOrder: Int!
2255
+
2256
+ """
2257
+ Extra charge applied when this choice is selected. With surchargeType FIXED it is an amount in minor currency units (e.g. 500 = 5.00). With PERCENT it is thousandths of a percent (e.g. 1500 = 1.5%).
2258
+ """
2259
+ surchargeAmount: Int
2260
+
2261
+ """How to interpret surchargeAmount — FIXED (a money amount) or PERCENT."""
2262
+ surchargeType: AttributeOptionSurchargeType
2263
+
2264
+ """
2265
+ Machine value (slug-style) — use it for form state and when submitting the choice to the cart.
2266
+ """
2267
+ value: String!
2268
+ }
2269
+
2170
2270
  """Format of stored content (HTML or MARKDOWN)"""
2171
2271
  enum ContentFormat {
2172
2272
  HTML
@@ -3455,7 +3555,7 @@ enum LanguageDirection {
3455
3555
  }
3456
3556
 
3457
3557
  """
3458
- Summary of a ProductVariant linked to an AttributeOption (product configurator).
3558
+ Summary of the stocked ProductVariant a configurator option maps to (component-style choice). Exposed via `ConfiguratorOption.linkedVariant`.
3459
3559
  """
3460
3560
  type LinkedVariantSummary {
3461
3561
  """Available stock (stock − active reservations, always ≥ 0)."""
@@ -5273,18 +5373,15 @@ type PricingTier {
5273
5373
 
5274
5374
  """Product - main catalog item"""
5275
5375
  type Product implements Node {
5276
- """Assigned AttributeSet ID (shared template fields)"""
5277
- attributeSetId: ID
5278
-
5279
5376
  """
5280
- Customer-facing product attributes (configurator)set definitions + per-product scoped
5377
+ This product's stored custom-field values merchant-managed metadata such as manufacturer, licence, material or EAN. Only fields the merchant marked visible are returned. Pass `namespace` to fetch a single group.
5281
5378
  """
5282
5379
  attributes(
5283
5380
  """
5284
- Optional filter — pass `{ fillingMode: CUSTOMER }` to return only buyer-fillable configurator fields
5381
+ Optional group filter — return only attributes in this namespace (e.g. "specs").
5285
5382
  """
5286
- filter: ProductAttributeFilterInput
5287
- ): [ProductAttributeDefinition!]!
5383
+ namespace: String
5384
+ ): [EntityAttributeField!]!
5288
5385
 
5289
5386
  """Average rating (1-5)"""
5290
5387
  averageRating: Float
@@ -5307,6 +5404,16 @@ type Product implements Node {
5307
5404
  """Opt-in: compare-at price range with conversion transparency."""
5308
5405
  compareAtPriceRangeWithConversion: ConvertedPriceRange
5309
5406
 
5407
+ """
5408
+ Configurable fields shown on the product page for the shopper to fill in or choose (combines shared template fields and product-specific ones). Pass `{ filledBy: CUSTOMER }` to return only the fields a shopper can edit.
5409
+ """
5410
+ configuratorFields(
5411
+ """
5412
+ Optional filter — pass `{ filledBy: CUSTOMER }` to return only the fields a shopper can edit.
5413
+ """
5414
+ filter: ConfiguratorFieldFilterInput
5415
+ ): [ConfiguratorField!]!
5416
+
5310
5417
  """Creation timestamp"""
5311
5418
  createdAt: DateTime!
5312
5419
 
@@ -5433,103 +5540,6 @@ type Product implements Node {
5433
5540
  visibility: ProductVisibility!
5434
5541
  }
5435
5542
 
5436
- """Product attribute definition (configurator)"""
5437
- type ProductAttributeDefinition {
5438
- """Billing mode — BUNDLED | SEPARATE_LINE; null = informational only"""
5439
- billingMode: AttributeBillingMode
5440
-
5441
- """Customer-facing description"""
5442
- description: String
5443
-
5444
- """Display order"""
5445
- displayOrder: Int!
5446
-
5447
- """Filling mode — MERCHANT | CUSTOMER | BOTH"""
5448
- fillingMode: AttributeFillingMode!
5449
-
5450
- """URL-friendly handle"""
5451
- handle: String!
5452
-
5453
- """Definition ID"""
5454
- id: ID!
5455
-
5456
- """Required validation flag"""
5457
- isRequired: Boolean!
5458
-
5459
- """Visibility flag"""
5460
- isVisible: Boolean!
5461
-
5462
- """Max value (NUMBER) / max length (TEXT)"""
5463
- maxValue: Float
5464
-
5465
- """Min value (NUMBER) / min length (TEXT)"""
5466
- minValue: Float
5467
-
5468
- """Field name (e.g. "Finiszer")"""
5469
- name: String!
5470
-
5471
- """Options for SELECT/RADIO/CHECKBOX types"""
5472
- options: [ProductAttributeOption!]!
5473
-
5474
- """
5475
- Per-product scope — set to product ID for unique configurator fields, null for shared (in AttributeSet)
5476
- """
5477
- scopeProductId: ID
5478
-
5479
- """Tax class ID; null = inherit from variant"""
5480
- taxClassId: ID
5481
-
5482
- """Field type — TEXT/SELECT/RADIO/CHECKBOX/etc."""
5483
- type: AttributeType!
5484
- }
5485
-
5486
- """Filter for product attributes query"""
5487
- input ProductAttributeFilterInput {
5488
- """
5489
- Filter by filling mode — MERCHANT | CUSTOMER | BOTH (configurator UI uses CUSTOMER + BOTH)
5490
- """
5491
- fillingMode: AttributeFillingMode
5492
- }
5493
-
5494
- """Selectable option of a product attribute (configurator)"""
5495
- type ProductAttributeOption {
5496
- """Hex color (for COLOR/SWATCH types)"""
5497
- colorHex: String
5498
-
5499
- """Option ID"""
5500
- id: ID!
5501
-
5502
- """Default option marker"""
5503
- isDefault: Boolean!
5504
-
5505
- """Display label shown to customer"""
5506
- label: String!
5507
-
5508
- """
5509
- Summary of the linked ProductVariant. Null when the option has no linkedVariantId or the variant was deleted.
5510
- """
5511
- linkedVariant: LinkedVariantSummary
5512
-
5513
- """
5514
- Linked ProductVariant ID for component stock decrement (hidden-products pattern).
5515
- """
5516
- linkedVariantId: ID
5517
-
5518
- """Sort order"""
5519
- sortOrder: Int!
5520
-
5521
- """
5522
- Surcharge amount when selected (FIXED = minor currency units; PERCENT = thousandths of percent).
5523
- """
5524
- surchargeAmount: Int
5525
-
5526
- """Surcharge type — FIXED | PERCENT."""
5527
- surchargeType: AttributeOptionSurchargeType
5528
-
5529
- """Internal value (slug-style)"""
5530
- value: String!
5531
- }
5532
-
5533
5543
  """Paginated product list"""
5534
5544
  type ProductConnection {
5535
5545
  """Product edges with cursors"""