@doswiftly/storefront-operations 12.0.0 → 13.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 +1 -1
- package/CHANGELOG.md +176 -0
- package/README.md +2 -2
- package/fragments.graphql +3 -2
- package/llms-full.txt +5 -4
- package/operations.json +4 -4
- package/package.json +1 -1
- package/queries.graphql +1 -1
- package/schema.graphql +135 -155
package/AGENTS.md
CHANGED
|
@@ -27,7 +27,7 @@ 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**:
|
|
30
|
+
- **Schema version**: 13.0.0
|
|
31
31
|
- **Queries**: 49
|
|
32
32
|
- **Mutations**: 40
|
|
33
33
|
- **Fragments**: 100
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,181 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 13.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 783ce01: Faceted aggregation accuracy + DX naming consolidation w `productFilters` API. Plus boolean facet count dla "availableForSale" + payload cleanup w Cart query.
|
|
8
|
+
|
|
9
|
+
## BREAKING — `AvailableFilters.matchCount` removed → `totalCount`
|
|
10
|
+
|
|
11
|
+
Pole `matchCount` zostało usunięte. Użyj `totalCount` — semantyka identyczna (produkty w current context PRZED zaaplikowaniem faceted filters), nazwa zaligned z `ProductConnection.totalCount` (Relay Connection spec).
|
|
12
|
+
|
|
13
|
+
**Migration** (1:1 rename):
|
|
14
|
+
|
|
15
|
+
```graphql
|
|
16
|
+
# PRZED
|
|
17
|
+
query Listing {
|
|
18
|
+
productFilters {
|
|
19
|
+
matchCount # ❌ removed
|
|
20
|
+
activeCount
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# PO
|
|
25
|
+
query Listing {
|
|
26
|
+
productFilters {
|
|
27
|
+
totalCount # ✅ same semantics, Relay-aligned
|
|
28
|
+
activeCount
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## BREAKING — `ProductFilter.available: true` semantics
|
|
34
|
+
|
|
35
|
+
Filter teraz includuje untracked produkty (gift cards, digital, made-to-order — wszystko z `trackQuantity = false`). Semantyka zgodna z `Product.isAvailable` resolver — single source of truth dla "co jest sellable".
|
|
36
|
+
|
|
37
|
+
**Pre-fix**: `available: true` zwracał tylko produkty z fizycznym stockiem (`available > 0`), wykluczał untracked. Storefront widział "Dostępne (3)" w sidebar (computed via `isAvailable`), po kliku dostawał 2 produkty (SQL filter różny).
|
|
38
|
+
|
|
39
|
+
**Post-fix**: oba źródła zwracają to samo.
|
|
40
|
+
|
|
41
|
+
Jeśli polegałeś na exclusion untracked przez `available: true` (np. żeby ukryć gift cards) — użyj `ProductFilter.type` lub osobnej logiki klienckiej.
|
|
42
|
+
|
|
43
|
+
## NEW — `AvailableFilters.availableCount: Int!`
|
|
44
|
+
|
|
45
|
+
Nowy boolean facet count — liczba produktów spełniających `Product.isAvailable` w current context. Storefront UI renderuje "Dostępne (N)" w sidebar facet panel.
|
|
46
|
+
|
|
47
|
+
Exclude-self: gdy `AvailableFiltersInput.available` jest provided w `currentFilters`, `availableCount` IGNORUJE ten filter (pokazuje "ile produktów ten facet odsłoni gdy włączysz") — inne facets aplikują go normalnie.
|
|
48
|
+
|
|
49
|
+
## NEW — `AvailableFiltersInput.available: Boolean`
|
|
50
|
+
|
|
51
|
+
Storefront może passować obecny `available` filter context do `productFilters` query — wymagane dla exclude-self semantyki. Pomiń (null/undefined) gdy filtr "Dostępne" nie jest aktywny w UI.
|
|
52
|
+
|
|
53
|
+
## IMPROVEMENT — Faceted count accuracy (`productCount` mismatch eliminated)
|
|
54
|
+
|
|
55
|
+
Per-attribute / per-brand / per-category `productCount` teraz używa exclude-self aggregation pattern. Facet count MATCHUJE `products(filters).totalCount` gdy storefront aplikuje facet jako filter.
|
|
56
|
+
|
|
57
|
+
**Pre-fix**: `productFilters({categoryId, available: true}).attributes[producent].filterValues[Funko].productCount = 4` (ignoring `available`), `products({categoryId, available: true, attributes: [Funko]}).totalCount = 3` (3 sellable + 1 OOS Funko). Storefront pokazywał "Funko (4)", user dostawał 3 — utracone zaufanie do countów.
|
|
58
|
+
|
|
59
|
+
**Post-fix**: oba zwracają 3. Facet ignoruje WYŁĄCZNIE swój wymiar w `currentFilters`, aplikuje pozostałe + `available` + context (categoryId/collectionId/searchQuery).
|
|
60
|
+
|
|
61
|
+
## IMPROVEMENT — Cart query payload size
|
|
62
|
+
|
|
63
|
+
`Cart.lines` query w SDK używał obu form (`edges + nodes`) z których konsumowane były wyłącznie `nodes`. Backend serializował `CartLine` fragment 2× per request. Usunięcie duplikatu — redukcja payload typowo 5-20KB per cart request (proportional do liczby pozycji).
|
|
64
|
+
|
|
65
|
+
Brak API change widzialnej dla consumer — SDK używa `cart.lines.nodes` jak wcześniej. Internal SDK improvement.
|
|
66
|
+
|
|
67
|
+
## Example: end-to-end facet sidebar query (post-rename)
|
|
68
|
+
|
|
69
|
+
```graphql
|
|
70
|
+
query ProductListing($input: AvailableFiltersInput) {
|
|
71
|
+
productFilters(input: $input) {
|
|
72
|
+
totalCount # ile produktów w current context (przed faceted filters)
|
|
73
|
+
availableCount # ile sellable (boolean facet, exclude-self)
|
|
74
|
+
activeCount # input.currentFilters.length
|
|
75
|
+
attributes {
|
|
76
|
+
handle
|
|
77
|
+
filterValues {
|
|
78
|
+
value
|
|
79
|
+
productCount
|
|
80
|
+
} # per-value facet count (exclude-self per attrDef)
|
|
81
|
+
}
|
|
82
|
+
brands {
|
|
83
|
+
slug
|
|
84
|
+
name
|
|
85
|
+
productCount
|
|
86
|
+
} # per-brand facet count
|
|
87
|
+
categories {
|
|
88
|
+
slug
|
|
89
|
+
name
|
|
90
|
+
productCount
|
|
91
|
+
} # per-category facet count
|
|
92
|
+
priceRange {
|
|
93
|
+
min {
|
|
94
|
+
amount
|
|
95
|
+
currencyCode
|
|
96
|
+
}
|
|
97
|
+
max {
|
|
98
|
+
amount
|
|
99
|
+
currencyCode
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
products(
|
|
105
|
+
filters: [
|
|
106
|
+
{ available: true }
|
|
107
|
+
{ attributes: [{ attributeId: "producent", values: ["Funko"] }] }
|
|
108
|
+
]
|
|
109
|
+
) {
|
|
110
|
+
totalCount
|
|
111
|
+
nodes {
|
|
112
|
+
id
|
|
113
|
+
title
|
|
114
|
+
isAvailable
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Gdy storefront aplikuje "Dostępne + Producent=Funko" jako filter w UI:
|
|
121
|
+
|
|
122
|
+
```graphql
|
|
123
|
+
variables: {
|
|
124
|
+
input: {
|
|
125
|
+
available: true,
|
|
126
|
+
currentFilters: [{ attributeId: "producent", values: ["Funko"] }]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- `availableCount` = liczba sellable Funko w context (ignoring `input.available`, applying `producent: Funko`)
|
|
132
|
+
- `attributes[producent].filterValues[Funko].productCount` = liczba sellable produktów (ignoring `producent`, applying `available: true`) = `products(filters).totalCount`
|
|
133
|
+
|
|
134
|
+
### Minor Changes
|
|
135
|
+
|
|
136
|
+
- e64cfc5: Brand entity available in Storefront API: filter products by canonical brand + read brand details per product.
|
|
137
|
+
|
|
138
|
+
**What's new in Storefront API**
|
|
139
|
+
- **`Product.brand: BrandSummary`** — every product now exposes its canonical brand (when assigned). Returns `{ id, name, slug, logo }` or `null` if the product has no brand. Resolved via DataLoader so listing 50 products incurs at most 1 brand query (no N+1).
|
|
140
|
+
- **`ProductFilter.brand: BrandFilter`** — new filter input accepting either `{ id: "uuid" }` or `{ slug: "funko" }`. Multiple `brand` entries in `filters[]` use OR semantics. Combine with other filters (price, tags, attributes) using AND across different field names.
|
|
141
|
+
- **`AvailableFilters.brands: [BrandFilterValue!]!`** — `productFilters` query now returns aggregated brands present in the current product set, each with `productCount`. Build a brand facet (checkbox list or dropdown with logo) directly from this response. Only active brands with at least one product in context are included.
|
|
142
|
+
|
|
143
|
+
**Example: brand facet + filter on a listing page**
|
|
144
|
+
|
|
145
|
+
```graphql
|
|
146
|
+
query Listing {
|
|
147
|
+
productFilters {
|
|
148
|
+
brands {
|
|
149
|
+
id
|
|
150
|
+
name
|
|
151
|
+
slug
|
|
152
|
+
logo
|
|
153
|
+
productCount
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
products(filters: [{ brand: { slug: "funko" } }]) {
|
|
157
|
+
totalCount
|
|
158
|
+
nodes {
|
|
159
|
+
id
|
|
160
|
+
title
|
|
161
|
+
brand {
|
|
162
|
+
name
|
|
163
|
+
slug
|
|
164
|
+
logo
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Migration**
|
|
172
|
+
- Existing `Product.vendor: String` (legacy free-text) remains supported. New brand-based filtering uses `ProductFilter.brand` instead of `productVendor`.
|
|
173
|
+
- No breaking changes. All new fields are nullable / optional — queries written before this release continue to work.
|
|
174
|
+
|
|
175
|
+
**SDK**
|
|
176
|
+
|
|
177
|
+
Regenerated types include `BrandSummary`, `BrandFilter`, `BrandFilterValue`, and `Product.brand`. Import them from `@doswiftly/storefront-operations` schema types.
|
|
178
|
+
|
|
3
179
|
## 12.0.0
|
|
4
180
|
|
|
5
181
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -247,7 +247,7 @@ full executable body of each operation.
|
|
|
247
247
|
|
|
248
248
|
| Operation | Description |
|
|
249
249
|
| --- | --- |
|
|
250
|
-
| `ProductFilters` | Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`,
|
|
250
|
+
| `ProductFilters` | Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`, `searchQuery`, optional `available` (boolean for the availability facet), and optional `currentFilters` (array of attribute filters currently applied by the UI). For each visible & filterable attribute, returns either discrete value counts (for `SELECT` / `CHECKBOX` types) or numeric range bounds (for `SLIDER` types). Plus `priceRange`, `brands`, per-category counts, `activeCount` (length of `currentFilters`), `totalCount` (products in context — Relay-aligned), and `availableCount` (boolean facet count for `availableForSale`). All per-facet counts use exclude-self aggregation: a facet's count IGNORES its own currently-applied filter and APPLIES other filters — so the count reflects "products if this facet were toggled" rather than "products currently visible". Untracked inventory (gift cards, digital, made-to-order) is always counted as available. Use to render filter sidebars on listing/search pages. |
|
|
251
251
|
|
|
252
252
|
#### Loyalty Program
|
|
253
253
|
|
|
@@ -554,7 +554,7 @@ full executable body of each operation.
|
|
|
554
554
|
| `AttributeDefinition` | `AttributeDefinition` | Filterable attribute exposed on the storefront — name, type (SELECT / CHECKBOX / SLIDER / etc.), filterability flags, display order, and either discrete `filterValues[]` or numeric `rangeBounds`. Spread inside `availableFilters.attributes[]`. |
|
|
555
555
|
| `PriceRangeFilter` | `PriceRange` | Min / max price range across products in the current listing context. Use to bound the price slider. |
|
|
556
556
|
| `CategoryFilterOption` | `CategoryFilterOption` | One category option in the categories filter — id, name, slug, count of products in this category within the current listing context, level + parentId for tree rendering. |
|
|
557
|
-
| `AvailableFilters` | `AvailableFilters` | Full result of the `productFilters($input)` query — attribute filters, price range, category filters, count of currently active filters, total products
|
|
557
|
+
| `AvailableFilters` | `AvailableFilters` | Full result of the `productFilters($input)` query — attribute filters, price range, category filters, brands, count of currently active filters, total products in context (Relay-aligned `totalCount`), and boolean facet count for `availableForSale` (`availableCount`). Spread on listing pages to render filter sidebars. Per-facet counts (`attributes[].filterValues[].productCount`, `brands[].productCount`, `categories[].productCount`, `availableCount`) use exclude-self aggregation: when a dimension is in `input.currentFilters` / `input.available`, its own facet count IGNORES that filter (shows "how many products would appear if this facet were toggled"), while OTHER dimensions APPLY their filters. This matches industry convention for accurate facet UX. |
|
|
558
558
|
|
|
559
559
|
#### Loyalty Program
|
|
560
560
|
|
package/fragments.graphql
CHANGED
|
@@ -1012,7 +1012,7 @@ fragment CategoryFilterOption on CategoryFilterOption {
|
|
|
1012
1012
|
parentId
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
1015
|
-
# Full result of the `productFilters($input)` query — attribute filters, price range, category filters, count of currently active filters, total products
|
|
1015
|
+
# Full result of the `productFilters($input)` query — attribute filters, price range, category filters, brands, count of currently active filters, total products in context (Relay-aligned `totalCount`), and boolean facet count for `availableForSale` (`availableCount`). Spread on listing pages to render filter sidebars. Per-facet counts (`attributes[].filterValues[].productCount`, `brands[].productCount`, `categories[].productCount`, `availableCount`) use exclude-self aggregation: when a dimension is in `input.currentFilters` / `input.available`, its own facet count IGNORES that filter (shows "how many products would appear if this facet were toggled"), while OTHER dimensions APPLY their filters. This matches industry convention for accurate facet UX.
|
|
1016
1016
|
fragment AvailableFilters on AvailableFilters {
|
|
1017
1017
|
attributes {
|
|
1018
1018
|
...AttributeDefinition
|
|
@@ -1024,7 +1024,8 @@ fragment AvailableFilters on AvailableFilters {
|
|
|
1024
1024
|
...CategoryFilterOption
|
|
1025
1025
|
}
|
|
1026
1026
|
activeCount
|
|
1027
|
-
|
|
1027
|
+
totalCount
|
|
1028
|
+
availableCount
|
|
1028
1029
|
}
|
|
1029
1030
|
|
|
1030
1031
|
# ============================================
|
package/llms-full.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DoSwiftly Storefront Operations — Full Reference
|
|
2
2
|
|
|
3
|
-
> Schema version: **
|
|
3
|
+
> Schema version: **13.0.0**
|
|
4
4
|
> 49 queries · 40 mutations · 100 fragments
|
|
5
5
|
|
|
6
6
|
Auto-generated from `.graphql` source files. Do not edit by hand — this file is
|
|
@@ -705,7 +705,7 @@ query AvailableShippingMethods($address: ShippingAddressInput!, $cart: CartShipp
|
|
|
705
705
|
|
|
706
706
|
**Section**: Attribute Filters
|
|
707
707
|
|
|
708
|
-
**Description**: Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`,
|
|
708
|
+
**Description**: Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`, `searchQuery`, optional `available` (boolean for the availability facet), and optional `currentFilters` (array of attribute filters currently applied by the UI). For each visible & filterable attribute, returns either discrete value counts (for `SELECT` / `CHECKBOX` types) or numeric range bounds (for `SLIDER` types). Plus `priceRange`, `brands`, per-category counts, `activeCount` (length of `currentFilters`), `totalCount` (products in context — Relay-aligned), and `availableCount` (boolean facet count for `availableForSale`). All per-facet counts use exclude-self aggregation: a facet's count IGNORES its own currently-applied filter and APPLIES other filters — so the count reflects "products if this facet were toggled" rather than "products currently visible". Untracked inventory (gift cards, digital, made-to-order) is always counted as available. Use to render filter sidebars on listing/search pages.
|
|
709
709
|
|
|
710
710
|
**Variables**:
|
|
711
711
|
- `$input`: `AvailableFiltersInput`
|
|
@@ -3887,7 +3887,7 @@ fragment CategoryFilterOption on CategoryFilterOption {
|
|
|
3887
3887
|
|
|
3888
3888
|
**Section**: Attribute Filters
|
|
3889
3889
|
|
|
3890
|
-
**Description**: Full result of the `productFilters($input)` query — attribute filters, price range, category filters, count of currently active filters, total products
|
|
3890
|
+
**Description**: Full result of the `productFilters($input)` query — attribute filters, price range, category filters, brands, count of currently active filters, total products in context (Relay-aligned `totalCount`), and boolean facet count for `availableForSale` (`availableCount`). Spread on listing pages to render filter sidebars. Per-facet counts (`attributes[].filterValues[].productCount`, `brands[].productCount`, `categories[].productCount`, `availableCount`) use exclude-self aggregation: when a dimension is in `input.currentFilters` / `input.available`, its own facet count IGNORES that filter (shows "how many products would appear if this facet were toggled"), while OTHER dimensions APPLY their filters. This matches industry convention for accurate facet UX.
|
|
3891
3891
|
|
|
3892
3892
|
**Uses fragments**: `AttributeDefinition`, `CategoryFilterOption`, `PriceRangeFilter`
|
|
3893
3893
|
|
|
@@ -3904,7 +3904,8 @@ fragment AvailableFilters on AvailableFilters {
|
|
|
3904
3904
|
...CategoryFilterOption
|
|
3905
3905
|
}
|
|
3906
3906
|
activeCount
|
|
3907
|
-
|
|
3907
|
+
totalCount
|
|
3908
|
+
availableCount
|
|
3908
3909
|
}
|
|
3909
3910
|
```
|
|
3910
3911
|
|
package/operations.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion": "
|
|
2
|
+
"schemaVersion": "13.0.0",
|
|
3
3
|
"queries": [
|
|
4
4
|
{
|
|
5
5
|
"name": "Shop",
|
|
@@ -528,7 +528,7 @@
|
|
|
528
528
|
"name": "ProductFilters",
|
|
529
529
|
"kind": "query",
|
|
530
530
|
"section": "Attribute Filters",
|
|
531
|
-
"description": "Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`,
|
|
531
|
+
"description": "Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`, `searchQuery`, optional `available` (boolean for the availability facet), and optional `currentFilters` (array of attribute filters currently applied by the UI). For each visible & filterable attribute, returns either discrete value counts (for `SELECT` / `CHECKBOX` types) or numeric range bounds (for `SLIDER` types). Plus `priceRange`, `brands`, per-category counts, `activeCount` (length of `currentFilters`), `totalCount` (products in context — Relay-aligned), and `availableCount` (boolean facet count for `availableForSale`). All per-facet counts use exclude-self aggregation: a facet's count IGNORES its own currently-applied filter and APPLIES other filters — so the count reflects \"products if this facet were toggled\" rather than \"products currently visible\". Untracked inventory (gift cards, digital, made-to-order) is always counted as available. Use to render filter sidebars on listing/search pages.",
|
|
532
532
|
"variables": [
|
|
533
533
|
{
|
|
534
534
|
"name": "input",
|
|
@@ -2557,14 +2557,14 @@
|
|
|
2557
2557
|
"name": "AvailableFilters",
|
|
2558
2558
|
"kind": "fragment",
|
|
2559
2559
|
"section": "Attribute Filters",
|
|
2560
|
-
"description": "Full result of the `productFilters($input)` query — attribute filters, price range, category filters, count of currently active filters, total products
|
|
2560
|
+
"description": "Full result of the `productFilters($input)` query — attribute filters, price range, category filters, brands, count of currently active filters, total products in context (Relay-aligned `totalCount`), and boolean facet count for `availableForSale` (`availableCount`). Spread on listing pages to render filter sidebars. Per-facet counts (`attributes[].filterValues[].productCount`, `brands[].productCount`, `categories[].productCount`, `availableCount`) use exclude-self aggregation: when a dimension is in `input.currentFilters` / `input.available`, its own facet count IGNORES that filter (shows \"how many products would appear if this facet were toggled\"), while OTHER dimensions APPLY their filters. This matches industry convention for accurate facet UX.",
|
|
2561
2561
|
"variables": [],
|
|
2562
2562
|
"fragmentRefs": [
|
|
2563
2563
|
"AttributeDefinition",
|
|
2564
2564
|
"CategoryFilterOption",
|
|
2565
2565
|
"PriceRangeFilter"
|
|
2566
2566
|
],
|
|
2567
|
-
"body": "fragment AvailableFilters on AvailableFilters {\n attributes {\n ...AttributeDefinition\n }\n priceRange {\n ...PriceRangeFilter\n }\n categories {\n ...CategoryFilterOption\n }\n activeCount\n
|
|
2567
|
+
"body": "fragment AvailableFilters on AvailableFilters {\n attributes {\n ...AttributeDefinition\n }\n priceRange {\n ...PriceRangeFilter\n }\n categories {\n ...CategoryFilterOption\n }\n activeCount\n totalCount\n availableCount\n}",
|
|
2568
2568
|
"onType": "AvailableFilters"
|
|
2569
2569
|
},
|
|
2570
2570
|
{
|
package/package.json
CHANGED
package/queries.graphql
CHANGED
|
@@ -387,7 +387,7 @@ query AvailableShippingMethods($address: ShippingAddressInput!, $cart: CartShipp
|
|
|
387
387
|
# Attribute Filters
|
|
388
388
|
# ============================================
|
|
389
389
|
|
|
390
|
-
# Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`,
|
|
390
|
+
# Returns the dynamic facet filters available for a listing context — pass `collectionId`, `categoryId`, `searchQuery`, optional `available` (boolean for the availability facet), and optional `currentFilters` (array of attribute filters currently applied by the UI). For each visible & filterable attribute, returns either discrete value counts (for `SELECT` / `CHECKBOX` types) or numeric range bounds (for `SLIDER` types). Plus `priceRange`, `brands`, per-category counts, `activeCount` (length of `currentFilters`), `totalCount` (products in context — Relay-aligned), and `availableCount` (boolean facet count for `availableForSale`). All per-facet counts use exclude-self aggregation: a facet's count IGNORES its own currently-applied filter and APPLIES other filters — so the count reflects "products if this facet were toggled" rather than "products currently visible". Untracked inventory (gift cards, digital, made-to-order) is always counted as available. Use to render filter sidebars on listing/search pages.
|
|
391
391
|
query ProductFilters($input: AvailableFiltersInput) {
|
|
392
392
|
productFilters(input: $input) {
|
|
393
393
|
...AvailableFilters
|
package/schema.graphql
CHANGED
|
@@ -72,6 +72,11 @@ type AttributeDefinition {
|
|
|
72
72
|
"""Attribute name (e.g., "Color", "Size")"""
|
|
73
73
|
name: String!
|
|
74
74
|
|
|
75
|
+
"""
|
|
76
|
+
Faza 3 Opcja A (2026-05-17): opcjonalne grupowanie atrybutu (np. "inventory", "marketing", "content"). NULL = atrybut bez grupy. Pomaga storefront-devom rozpoznać "te atrybuty są custom dane ERP" vs "te są filtrowalne cechy katalogu".
|
|
77
|
+
"""
|
|
78
|
+
namespace: String
|
|
79
|
+
|
|
75
80
|
"""Range bounds (for NUMBER, CURRENCY)"""
|
|
76
81
|
rangeBounds: AttributeRangeBounds
|
|
77
82
|
|
|
@@ -99,6 +104,11 @@ input AttributeFilterInput {
|
|
|
99
104
|
"""Minimum value (for NUMBER, CURRENCY)"""
|
|
100
105
|
minValue: Float
|
|
101
106
|
|
|
107
|
+
"""
|
|
108
|
+
Opcjonalny dyskryminator grupy atrybutów (np. "inventory", "marketing"). Faza 3 Opcja A (2026-05-17): konsolidacja MetaProperty-style custom fields pod AttributeDefinition — namespace pozwala filtrować "Marka z grupy XYZ" gdy multiple shopów ma `Marka` w różnych grupach.
|
|
109
|
+
"""
|
|
110
|
+
namespace: String
|
|
111
|
+
|
|
102
112
|
"""Text search (for TEXT, TEXTAREA)"""
|
|
103
113
|
textSearch: String
|
|
104
114
|
|
|
@@ -248,34 +258,51 @@ enum AttributeType {
|
|
|
248
258
|
"""Available filters for product listing"""
|
|
249
259
|
type AvailableFilters {
|
|
250
260
|
"""
|
|
251
|
-
|
|
261
|
+
Liczba currently applied filters w `currentFilters` (Storefront UI renderuje "Filtry (3)" w sidebar header).
|
|
252
262
|
"""
|
|
253
263
|
activeCount: Int!
|
|
254
264
|
|
|
255
265
|
"""Filterable attributes with values"""
|
|
256
266
|
attributes: [AttributeDefinition!]!
|
|
257
267
|
|
|
258
|
-
"""
|
|
259
|
-
|
|
268
|
+
"""
|
|
269
|
+
Boolean facet count: liczba produktów w current context spełniających `Product.isAvailable` (przynajmniej jeden aktywny wariant z untracked inventory LUB available>0). Exclude-self: jeśli `available: true` jest w `currentFilters`, count IGNORUJE ten filtr i pokazuje "ile produktów dostępnych w bazowym kontekście" (nie "ile teraz widać"). Spójne semantically z `attributes[].filterValues[].productCount` (per-enum-value facet) i `brands[].productCount` (per-brand facet) — wszystkie używają exclude-self per dimension. Untracked inventory (gift cards, digital, na-zamówienie) zawsze liczy się jako available.
|
|
270
|
+
"""
|
|
271
|
+
availableCount: Int!
|
|
260
272
|
|
|
261
273
|
"""
|
|
262
|
-
|
|
274
|
+
Faza 3 (2026-05-17): canonical Brand entities z product counts w current context. Storefront UI renderuje checkbox list / dropdown z logo per brand. Klik → `filters: [{brand: {slug}}]`. Tylko brandy z >0 produktów w current product set + isActive=true.
|
|
263
275
|
"""
|
|
264
|
-
|
|
276
|
+
brands: [BrandFilterValue!]
|
|
277
|
+
|
|
278
|
+
"""Categories available for filtering"""
|
|
279
|
+
categories: [CategoryFilterOption!]
|
|
265
280
|
|
|
266
281
|
"""Price range for filtering"""
|
|
267
282
|
priceRange: PriceRange
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
Total products w current context (categoryId/collectionId/searchQuery) PRZED zaaplikowaniem faceted filters z `currentFilters`. Spójne z `ProductConnection.totalCount` Relay Connection spec — gdy `currentFilters` jest pusty, `AvailableFilters.totalCount` === `products(filters).totalCount`. Użyj do "Wszystkie produkty (N)" w sidebar header.
|
|
286
|
+
"""
|
|
287
|
+
totalCount: Int!
|
|
268
288
|
}
|
|
269
289
|
|
|
270
290
|
"""Context for available filters query"""
|
|
271
291
|
input AvailableFiltersInput {
|
|
292
|
+
"""
|
|
293
|
+
Czy filtr "dostępne" jest currently zaaplikowany. Mirror `ProductFilter.available`. Backend używa do exclude-self semantyki: gdy `available: true` (lub `false`) tutaj, `AvailableFilters.availableCount` IGNORUJE ten filtr (pokazuje "ile produktów ten facet odsłoni gdy włączysz"), pozostałe facet counts (`attributes[].filterValues[].productCount`, `brands[].productCount`, `categories[].productCount`) APLIKUJĄ go (pokazują "ile produktów w obecnym kontekście + tym facet"). Pomiń (null/undefined) gdy storefront nie aplikuje "dostępne" filter.
|
|
294
|
+
"""
|
|
295
|
+
available: Boolean
|
|
296
|
+
|
|
272
297
|
"""Category ID context (supports comma-separated UUIDs)"""
|
|
273
298
|
categoryId: ID
|
|
274
299
|
|
|
275
300
|
"""Collection ID context (supports comma-separated UUIDs)"""
|
|
276
301
|
collectionId: ID
|
|
277
302
|
|
|
278
|
-
"""
|
|
303
|
+
"""
|
|
304
|
+
Currently applied attribute filters (to update facet counts). Per facet `productCount` używa exclude-self pattern: gdy attrId X jest w currentFilters, facet values X.* są liczone IGNORUJĄC X (pokazują "ile produktów dla X=Y w pozostałym kontekście"). Pozostałe attrIds są aplikowane.
|
|
305
|
+
"""
|
|
279
306
|
currentFilters: [AttributeFilterInput!]
|
|
280
307
|
|
|
281
308
|
"""Search query context"""
|
|
@@ -598,6 +625,52 @@ type BrandColors {
|
|
|
598
625
|
secondary: BrandColorGroup
|
|
599
626
|
}
|
|
600
627
|
|
|
628
|
+
"""Filter products po brand (canonical entity)"""
|
|
629
|
+
input BrandFilter {
|
|
630
|
+
"""Brand ID — exact UUID match"""
|
|
631
|
+
id: ID
|
|
632
|
+
|
|
633
|
+
"""
|
|
634
|
+
Brand slug — URL-friendly identifier. Preferowany over `id` dla storefront refetch (stable po URL share).
|
|
635
|
+
"""
|
|
636
|
+
slug: String
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
"""Brand filter value z product count"""
|
|
640
|
+
type BrandFilterValue {
|
|
641
|
+
"""Brand ID"""
|
|
642
|
+
id: ID!
|
|
643
|
+
|
|
644
|
+
"""Brand logo URL (UI rendering)"""
|
|
645
|
+
logo: String
|
|
646
|
+
|
|
647
|
+
"""Brand name"""
|
|
648
|
+
name: String!
|
|
649
|
+
|
|
650
|
+
"""Liczba produktów z tym brandem w current product set"""
|
|
651
|
+
productCount: Int!
|
|
652
|
+
|
|
653
|
+
"""Brand slug (refetch payload — paste w ProductFilter.brand.slug)"""
|
|
654
|
+
slug: String!
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
"""
|
|
658
|
+
Brand summary — minimalna projekcja dla storefront catalog + filter rendering
|
|
659
|
+
"""
|
|
660
|
+
type BrandSummary {
|
|
661
|
+
"""Brand unique identifier"""
|
|
662
|
+
id: ID!
|
|
663
|
+
|
|
664
|
+
"""Brand logo URL (CDN-served, storefront-rendered)"""
|
|
665
|
+
logo: String
|
|
666
|
+
|
|
667
|
+
"""Brand display name (e.g. "Funko", "Nintendo")"""
|
|
668
|
+
name: String!
|
|
669
|
+
|
|
670
|
+
"""URL-friendly slug — używane dla future /marka/[slug] landing pages"""
|
|
671
|
+
slug: String!
|
|
672
|
+
}
|
|
673
|
+
|
|
601
674
|
"""Business hours for a day"""
|
|
602
675
|
type BusinessHour {
|
|
603
676
|
"""Closing time (HH:MM format)"""
|
|
@@ -1904,6 +1977,9 @@ type Customer implements Node {
|
|
|
1904
1977
|
"""Saved addresses (Relay Connection)"""
|
|
1905
1978
|
addresses(after: String, before: String, first: Int, last: Int): MailingAddressConnection!
|
|
1906
1979
|
|
|
1980
|
+
"""Customer custom field values (post-Opcja A unified custom fields)."""
|
|
1981
|
+
attributes(namespace: String): [EntityAttributeField!]!
|
|
1982
|
+
|
|
1907
1983
|
"""Company name (populated for COMPANY type)"""
|
|
1908
1984
|
companyName: String
|
|
1909
1985
|
|
|
@@ -1937,16 +2013,6 @@ type Customer implements Node {
|
|
|
1937
2013
|
"""Last name"""
|
|
1938
2014
|
lastName: String
|
|
1939
2015
|
|
|
1940
|
-
"""
|
|
1941
|
-
Lista meta properties (Relay Connection) — opcjonalnie scoped do namespace
|
|
1942
|
-
"""
|
|
1943
|
-
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
1944
|
-
|
|
1945
|
-
"""
|
|
1946
|
-
Pojedyncze meta property po (namespace, key) — dla zalogowanego klienta zwraca także private
|
|
1947
|
-
"""
|
|
1948
|
-
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
1949
|
-
|
|
1950
2016
|
"""Total orders count (UnsignedInt64 — BigInt-safe)"""
|
|
1951
2017
|
orderCount: UnsignedInt64!
|
|
1952
2018
|
|
|
@@ -2347,6 +2413,39 @@ enum EmailMarketingState {
|
|
|
2347
2413
|
UNSUBSCRIBED
|
|
2348
2414
|
}
|
|
2349
2415
|
|
|
2416
|
+
"""Attribute assignment per entity (polymorphic owner)"""
|
|
2417
|
+
type EntityAttributeField {
|
|
2418
|
+
"""AttributeDefinition ID"""
|
|
2419
|
+
definitionId: ID!
|
|
2420
|
+
|
|
2421
|
+
"""Attribute handle (URL-friendly identifier)"""
|
|
2422
|
+
handle: String!
|
|
2423
|
+
|
|
2424
|
+
"""EntityAttribute row ID"""
|
|
2425
|
+
id: ID!
|
|
2426
|
+
|
|
2427
|
+
"""
|
|
2428
|
+
Per-entity visibility override (NULL = use AttributeDefinition.isVisible).
|
|
2429
|
+
"""
|
|
2430
|
+
isVisibleOverride: Boolean
|
|
2431
|
+
|
|
2432
|
+
"""Attribute display name"""
|
|
2433
|
+
name: String!
|
|
2434
|
+
|
|
2435
|
+
"""
|
|
2436
|
+
Optional grouping namespace (e.g. "inventory", "marketing"). NULL = ungrouped.
|
|
2437
|
+
"""
|
|
2438
|
+
namespace: String
|
|
2439
|
+
|
|
2440
|
+
"""Attribute data type"""
|
|
2441
|
+
type: AttributeType!
|
|
2442
|
+
|
|
2443
|
+
"""
|
|
2444
|
+
Value serialized as JSON string. Storefront-dev parses according to `type` (TEXT/NUMBER/BOOLEAN/JSON/etc.). Null values serialize as JSON `null`.
|
|
2445
|
+
"""
|
|
2446
|
+
value: String!
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2350
2449
|
"""Exchange rate between currencies"""
|
|
2351
2450
|
type ExchangeRate {
|
|
2352
2451
|
"""Source currency code"""
|
|
@@ -3269,119 +3368,6 @@ enum MenuItemType {
|
|
|
3269
3368
|
SEARCH
|
|
3270
3369
|
}
|
|
3271
3370
|
|
|
3272
|
-
"""
|
|
3273
|
-
Payload `customerMetaPropertiesSet` — bulk upsert własnych meta properties klienta
|
|
3274
|
-
"""
|
|
3275
|
-
type MetaPropertiesSetPayload {
|
|
3276
|
-
"""Upserted meta properties (puste gdy userErrors)"""
|
|
3277
|
-
metaProperties: [MetaProperty!]!
|
|
3278
|
-
|
|
3279
|
-
"""User errors (code z `MetaPropertyErrorCode` enum)"""
|
|
3280
|
-
userErrors: [UserError!]!
|
|
3281
|
-
}
|
|
3282
|
-
|
|
3283
|
-
"""Pojedyncze pole dodatkowe (meta property) na encji commerce"""
|
|
3284
|
-
type MetaProperty implements Node {
|
|
3285
|
-
"""Created at"""
|
|
3286
|
-
createdAt: DateTime!
|
|
3287
|
-
|
|
3288
|
-
"""Unique identifier"""
|
|
3289
|
-
id: ID!
|
|
3290
|
-
|
|
3291
|
-
"""
|
|
3292
|
-
true = readable tylko przez authenticated context (admin/customer self). false (default) = readable również przez anonymous Storefront API.
|
|
3293
|
-
"""
|
|
3294
|
-
isPrivate: Boolean!
|
|
3295
|
-
|
|
3296
|
-
"""
|
|
3297
|
-
Key w obrębie namespace (3-64 chars). Unique per (ownerType, ownerId, namespace).
|
|
3298
|
-
"""
|
|
3299
|
-
key: String!
|
|
3300
|
-
|
|
3301
|
-
"""
|
|
3302
|
-
Namespace (3-64 chars). Zapobiega kolizji kluczy między aplikacjami. Reserved prefix: `doswiftly:` (platform).
|
|
3303
|
-
"""
|
|
3304
|
-
namespace: String!
|
|
3305
|
-
|
|
3306
|
-
"""Typ wartości — informuje klienta jak parse value."""
|
|
3307
|
-
type: MetaPropertyValueType!
|
|
3308
|
-
|
|
3309
|
-
"""Last updated at"""
|
|
3310
|
-
updatedAt: DateTime!
|
|
3311
|
-
|
|
3312
|
-
"""
|
|
3313
|
-
Wartość — zawsze String. Klient parsuje zgodnie z polem `type` (np. INTEGER → parseInt, JSON → JSON.parse).
|
|
3314
|
-
"""
|
|
3315
|
-
value: String!
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3318
|
-
"""Paginated meta property list (Relay Connection)"""
|
|
3319
|
-
type MetaPropertyConnection {
|
|
3320
|
-
"""Edges (cursor + node pairs)"""
|
|
3321
|
-
edges: [MetaPropertyEdge!]!
|
|
3322
|
-
|
|
3323
|
-
"""Nodes (shortcut bez cursor)"""
|
|
3324
|
-
nodes: [MetaProperty!]!
|
|
3325
|
-
|
|
3326
|
-
"""Page info"""
|
|
3327
|
-
pageInfo: PageInfo!
|
|
3328
|
-
|
|
3329
|
-
"""Total count (max po wszystkich pages)"""
|
|
3330
|
-
totalCount: Int!
|
|
3331
|
-
}
|
|
3332
|
-
|
|
3333
|
-
"""Payload `customerMetaPropertyDelete`"""
|
|
3334
|
-
type MetaPropertyDeletePayload {
|
|
3335
|
-
"""ID usuniętego meta property (null gdy userErrors)"""
|
|
3336
|
-
deletedId: ID
|
|
3337
|
-
|
|
3338
|
-
"""User errors (code z `MetaPropertyErrorCode` enum)"""
|
|
3339
|
-
userErrors: [UserError!]!
|
|
3340
|
-
}
|
|
3341
|
-
|
|
3342
|
-
"""Meta property edge (Relay pagination)"""
|
|
3343
|
-
type MetaPropertyEdge {
|
|
3344
|
-
"""Cursor for pagination"""
|
|
3345
|
-
cursor: String!
|
|
3346
|
-
|
|
3347
|
-
"""Meta property node"""
|
|
3348
|
-
node: MetaProperty!
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
"""Input dla pojedynczego meta property w bulk set"""
|
|
3352
|
-
input MetaPropertyInput {
|
|
3353
|
-
"""Visibility — true = admin/customer-only, false = storefront-readable"""
|
|
3354
|
-
isPrivate: Boolean = false
|
|
3355
|
-
|
|
3356
|
-
"""Key (3-64 chars)"""
|
|
3357
|
-
key: String!
|
|
3358
|
-
|
|
3359
|
-
"""Namespace (3-64 chars, NOT starting with `doswiftly:`)"""
|
|
3360
|
-
namespace: String!
|
|
3361
|
-
|
|
3362
|
-
"""Typ wartości (driver walidacji backend-side)"""
|
|
3363
|
-
type: MetaPropertyValueType!
|
|
3364
|
-
|
|
3365
|
-
"""Wartość jako String (parse zgodnie z type)"""
|
|
3366
|
-
value: String!
|
|
3367
|
-
}
|
|
3368
|
-
|
|
3369
|
-
"""
|
|
3370
|
-
Typy wartości — STRING (max 255 chars), TEXT (no cap), INTEGER, DECIMAL, BOOLEAN ("true"/"false"), JSON (stringified), DATE (YYYY-MM-DD), DATE_TIME (ISO 8601), URL (http(s)://...), COLOR (#RRGGBB / #RRGGBBAA hex).
|
|
3371
|
-
"""
|
|
3372
|
-
enum MetaPropertyValueType {
|
|
3373
|
-
BOOLEAN
|
|
3374
|
-
COLOR
|
|
3375
|
-
DATE
|
|
3376
|
-
DATE_TIME
|
|
3377
|
-
DECIMAL
|
|
3378
|
-
INTEGER
|
|
3379
|
-
JSON
|
|
3380
|
-
STRING
|
|
3381
|
-
TEXT
|
|
3382
|
-
URL
|
|
3383
|
-
}
|
|
3384
|
-
|
|
3385
3371
|
"""Monetary value with currency"""
|
|
3386
3372
|
type Money {
|
|
3387
3373
|
"""Decimal money amount"""
|
|
@@ -3463,12 +3449,6 @@ type Mutation {
|
|
|
3463
3449
|
"""Logout customer (clears auth cookie)"""
|
|
3464
3450
|
customerLogout: CustomerLogoutPayload!
|
|
3465
3451
|
|
|
3466
|
-
"""Bulk upsert własnych meta properties klienta (Bearer token wymagany)"""
|
|
3467
|
-
customerMetaPropertiesSet(properties: [MetaPropertyInput!]!): MetaPropertiesSetPayload!
|
|
3468
|
-
|
|
3469
|
-
"""Usuwa pojedyncze meta property klienta po (namespace, key)"""
|
|
3470
|
-
customerMetaPropertyDelete(key: String!, namespace: String!): MetaPropertyDeletePayload!
|
|
3471
|
-
|
|
3472
3452
|
"""Refresh access token"""
|
|
3473
3453
|
customerRefreshToken: CustomerRefreshTokenPayload!
|
|
3474
3454
|
|
|
@@ -3576,6 +3556,9 @@ type Order implements Node {
|
|
|
3576
3556
|
"""
|
|
3577
3557
|
accessToken: String!
|
|
3578
3558
|
|
|
3559
|
+
"""Order custom field values (post-Opcja A unified custom fields)."""
|
|
3560
|
+
attributes(namespace: String): [EntityAttributeField!]!
|
|
3561
|
+
|
|
3579
3562
|
"""Czy storefront może zainicjować płatność dla tego order"""
|
|
3580
3563
|
canCreatePayment: Boolean!
|
|
3581
3564
|
|
|
@@ -3602,12 +3585,6 @@ type Order implements Node {
|
|
|
3602
3585
|
"""
|
|
3603
3586
|
lineItems(after: String, first: Int = 10): OrderLineItemConnection!
|
|
3604
3587
|
|
|
3605
|
-
"""Lista meta properties (Relay Connection)"""
|
|
3606
|
-
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
3607
|
-
|
|
3608
|
-
"""Pojedyncze meta property po (namespace, key)"""
|
|
3609
|
-
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
3610
|
-
|
|
3611
3588
|
"""Order number (human-readable)"""
|
|
3612
3589
|
orderNumber: String!
|
|
3613
3590
|
|
|
@@ -4014,6 +3991,11 @@ type Product implements Node {
|
|
|
4014
3991
|
"""Average rating (1-5)"""
|
|
4015
3992
|
averageRating: Float
|
|
4016
3993
|
|
|
3994
|
+
"""
|
|
3995
|
+
Canonical brand entity (Faza 3, 2026-05-17). Resolved via DataLoader (N+1 safe dla list queries). Null gdy product nie ma przypisanego brand_id.
|
|
3996
|
+
"""
|
|
3997
|
+
brand: BrandSummary
|
|
3998
|
+
|
|
4017
3999
|
"""
|
|
4018
4000
|
Wszystkie kategorie do których produkt należy (M2M przez ProductCategory junction). Posortowane po junction.sortOrder ASC. Empty list gdy produkt nie jest w żadnej kategorii.
|
|
4019
4001
|
"""
|
|
@@ -4056,12 +4038,6 @@ type Product implements Node {
|
|
|
4056
4038
|
"""
|
|
4057
4039
|
isPurchasable: Boolean!
|
|
4058
4040
|
|
|
4059
|
-
"""Lista meta properties — Storefront API filtruje isPrivate=false"""
|
|
4060
|
-
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
4061
|
-
|
|
4062
|
-
"""Pojedyncze meta property — Storefront API filtruje isPrivate=false"""
|
|
4063
|
-
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
4064
|
-
|
|
4065
4041
|
"""
|
|
4066
4042
|
Per-product option definitions (Color, Size, …) with their available values. Use these to build a variant picker without aggregating `selectedOptions` manually.
|
|
4067
4043
|
"""
|
|
@@ -4109,7 +4085,9 @@ type Product implements Node {
|
|
|
4109
4085
|
"""Product variants (Relay Connection)"""
|
|
4110
4086
|
variants(after: String, before: String, first: Float, last: Float): ProductVariantConnection!
|
|
4111
4087
|
|
|
4112
|
-
"""
|
|
4088
|
+
"""
|
|
4089
|
+
Legacy vendor/brand name (free-text). Preferred: `brand` field (canonical Brand entity, Faza 3).
|
|
4090
|
+
"""
|
|
4113
4091
|
vendor: String
|
|
4114
4092
|
|
|
4115
4093
|
"""Catalog visibility (Faza 1.5) — PUBLIC | HIDDEN | BUNDLE_ONLY"""
|
|
@@ -4245,13 +4223,18 @@ type ProductEdge {
|
|
|
4245
4223
|
"""Single product filter"""
|
|
4246
4224
|
input ProductFilter {
|
|
4247
4225
|
"""
|
|
4248
|
-
DoSwiftly: dynamic attribute filters (configurator system).
|
|
4226
|
+
DoSwiftly: dynamic attribute filters (configurator + custom fields system). Optional `namespace` w AttributeFilterInput dyskryminuje per grupa atrybutów.
|
|
4249
4227
|
"""
|
|
4250
4228
|
attributes: [AttributeFilterInput!]
|
|
4251
4229
|
|
|
4252
4230
|
"""Filter by availability for sale"""
|
|
4253
4231
|
available: Boolean
|
|
4254
4232
|
|
|
4233
|
+
"""
|
|
4234
|
+
Faza 3 (2026-05-17): filter by canonical Brand entity. Wybierz po `slug` (URL-stable) lub `id` (raw UUID). Multiple `brand` entries w `filters[]` array = OR semantics. Vendor (legacy free-text) wciąż wspierany via `productVendor`.
|
|
4235
|
+
"""
|
|
4236
|
+
brand: BrandFilter
|
|
4237
|
+
|
|
4255
4238
|
"""Filter by category"""
|
|
4256
4239
|
category: CategoryFilter
|
|
4257
4240
|
|
|
@@ -4437,6 +4420,9 @@ enum ProductTypeEnum {
|
|
|
4437
4420
|
|
|
4438
4421
|
"""Product variant - purchasable unit"""
|
|
4439
4422
|
type ProductVariant {
|
|
4423
|
+
"""Variant custom field values (post-Opcja A unified custom fields)."""
|
|
4424
|
+
attributes(namespace: String): [EntityAttributeField!]!
|
|
4425
|
+
|
|
4440
4426
|
"""
|
|
4441
4427
|
Available stock (computed: stock - reserved). Null when track is disabled.
|
|
4442
4428
|
"""
|
|
@@ -4460,12 +4446,6 @@ type ProductVariant {
|
|
|
4460
4446
|
"""Whether variant is available for purchase"""
|
|
4461
4447
|
isAvailable: Boolean!
|
|
4462
4448
|
|
|
4463
|
-
"""Lista meta properties — Storefront API filtruje isPrivate=false"""
|
|
4464
|
-
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
4465
|
-
|
|
4466
|
-
"""Pojedyncze meta property — Storefront API filtruje isPrivate=false"""
|
|
4467
|
-
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
4468
|
-
|
|
4469
4449
|
"""Variant price (Money). Default field — industry-standard schema."""
|
|
4470
4450
|
price: Money!
|
|
4471
4451
|
|