@doswiftly/storefront-operations 20.1.0 → 21.0.0

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.1.0
30
+ - **Schema version**: 21.0.0
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,60 @@
1
1
  # Changelog
2
2
 
3
+ ## 21.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - a539021: Product attribute fields renamed and split by purpose (breaking)
8
+
9
+ `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.
10
+
11
+ The configurator field **definitions** that previously lived under `Product.attributes` have moved to **`Product.configuratorFields(filter: { filledBy: ... })`**, returning `[ConfiguratorField!]!`.
12
+
13
+ Renames:
14
+ - Type `ProductAttributeDefinition` → `ConfiguratorField`, with field renames `fillingMode` → `filledBy`, `billingMode` → `pricingMode`, `isRequired` → `required`, `displayOrder` → `sortOrder`.
15
+ - Type `ProductAttributeOption` → `ConfiguratorOption`.
16
+ - Input `ProductAttributeFilterInput` → `ConfiguratorFieldFilterInput` (its `fillingMode` argument → `filledBy`).
17
+
18
+ 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).
19
+
20
+ 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.
21
+
22
+ ## 20.2.0
23
+
24
+ ### Minor Changes
25
+
26
+ - 17fedcf: Addresses now carry a structured building / flat number alongside the free-form street line.
27
+
28
+ **Why**: Polish addresses — and carriers like InPost as well as VAT invoicing — need the building number as a discrete field rather than mixed into a single street line (e.g. `"ul. Piękna 5/3"` must split into building `5`, flat `3`). The `MailingAddress` type and the cart / mailing address inputs now expose `buildingNumber` and `flatNumber` so storefronts can collect and display them precisely.
29
+
30
+ **Additive (backward-compatible)**:
31
+ 1. `MailingAddress` (read) gains `buildingNumber` and `flatNumber` — populated on cart, order and saved customer addresses; `null` when only the free-form line was provided.
32
+ 2. The address inputs (`CartAddressInput`, `MailingAddressInput`) accept optional `buildingNumber` and `flatNumber`. For a Polish street delivery without a pickup point the building number is now required server-side — omitting it returns a `BUILDING_NUMBER_REQUIRED` user error instead of silently guessing it from the street text.
33
+
34
+ **Usage example**:
35
+
36
+ ```ts
37
+ await cartSetShippingAddress({
38
+ cartId,
39
+ address: {
40
+ streetLine1: "ul. Piękna 5/3",
41
+ buildingNumber: "5",
42
+ flatNumber: "3",
43
+ city: "Warszawa",
44
+ postalCode: "00-001",
45
+ country: "PL",
46
+ },
47
+ });
48
+
49
+ // reading it back
50
+ const { buildingNumber, flatNumber } = order.shippingAddress;
51
+ ```
52
+
53
+ **Migration checklist for existing storefronts**:
54
+ - [ ] Add a `buildingNumber` (and optional `flatNumber`) field to your address forms.
55
+ - [ ] For Polish street addresses, send `buildingNumber`, or handle the `BUILDING_NUMBER_REQUIRED` user error returned by the cart address mutations.
56
+ - [ ] Optionally render `buildingNumber` / `flatNumber` when displaying saved or order addresses.
57
+
3
58
  ## 20.1.0
4
59
 
5
60
  ### 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
  }
@@ -257,6 +259,8 @@ fragment MailingAddress on MailingAddress {
257
259
  id
258
260
  streetLine1
259
261
  streetLine2
262
+ buildingNumber
263
+ flatNumber
260
264
  city
261
265
  company
262
266
  country
@@ -1630,10 +1634,26 @@ fragment UrlRedirect on UrlRedirect {
1630
1634
  }
1631
1635
 
1632
1636
  # ============================================
1633
- # 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)
1634
1654
  # ============================================
1635
1655
 
1636
- # 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`.
1637
1657
  fragment LinkedVariantSummary on LinkedVariantSummary {
1638
1658
  id
1639
1659
  productId
@@ -1644,8 +1664,8 @@ fragment LinkedVariantSummary on LinkedVariantSummary {
1644
1664
  trackQuantity
1645
1665
  }
1646
1666
 
1647
- # 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.
1648
- 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 {
1649
1669
  id
1650
1670
  value
1651
1671
  label
@@ -1654,29 +1674,26 @@ fragment ProductAttributeOption on ProductAttributeOption {
1654
1674
  surchargeAmount
1655
1675
  surchargeType
1656
1676
  isDefault
1657
- linkedVariantId
1658
1677
  linkedVariant {
1659
1678
  ...LinkedVariantSummary
1660
1679
  }
1661
1680
  }
1662
1681
 
1663
- # 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.
1664
- 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 {
1665
1684
  id
1666
1685
  name
1667
1686
  handle
1668
1687
  description
1669
1688
  type
1670
- fillingMode
1671
- billingMode
1672
- taxClassId
1673
- scopeProductId
1674
- isRequired
1689
+ filledBy
1690
+ pricingMode
1691
+ required
1675
1692
  isVisible
1676
- displayOrder
1693
+ sortOrder
1677
1694
  minValue
1678
1695
  maxValue
1679
1696
  options {
1680
- ...ProductAttributeOption
1697
+ ...ConfiguratorOption
1681
1698
  }
1682
1699
  }
package/llms-full.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # DoSwiftly Storefront Operations — Full Reference
2
2
 
3
- > Schema version: **20.1.0**
4
- > 52 queries · 44 mutations · 104 fragments
3
+ > Schema version: **21.0.0**
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
  }
@@ -2937,6 +2939,8 @@ fragment MailingAddress on MailingAddress {
2937
2939
  id
2938
2940
  streetLine1
2939
2941
  streetLine2
2942
+ buildingNumber
2943
+ flatNumber
2940
2944
  city
2941
2945
  company
2942
2946
  country
@@ -5078,11 +5082,31 @@ fragment UrlRedirect on UrlRedirect {
5078
5082
  }
5079
5083
  ```
5080
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
+
5081
5105
  ### Fragment: `LinkedVariantSummary` on `LinkedVariantSummary`
5082
5106
 
5083
- **Section**: Product Configurator (per-product attributes)
5107
+ **Section**: Product Configurator (per-product fields)
5084
5108
 
5085
- **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`.
5086
5110
 
5087
5111
  **GraphQL**:
5088
5112
  ```graphql
@@ -5097,17 +5121,17 @@ fragment LinkedVariantSummary on LinkedVariantSummary {
5097
5121
  }
5098
5122
  ```
5099
5123
 
5100
- ### Fragment: `ProductAttributeOption` on `ProductAttributeOption`
5124
+ ### Fragment: `ConfiguratorOption` on `ConfiguratorOption`
5101
5125
 
5102
- **Section**: Product Configurator (per-product attributes)
5126
+ **Section**: Product Configurator (per-product fields)
5103
5127
 
5104
- **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.
5105
5129
 
5106
5130
  **Uses fragments**: `LinkedVariantSummary`
5107
5131
 
5108
5132
  **GraphQL**:
5109
5133
  ```graphql
5110
- fragment ProductAttributeOption on ProductAttributeOption {
5134
+ fragment ConfiguratorOption on ConfiguratorOption {
5111
5135
  id
5112
5136
  value
5113
5137
  label
@@ -5116,40 +5140,37 @@ fragment ProductAttributeOption on ProductAttributeOption {
5116
5140
  surchargeAmount
5117
5141
  surchargeType
5118
5142
  isDefault
5119
- linkedVariantId
5120
5143
  linkedVariant {
5121
5144
  ...LinkedVariantSummary
5122
5145
  }
5123
5146
  }
5124
5147
  ```
5125
5148
 
5126
- ### Fragment: `ProductAttributeDefinition` on `ProductAttributeDefinition`
5149
+ ### Fragment: `ConfiguratorField` on `ConfiguratorField`
5127
5150
 
5128
- **Section**: Product Configurator (per-product attributes)
5151
+ **Section**: Product Configurator (per-product fields)
5129
5152
 
5130
- **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.
5131
5154
 
5132
- **Uses fragments**: `ProductAttributeOption`
5155
+ **Uses fragments**: `ConfiguratorOption`
5133
5156
 
5134
5157
  **GraphQL**:
5135
5158
  ```graphql
5136
- fragment ProductAttributeDefinition on ProductAttributeDefinition {
5159
+ fragment ConfiguratorField on ConfiguratorField {
5137
5160
  id
5138
5161
  name
5139
5162
  handle
5140
5163
  description
5141
5164
  type
5142
- fillingMode
5143
- billingMode
5144
- taxClassId
5145
- scopeProductId
5146
- isRequired
5165
+ filledBy
5166
+ pricingMode
5167
+ required
5147
5168
  isVisible
5148
- displayOrder
5169
+ sortOrder
5149
5170
  minValue
5150
5171
  maxValue
5151
5172
  options {
5152
- ...ProductAttributeOption
5173
+ ...ConfiguratorOption
5153
5174
  }
5154
5175
  }
5155
5176
  ```
package/operations.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "20.1.0",
2
+ "schemaVersion": "21.0.0",
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
  {
@@ -2117,7 +2118,7 @@
2117
2118
  "fragmentRefs": [
2118
2119
  "PickupPoint"
2119
2120
  ],
2120
- "body": "fragment MailingAddress on MailingAddress {\n id\n streetLine1\n streetLine2\n city\n company\n country\n countryCode\n firstName\n lastName\n name\n phone\n state\n stateCode\n postalCode\n isDefault\n taxId\n vatNumber\n regon\n pickupPoint {\n ...PickupPoint\n }\n}",
2121
+ "body": "fragment MailingAddress on MailingAddress {\n id\n streetLine1\n streetLine2\n buildingNumber\n flatNumber\n city\n company\n country\n countryCode\n firstName\n lastName\n name\n phone\n state\n stateCode\n postalCode\n isDefault\n taxId\n vatNumber\n regon\n pickupPoint {\n ...PickupPoint\n }\n}",
2121
2122
  "onType": "MailingAddress"
2122
2123
  },
2123
2124
  {
@@ -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.1.0",
3
+ "version": "21.0.0",
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
@@ -1049,6 +1049,11 @@ type CartAddLinesPayload {
1049
1049
  Mailing address used as the shipping or billing address on a cart. Carries an optional pickup point (for parcel locker / collection-point delivery) and optional B2B fields (`taxId`, `vatNumber`, `regon`).
1050
1050
  """
1051
1051
  input CartAddressInput {
1052
+ """
1053
+ Building / house number as a discrete field, overlaid on `streetLine1`. Required for delivery and billing addresses in Poland (`country: PL`) without a `pickupPoint` — carriers (e.g. InPost) and invoicing need the building number split out from the street name.
1054
+ """
1055
+ buildingNumber: String
1056
+
1052
1057
  """City"""
1053
1058
  city: String!
1054
1059
 
@@ -1061,6 +1066,11 @@ input CartAddressInput {
1061
1066
  """First name"""
1062
1067
  firstName: String
1063
1068
 
1069
+ """
1070
+ Flat / apartment number as a discrete field — optional companion to `buildingNumber`.
1071
+ """
1072
+ flatNumber: String
1073
+
1064
1074
  """Last name"""
1065
1075
  lastName: String
1066
1076
 
@@ -2157,6 +2167,106 @@ enum CompensationType {
2157
2167
  STORE_CREDIT
2158
2168
  }
2159
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
+
2160
2270
  """Format of stored content (HTML or MARKDOWN)"""
2161
2271
  enum ContentFormat {
2162
2272
  HTML
@@ -3445,7 +3555,7 @@ enum LanguageDirection {
3445
3555
  }
3446
3556
 
3447
3557
  """
3448
- 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`.
3449
3559
  """
3450
3560
  type LinkedVariantSummary {
3451
3561
  """Available stock (stock − active reservations, always ≥ 0)."""
@@ -3836,6 +3946,11 @@ enum LoyaltyTransactionType {
3836
3946
  A mailing address — shipping or billing — used on a cart, on the resulting order, or saved in a customer address book. Carries optional B2B fields (`taxId`, `vatNumber`, `regon`) and an optional `pickupPoint` for parcel locker / collection-point delivery.
3837
3947
  """
3838
3948
  type MailingAddress implements Node {
3949
+ """
3950
+ Building / house number as a discrete field, overlaid on `streetLine1`. Populated for structured addresses (required for PL delivery / billing without a pickup point); null when only the free-form `streetLine1` was provided.
3951
+ """
3952
+ buildingNumber: String
3953
+
3839
3954
  """City / town."""
3840
3955
  city: String
3841
3956
 
@@ -3853,6 +3968,11 @@ type MailingAddress implements Node {
3853
3968
  """Buyer first name."""
3854
3969
  firstName: String
3855
3970
 
3971
+ """
3972
+ Flat / apartment number as a discrete field — optional companion to `buildingNumber`.
3973
+ """
3974
+ flatNumber: String
3975
+
3856
3976
  """
3857
3977
  Country-aware ordered address lines, ready to render line by line in UI without manual formatting. The exact line layout depends on country (e.g. PL puts postal code before city, US after state).
3858
3978
  """
@@ -3944,6 +4064,11 @@ type MailingAddressEdge {
3944
4064
 
3945
4065
  """Input for mailing address"""
3946
4066
  input MailingAddressInput {
4067
+ """
4068
+ Building / house number as a discrete field, overlaid on `streetLine1`. Required for saved Polish addresses (`country: PL`) — used for carrier delivery and invoicing.
4069
+ """
4070
+ buildingNumber: String
4071
+
3947
4072
  """City"""
3948
4073
  city: String
3949
4074
 
@@ -3956,6 +4081,11 @@ input MailingAddressInput {
3956
4081
  """First name"""
3957
4082
  firstName: String
3958
4083
 
4084
+ """
4085
+ Flat / apartment number as a discrete field — optional companion to `buildingNumber`.
4086
+ """
4087
+ flatNumber: String
4088
+
3959
4089
  """Last name"""
3960
4090
  lastName: String
3961
4091
 
@@ -5243,18 +5373,15 @@ type PricingTier {
5243
5373
 
5244
5374
  """Product - main catalog item"""
5245
5375
  type Product implements Node {
5246
- """Assigned AttributeSet ID (shared template fields)"""
5247
- attributeSetId: ID
5248
-
5249
5376
  """
5250
- 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.
5251
5378
  """
5252
5379
  attributes(
5253
5380
  """
5254
- 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").
5255
5382
  """
5256
- filter: ProductAttributeFilterInput
5257
- ): [ProductAttributeDefinition!]!
5383
+ namespace: String
5384
+ ): [EntityAttributeField!]!
5258
5385
 
5259
5386
  """Average rating (1-5)"""
5260
5387
  averageRating: Float
@@ -5277,6 +5404,16 @@ type Product implements Node {
5277
5404
  """Opt-in: compare-at price range with conversion transparency."""
5278
5405
  compareAtPriceRangeWithConversion: ConvertedPriceRange
5279
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
+
5280
5417
  """Creation timestamp"""
5281
5418
  createdAt: DateTime!
5282
5419
 
@@ -5403,103 +5540,6 @@ type Product implements Node {
5403
5540
  visibility: ProductVisibility!
5404
5541
  }
5405
5542
 
5406
- """Product attribute definition (configurator)"""
5407
- type ProductAttributeDefinition {
5408
- """Billing mode — BUNDLED | SEPARATE_LINE; null = informational only"""
5409
- billingMode: AttributeBillingMode
5410
-
5411
- """Customer-facing description"""
5412
- description: String
5413
-
5414
- """Display order"""
5415
- displayOrder: Int!
5416
-
5417
- """Filling mode — MERCHANT | CUSTOMER | BOTH"""
5418
- fillingMode: AttributeFillingMode!
5419
-
5420
- """URL-friendly handle"""
5421
- handle: String!
5422
-
5423
- """Definition ID"""
5424
- id: ID!
5425
-
5426
- """Required validation flag"""
5427
- isRequired: Boolean!
5428
-
5429
- """Visibility flag"""
5430
- isVisible: Boolean!
5431
-
5432
- """Max value (NUMBER) / max length (TEXT)"""
5433
- maxValue: Float
5434
-
5435
- """Min value (NUMBER) / min length (TEXT)"""
5436
- minValue: Float
5437
-
5438
- """Field name (e.g. "Finiszer")"""
5439
- name: String!
5440
-
5441
- """Options for SELECT/RADIO/CHECKBOX types"""
5442
- options: [ProductAttributeOption!]!
5443
-
5444
- """
5445
- Per-product scope — set to product ID for unique configurator fields, null for shared (in AttributeSet)
5446
- """
5447
- scopeProductId: ID
5448
-
5449
- """Tax class ID; null = inherit from variant"""
5450
- taxClassId: ID
5451
-
5452
- """Field type — TEXT/SELECT/RADIO/CHECKBOX/etc."""
5453
- type: AttributeType!
5454
- }
5455
-
5456
- """Filter for product attributes query"""
5457
- input ProductAttributeFilterInput {
5458
- """
5459
- Filter by filling mode — MERCHANT | CUSTOMER | BOTH (configurator UI uses CUSTOMER + BOTH)
5460
- """
5461
- fillingMode: AttributeFillingMode
5462
- }
5463
-
5464
- """Selectable option of a product attribute (configurator)"""
5465
- type ProductAttributeOption {
5466
- """Hex color (for COLOR/SWATCH types)"""
5467
- colorHex: String
5468
-
5469
- """Option ID"""
5470
- id: ID!
5471
-
5472
- """Default option marker"""
5473
- isDefault: Boolean!
5474
-
5475
- """Display label shown to customer"""
5476
- label: String!
5477
-
5478
- """
5479
- Summary of the linked ProductVariant. Null when the option has no linkedVariantId or the variant was deleted.
5480
- """
5481
- linkedVariant: LinkedVariantSummary
5482
-
5483
- """
5484
- Linked ProductVariant ID for component stock decrement (hidden-products pattern).
5485
- """
5486
- linkedVariantId: ID
5487
-
5488
- """Sort order"""
5489
- sortOrder: Int!
5490
-
5491
- """
5492
- Surcharge amount when selected (FIXED = minor currency units; PERCENT = thousandths of percent).
5493
- """
5494
- surchargeAmount: Int
5495
-
5496
- """Surcharge type — FIXED | PERCENT."""
5497
- surchargeType: AttributeOptionSurchargeType
5498
-
5499
- """Internal value (slug-style)"""
5500
- value: String!
5501
- }
5502
-
5503
5543
  """Paginated product list"""
5504
5544
  type ProductConnection {
5505
5545
  """Product edges with cursors"""