@doswiftly/storefront-operations 6.0.0 → 7.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/CHANGELOG.md +266 -0
- package/README.md +12 -9
- package/fragments.graphql +5 -0
- package/package.json +1 -1
- package/schema.graphql +297 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,271 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 227629d: # Storefront GraphQL Operations 7.0 — Breaking changes + nowe rozszerzenia API
|
|
8
|
+
|
|
9
|
+
Major bump wprowadza zmiany breaking w schemie storefront GraphQL plus 3 znaczące nowe funkcjonalności: per-address tax info dla B2B, generic Meta Properties (extensible custom fields), oraz strukturalne userErrors[] z typed codes dla wszystkich mutacji.
|
|
10
|
+
|
|
11
|
+
## Breaking changes
|
|
12
|
+
|
|
13
|
+
### 1. `Product.category` (free-text) → `Product.categories` + `Product.primaryCategory` (structured)
|
|
14
|
+
|
|
15
|
+
Wcześniej `Product.category: String` zwracał free-text label — tekst nie był powiązany z entity Category. Klienci nie mogli pobrać slug/name kategorii ani zbudować breadcrumb. Po 7.0:
|
|
16
|
+
|
|
17
|
+
```graphql
|
|
18
|
+
# PRZED (6.x):
|
|
19
|
+
product { category } # → "Pop Vinyl" (free-text string, brak slug/parent)
|
|
20
|
+
|
|
21
|
+
# PO (7.0):
|
|
22
|
+
product {
|
|
23
|
+
categories { id slug name parent { slug } } # M2M lista (junction table)
|
|
24
|
+
primaryCategory { slug name } # categories[0] dla breadcrumb
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Migration**:
|
|
29
|
+
- Klient renderujący breadcrumb po category name: zamień `product.category` na `product.primaryCategory.name`.
|
|
30
|
+
- Klient filtrujący kategorie z `Product.category` jako string: użyj `product.categories[]` array.
|
|
31
|
+
- Filter `productCategory` w `ProductFilter` usunięty — używaj `category: { id }` (junction lookup, działało wcześniej).
|
|
32
|
+
|
|
33
|
+
### 2. `categories` query — `CategoryTree` → `CategoryConnection` (Relay)
|
|
34
|
+
|
|
35
|
+
Wcześniej `categories` zwracał outlier shape `{ roots, totalCount }` vs reszta schemy używała Relay Connection. Klienci próbujący `categories { nodes }` dostawali GraphQL error. Po 7.0:
|
|
36
|
+
|
|
37
|
+
```graphql
|
|
38
|
+
# PRZED (6.x):
|
|
39
|
+
categories(first: 20, rootsOnly: true) {
|
|
40
|
+
roots { id name children { id name } }
|
|
41
|
+
totalCount
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# PO (7.0):
|
|
45
|
+
categories(first: 20, rootsOnly: true) {
|
|
46
|
+
nodes { id name children { id name } }
|
|
47
|
+
edges { cursor node { id } }
|
|
48
|
+
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
|
|
49
|
+
totalCount
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Migration**:
|
|
54
|
+
- Zamień `categories.roots` na `categories.nodes`.
|
|
55
|
+
- Tree shape rebuild po stronie klienta — używaj `Category.parent` / `Category.children` field resolvers (DataLoader N+1 safe). Filtr `rootsOnly: true` zwraca tylko top-level (parent IS NULL).
|
|
56
|
+
- Pełen Relay args: `first/after/last/before` zamiast tylko `first/after`.
|
|
57
|
+
|
|
58
|
+
### 3. `Customer.defaultAddress` — czytelny stan (poprzednio zawsze null)
|
|
59
|
+
|
|
60
|
+
Wcześniej `Customer.defaultAddress` zwracał `null` mimo że adres miał `isDefault: true` w `addresses`. Niespójność łamała storefront breadcrumb i powodowała duplikaty w formularzu dostawy. Po 7.0 zwraca poprawnie wartość — single source of truth z `addresses[].isDefaultShipping` flag.
|
|
61
|
+
|
|
62
|
+
**Migration**: brak — klient po prostu zacznie dostawać poprawne wartości.
|
|
63
|
+
|
|
64
|
+
### 4. Apollo response — bez `extensions.stacktrace` w produkcji
|
|
65
|
+
|
|
66
|
+
Wcześniej w środowisku produkcyjnym GraphQL response zawierał stacktrace z absolutnymi ścieżkami systemu plików backendu. Po 7.0 production response strippuje wszystko poza `code`, `message`, `locations`, `path` + banner `extensions.environment`. Stacktrace dostępny **tylko** w dev/staging dla DX.
|
|
67
|
+
|
|
68
|
+
**Migration**: brak — klient produkcyjny nie powinien był polegać na stacktrace.
|
|
69
|
+
|
|
70
|
+
### 5. Validation errors → `userErrors[]` z typed codes (tech debt resolution)
|
|
71
|
+
|
|
72
|
+
Wcześniej walidacja DTO leciała jako `body.errors[].extensions.code = "INTERNAL_SERVER_ERROR"` envelope (wyglądało jak crash). Po 7.0 mutations zwracają strukturalne `userErrors[]` z explicit codes per validation rule:
|
|
73
|
+
|
|
74
|
+
```graphql
|
|
75
|
+
# Invalid NIP, password too short, malformed email — wszystkie zwracają payload
|
|
76
|
+
mutation {
|
|
77
|
+
customerUpdate(customer: { taxId: "1234567890" }) {
|
|
78
|
+
customer {
|
|
79
|
+
id
|
|
80
|
+
}
|
|
81
|
+
userErrors {
|
|
82
|
+
field
|
|
83
|
+
code
|
|
84
|
+
message
|
|
85
|
+
}
|
|
86
|
+
# → [{ field: ["taxId"], code: "INVALID_TAX_ID_CHECKSUM", message: "..." }]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Typed codes** dla 25 walidatorów: `INVALID_TAX_ID_CHECKSUM`, `INVALID_VAT_NUMBER`, `INVALID_REGON`, `INVALID_EMAIL_FORMAT`, `INVALID_FORMAT`, `INVALID_PHONE_FORMAT`, `INVALID_UUID`, `INVALID_URL`, `INVALID_DATE`, `INVALID_ENUM_VALUE`, `INVALID_TYPE`, `INVALID_COUNTRY_CODE`, `INVALID_POSTAL_CODE`, `TOO_SHORT`, `TOO_LONG`, `TOO_MANY`, `TOO_FEW`, `OUT_OF_RANGE`, `REQUIRED`, `INVALID_NESTED`, `VALIDATION_ERROR`, etc.
|
|
92
|
+
|
|
93
|
+
**Migration**: klient widzący poprzednio `body.errors[].extensions.code = "INTERNAL_SERVER_ERROR"` — sprawdź `data.<mutation>.userErrors[]` zamiast envelope.
|
|
94
|
+
|
|
95
|
+
## New features
|
|
96
|
+
|
|
97
|
+
### 6. `MailingAddress.taxId` + `vatNumber` — per-address B2B tax info
|
|
98
|
+
|
|
99
|
+
Adres ma teraz opcjonalne `taxId` (polski NIP, 10 cyfr z checksumą) + `vatNumber` (VAT UE, np. PL5260250274). Use case: B2B z różnymi danymi firmowymi per adres dostawy/billing (faktura na firmę matkę, dostawa na oddział z osobnym NIP-em).
|
|
100
|
+
|
|
101
|
+
```graphql
|
|
102
|
+
mutation {
|
|
103
|
+
customerAddAddress(
|
|
104
|
+
address: {
|
|
105
|
+
streetLine1: "ul. Marszałkowska 100"
|
|
106
|
+
city: "Warszawa"
|
|
107
|
+
postalCode: "00-001"
|
|
108
|
+
country: PL
|
|
109
|
+
company: "GameGoods Sp. z o.o."
|
|
110
|
+
taxId: "5260250274"
|
|
111
|
+
vatNumber: "PL5260250274"
|
|
112
|
+
}
|
|
113
|
+
) {
|
|
114
|
+
address {
|
|
115
|
+
taxId
|
|
116
|
+
vatNumber
|
|
117
|
+
}
|
|
118
|
+
userErrors {
|
|
119
|
+
code
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Walidacja format/checksum aktywna — invalid NIP zwraca `userErrors` z `INVALID_TAX_ID_CHECKSUM`.
|
|
126
|
+
|
|
127
|
+
### 7. Meta Properties — extensible custom fields per encji
|
|
128
|
+
|
|
129
|
+
Generic mechanizm rozszerzania encji (Customer/Product/ProductVariant/Order) o storefront-specific dane bez wymogu zmian schematu po stronie platformy. Per-customer birthday, per-product warranty_years, per-order gift_message — wszystko jako typed key-value entries w polymorphic table.
|
|
130
|
+
|
|
131
|
+
```graphql
|
|
132
|
+
# Customer self-edit (Bearer token klienta)
|
|
133
|
+
mutation {
|
|
134
|
+
customerMetaPropertiesSet(
|
|
135
|
+
properties: [
|
|
136
|
+
{
|
|
137
|
+
namespace: "storefront"
|
|
138
|
+
key: "birthday"
|
|
139
|
+
value: "1990-05-15"
|
|
140
|
+
type: DATE
|
|
141
|
+
}
|
|
142
|
+
{
|
|
143
|
+
namespace: "storefront"
|
|
144
|
+
key: "favorite_color"
|
|
145
|
+
value: "#FF6600"
|
|
146
|
+
type: COLOR
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
) {
|
|
150
|
+
metaProperties {
|
|
151
|
+
id
|
|
152
|
+
namespace
|
|
153
|
+
key
|
|
154
|
+
value
|
|
155
|
+
type
|
|
156
|
+
}
|
|
157
|
+
userErrors {
|
|
158
|
+
code
|
|
159
|
+
message
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Read na każdej encji
|
|
165
|
+
query {
|
|
166
|
+
customer {
|
|
167
|
+
metaProperty(namespace: "storefront", key: "birthday") {
|
|
168
|
+
value
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
product(id: "...") {
|
|
172
|
+
metaProperties(first: 10, namespace: "warranty") {
|
|
173
|
+
nodes {
|
|
174
|
+
key
|
|
175
|
+
value
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
10 typed values: `STRING/TEXT/INTEGER/DECIMAL/BOOLEAN/JSON/DATE/DATE_TIME/URL/COLOR`. Visibility flag `isPrivate` (storefront API filtruje `isPrivate=true` na public encjach jak Product/ProductVariant). Reserved namespace prefix `doswiftly:` dla platform fields.
|
|
183
|
+
|
|
184
|
+
**Faza 1 MVP scope**: Customer self-edit only (`customerMetaPropertiesSet` / `customerMetaPropertyDelete` z auth Bearer token klienta). Cross-resource writes (Product/Order/Variant) wymagają Admin layer (Faza 2).
|
|
185
|
+
|
|
186
|
+
### 8. NIP/REGON sanity guard — odrzucone all-zeros / repeated-digit patterns
|
|
187
|
+
|
|
188
|
+
Walidator NIP wcześniej akceptował `'0000000000'` (suma kontrolna 0=0). Po 7.0 odrzucamy wszystkie repeated-digit patterns (`'0000000000'`, `'1111111111'`, ..., `'9999999999'`) — to oczywiste fake test data / NULL placeholders, żaden urząd ich nie wystawia. Analogicznie REGON 9-digit i 14-digit.
|
|
189
|
+
|
|
190
|
+
### 9. Empty string clear-value semantyka (wszystkie nullable input fields)
|
|
191
|
+
|
|
192
|
+
Customer kasujący wartość w UI inputie (np. `<input value="" />`) wysyła `phone: ""` zamiast `null`. Po 7.0 backend traktuje empty string jako "wyczyść pole" (DB → null) — natywne form library behavior, klient nie wymaga specjalnej logiki rozróżniającej `null` vs `""`.
|
|
193
|
+
|
|
194
|
+
Pokrycie: wszystkie nullable string/enum fields w `CustomerCreateInput`, `CustomerUpdateInput`, `MailingAddressInput`.
|
|
195
|
+
|
|
196
|
+
## Notes
|
|
197
|
+
- 7.0 jest cumulative — kumulacja zmian z 6.x patches (B2B fields w `customerUpdate`, marketing consent flow) + breaking changes opisane wyżej.
|
|
198
|
+
- Wszystkie zmiany pokryte test coverage — backend integration tests gwarantują regression-free contract.
|
|
199
|
+
|
|
200
|
+
## 6.1.0
|
|
201
|
+
|
|
202
|
+
### Minor Changes
|
|
203
|
+
|
|
204
|
+
- fbc6a94: Rozszerzono `CustomerUpdateInput` w GraphQL Storefront API o pełen kontrakt B2B — storefront developer może teraz zaktualizować dane firmowe klienta (nazwa firmy, NIP, VAT UE, REGON) oraz typ klienta (osoba fizyczna / firma) bezpośrednio przez mutację `customerUpdate`. Wcześniej te pola były dostępne wyłącznie z poziomu panelu administratora.
|
|
205
|
+
|
|
206
|
+
**Nowe pola Input**:
|
|
207
|
+
- `customerType: CustomerType` — `INDIVIDUAL` (B2C) lub `COMPANY` (B2B). Opcjonalne — gdy pominięte, backend dokona inteligentnej inferencji (patrz „Strict hybrid" niżej).
|
|
208
|
+
- `companyName: String` — nazwa firmy (wymagana dla `COMPANY`).
|
|
209
|
+
- `taxId: String` — polski NIP (10 cyfr z sumą kontrolną).
|
|
210
|
+
- `vatNumber: String` — numer VAT UE (np. `PL5260250274`, `DE123456789`).
|
|
211
|
+
- `regon: String` — polski REGON (9 lub 14 cyfr z sumą kontrolną).
|
|
212
|
+
|
|
213
|
+
**Nowe pola czytelne na typie `Customer`** (dostępne automatycznie przez fragment `Customer`):
|
|
214
|
+
- `customerType`, `companyName`, `taxId`, `vatNumber`, `regon`.
|
|
215
|
+
|
|
216
|
+
**Strict hybrid — kontrakt aktualizacji typu klienta**:
|
|
217
|
+
1. Jeśli `customerType` jest w payloadzie, wygrywa explicit (nawet jeśli inne pola sugerowałyby co innego).
|
|
218
|
+
2. Jeśli `customerType` jest pominięty, backend dokonuje implicit upgrade `INDIVIDUAL → COMPANY` tylko gdy podano niepuste pole firmowe. Implicit downgrade nigdy się nie zdarza — wymaga jawnego `customerType: INDIVIDUAL`.
|
|
219
|
+
3. Każde efektywne `COMPANY` (jawne lub przez inferencję) wymaga niepustej `companyName` — w przeciwnym razie mutacja zwraca `userErrors` z kodem `CUSTOMER_UPDATE_FAILED`.
|
|
220
|
+
4. `undefined` w payloadzie = brak zmiany pola; `null` = świadome czyszczenie wartości.
|
|
221
|
+
|
|
222
|
+
**Walidacja format**: NIP, VAT UE i REGON walidowane przez wspólny dekorator (regex + algorytm sumy kontrolnej). Niepoprawny format → błąd walidacji input'u GraphQL.
|
|
223
|
+
|
|
224
|
+
**Concurrency safety**: `customerUpdate` używa optimistic locking — równoczesne aktualizacje tego samego klienta (np. profil w wielu zakładkach) zwracają `userErrors` z message o konflikcie wersji zamiast cichego nadpisania.
|
|
225
|
+
|
|
226
|
+
**Use cases dla storefront-developera**:
|
|
227
|
+
- Strona „moje dane" w sklepie — pełen formularz B2C/B2B z radio pickerem typu klienta.
|
|
228
|
+
- Checkout pre-fill — możliwość uzupełnienia danych do faktury bez wchodzenia do panelu sklepu.
|
|
229
|
+
- Rejestracja firm / sole-trader scenarios — implicit upgrade gdy klient wpisze NIP w polu opcjonalnym.
|
|
230
|
+
|
|
231
|
+
**Test coverage**: 8 service-level scenariuszy (każda z 4 reguł strict hybrid + concurrency conflict + audit trail + EventEmitter) + 3 GraphQL surface scenariusze (full B2B payload, error mapping, format validation).
|
|
232
|
+
|
|
233
|
+
- c91b700: Rozszerzono storefront GraphQL API o pełną obsługę zgód marketingowych — storefront-developer może teraz zbudować checkbox marketingu przy rejestracji, toggle preferencji w panelu klienta, oraz widget „zapisz się do newslettera" w stopce sklepu. Wszystkie 3 use case'y działają end-to-end (z double opt-in dla anonimowego widget'u i audit trail dla zgodności z RODO).
|
|
234
|
+
|
|
235
|
+
**Nowe pola Input (signup)** — `CustomerCreateInput`:
|
|
236
|
+
- `acceptsMarketing: Boolean` — checkbox marketingu przy `customerSignup`. `true` → state SUBSCRIBED bezpośrednio (signup = adres potwierdzony). `false`/`null` → bez zmian (NOT_SUBSCRIBED).
|
|
237
|
+
- `marketingOptInLevel: MarketingOptInLevel` (`SINGLE_OPT_IN | CONFIRMED_OPT_IN | UNKNOWN`) — opcjonalny override. Ustaw `CONFIRMED_OPT_IN` aby wymusić double opt-in (state PENDING + automatyczny email z linkiem potwierdzającym).
|
|
238
|
+
|
|
239
|
+
**Nowe pole Input (panel klienta)** — `CustomerUpdateInput`:
|
|
240
|
+
- `acceptsMarketing: Boolean` — toggle dla zalogowanego klienta. `true` → SUBSCRIBED, `false` → UNSUBSCRIBED. Działa tylko z access tokenem (auth-required mutation).
|
|
241
|
+
|
|
242
|
+
**Nowe mutacje (newsletter widget — guest, bez auth)**:
|
|
243
|
+
- `customerSubscribeToMarketing(input: { email, marketingOptInLevel? }): SubscribeToMarketingPayload` — anonimowy opt-in. Backend zawsze stosuje double opt-in (state PENDING → email z linkiem → klik → SUBSCRIBED) niezależnie od `marketingOptInLevel` w input — bez tego ktoś mógłby zapisywać cudze adresy.
|
|
244
|
+
- `customerUnsubscribeFromMarketing(input: { email }): UnsubscribeFromMarketingPayload` — anonimowy opt-out, idempotent.
|
|
245
|
+
|
|
246
|
+
**Payloady — `accepted: Boolean!` semantyka (anti-enumeration)**: obie mutacje guest zwracają `accepted: true` niezależnie od stanu emaila (registered / nieistniejący / już SUBSCRIBED). Storefront-developer dostaje deterministyczny shape response — atakujący nie może przez to API enumerate'ować adresów ani odróżnić invalid email format od valid (orzeczenie: też swallowed jako `accepted: true`). Stan końcowy zawsze widoczny przez `Customer.emailMarketing` (gdy klient autoryzowany).
|
|
247
|
+
|
|
248
|
+
**Mutacje C są throttlowane per IP** (limit 10/min) i pod botprotection guardem — storefront powinien zintegrować odpowiedni captcha provider. Email-bombing przez wpisywanie cudzego adresu w widget'cie blokowany dodatkowo na poziomie backendu (max 1 email potwierdzenia na adres na dobę).
|
|
249
|
+
|
|
250
|
+
**Strict-hybrid kontrakt update'u**:
|
|
251
|
+
- Brak pola w Input = no-op dla tego pola (zachowanie back-compat dla istniejących storefrontów bez nowych pól).
|
|
252
|
+
- `acceptsMarketing: null` na auth'd customer = explicit no-change (nie unsubscribe — do unsubscribe użyj `false`).
|
|
253
|
+
|
|
254
|
+
**Double opt-in dla widget'u — co dzieje się po kliknięciu „Zapisz"**:
|
|
255
|
+
1. Mutation `customerSubscribeToMarketing` → backend zapisuje stan PENDING + uruchamia wysłanie maila potwierdzającego.
|
|
256
|
+
2. Email idzie z domeny sklepu (per-shop branding — `From: "Sklep <noreply@shop-domain>"`) z CTA buttonem prowadzącym pod URL potwierdzający (token JWT 24h).
|
|
257
|
+
3. Klient klika link → backend transitionuje stan z PENDING na SUBSCRIBED.
|
|
258
|
+
4. Po SUBSCRIBED storefront może wysyłać maile marketingowe (gate consent jest egzekwowane przez backend pre-send).
|
|
259
|
+
|
|
260
|
+
**Read surface — `Customer.emailMarketing: EmailMarketingState!`** (pole już istniejące, dla auth'd customer): storefront odczytuje `NOT_SUBSCRIBED | PENDING | SUBSCRIBED | UNSUBSCRIBED | INVALID | REDACTED` aby zdecydować jak renderować checkbox/toggle w UI (np. PENDING = pokaż „sprawdź email", SUBSCRIBED = checkbox zaznaczony).
|
|
261
|
+
|
|
262
|
+
**Use cases**:
|
|
263
|
+
- Strona `/auth/register` — checkbox „Chcę otrzymywać newsletter" zapisany razem z signupem.
|
|
264
|
+
- Strona `/account/preferences` — toggle marketing on/off dla zalogowanego klienta.
|
|
265
|
+
- Stopka layoutu sklepu — input email + button „Zapisz się" → mutation guest subscribe → automatyczny email z linkiem potwierdzającym.
|
|
266
|
+
|
|
267
|
+
**Suppression list awareness**: jeśli adres trafił wcześniej na suppression list (hard bounce / complaint feedback), kolejna subskrypcja zwraca `accepted: true` ale email potwierdzający nie wychodzi. Klient pozostanie w stanie PENDING — admin sklepu musi ręcznie odblokować adres przez panel administracyjny.
|
|
268
|
+
|
|
3
269
|
## 6.0.0
|
|
4
270
|
|
|
5
271
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ export function ProductList() {
|
|
|
96
96
|
| `Collection` | Collection with its products |
|
|
97
97
|
| `Collections` | All collections |
|
|
98
98
|
| `Category` | Category with hierarchy |
|
|
99
|
-
| `Categories` |
|
|
99
|
+
| `Categories` | Paginowana lista kategorii (Relay Connection) — drzewo budujesz po stronie klienta z `parent`/`children` |
|
|
100
100
|
| `Cart` | Cart contents by ID |
|
|
101
101
|
| `Customer` | Logged-in customer data |
|
|
102
102
|
| `CustomerOrders` | Customer order history |
|
|
@@ -116,23 +116,26 @@ export function ProductList() {
|
|
|
116
116
|
|
|
117
117
|
**Authentication**
|
|
118
118
|
|
|
119
|
-
- `
|
|
119
|
+
- `CustomerSignup` - Register new customer
|
|
120
120
|
- `CustomerLogin` - Login and get token
|
|
121
121
|
- `CustomerLogout` - Logout
|
|
122
|
-
- `
|
|
122
|
+
- `CustomerRefreshToken` - Refresh auth token
|
|
123
|
+
- `CustomerActivate` - Activate account via email link
|
|
124
|
+
- `CustomerMetaPropertiesSet` - Set custom key-value entries on the logged-in customer
|
|
125
|
+
- `CustomerMetaPropertyDelete` - Delete a custom key-value entry from the logged-in customer
|
|
123
126
|
|
|
124
127
|
**Customer Profile**
|
|
125
128
|
|
|
126
129
|
- `CustomerUpdate` - Update profile info
|
|
127
|
-
- `
|
|
128
|
-
- `
|
|
129
|
-
- `
|
|
130
|
-
- `
|
|
130
|
+
- `CustomerAddAddress` - Add new address
|
|
131
|
+
- `CustomerUpdateAddress` - Update address
|
|
132
|
+
- `CustomerRemoveAddress` - Remove address
|
|
133
|
+
- `CustomerSetDefaultAddress` - Set default address
|
|
131
134
|
|
|
132
135
|
**Password**
|
|
133
136
|
|
|
134
|
-
- `
|
|
135
|
-
- `
|
|
137
|
+
- `CustomerRequestPasswordReset` - Request password reset email
|
|
138
|
+
- `CustomerResetPassword` - Reset password with token
|
|
136
139
|
|
|
137
140
|
## Schema
|
|
138
141
|
|
package/fragments.graphql
CHANGED
package/package.json
CHANGED
package/schema.graphql
CHANGED
|
@@ -1182,11 +1182,14 @@ type Category {
|
|
|
1182
1182
|
sortOrder: Int!
|
|
1183
1183
|
}
|
|
1184
1184
|
|
|
1185
|
-
"""Paginated category list"""
|
|
1185
|
+
"""Paginated category list (Relay Connection)"""
|
|
1186
1186
|
type CategoryConnection {
|
|
1187
|
-
"""List of category edges"""
|
|
1187
|
+
"""List of category edges (cursor + node pairs)"""
|
|
1188
1188
|
edges: [CategoryEdge!]!
|
|
1189
1189
|
|
|
1190
|
+
"""List of category nodes (shortcut without cursor)"""
|
|
1191
|
+
nodes: [Category!]!
|
|
1192
|
+
|
|
1190
1193
|
"""Pagination info"""
|
|
1191
1194
|
pageInfo: PageInfo!
|
|
1192
1195
|
|
|
@@ -1230,15 +1233,6 @@ type CategoryFilterOption {
|
|
|
1230
1233
|
slug: String!
|
|
1231
1234
|
}
|
|
1232
1235
|
|
|
1233
|
-
"""Category tree structure"""
|
|
1234
|
-
type CategoryTree {
|
|
1235
|
-
"""Root categories"""
|
|
1236
|
-
roots: [Category!]!
|
|
1237
|
-
|
|
1238
|
-
"""Total categories count"""
|
|
1239
|
-
totalCount: Int!
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
1236
|
"""Checkout object"""
|
|
1243
1237
|
type Checkout {
|
|
1244
1238
|
"""Applied gift cards"""
|
|
@@ -2010,9 +2004,15 @@ type Customer implements Node {
|
|
|
2010
2004
|
"""Saved addresses (Relay Connection)"""
|
|
2011
2005
|
addresses(after: String, before: String, first: Int, last: Int): MailingAddressConnection!
|
|
2012
2006
|
|
|
2007
|
+
"""Company name (populated for COMPANY type)"""
|
|
2008
|
+
companyName: String
|
|
2009
|
+
|
|
2013
2010
|
"""Account creation date"""
|
|
2014
2011
|
createdAt: DateTime!
|
|
2015
2012
|
|
|
2013
|
+
"""Business type discriminator (INDIVIDUAL/COMPANY)"""
|
|
2014
|
+
customerType: CustomerType!
|
|
2015
|
+
|
|
2016
2016
|
"""Default address"""
|
|
2017
2017
|
defaultAddress: MailingAddress
|
|
2018
2018
|
|
|
@@ -2037,6 +2037,16 @@ type Customer implements Node {
|
|
|
2037
2037
|
"""Last name"""
|
|
2038
2038
|
lastName: String
|
|
2039
2039
|
|
|
2040
|
+
"""
|
|
2041
|
+
Lista meta properties (Relay Connection) — opcjonalnie scoped do namespace
|
|
2042
|
+
"""
|
|
2043
|
+
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
2044
|
+
|
|
2045
|
+
"""
|
|
2046
|
+
Pojedyncze meta property po (namespace, key) — dla zalogowanego klienta zwraca także private
|
|
2047
|
+
"""
|
|
2048
|
+
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
2049
|
+
|
|
2040
2050
|
"""Total orders count (UnsignedInt64 — BigInt-safe)"""
|
|
2041
2051
|
orderCount: UnsignedInt64!
|
|
2042
2052
|
|
|
@@ -2046,14 +2056,23 @@ type Customer implements Node {
|
|
|
2046
2056
|
"""Phone number"""
|
|
2047
2057
|
phone: String
|
|
2048
2058
|
|
|
2059
|
+
"""Polish business registry number — REGON"""
|
|
2060
|
+
regon: String
|
|
2061
|
+
|
|
2049
2062
|
"""Customer tags for segmentation (e.g. vip, wholesale, b2b)"""
|
|
2050
2063
|
tags: [String!]!
|
|
2051
2064
|
|
|
2065
|
+
"""Polish tax ID — NIP"""
|
|
2066
|
+
taxId: String
|
|
2067
|
+
|
|
2052
2068
|
"""Total amount spent"""
|
|
2053
2069
|
totalSpent: Money!
|
|
2054
2070
|
|
|
2055
2071
|
"""Last update date"""
|
|
2056
2072
|
updatedAt: DateTime!
|
|
2073
|
+
|
|
2074
|
+
"""EU VAT number"""
|
|
2075
|
+
vatNumber: String
|
|
2057
2076
|
}
|
|
2058
2077
|
|
|
2059
2078
|
"""Customer access token"""
|
|
@@ -2103,6 +2122,11 @@ type CustomerAddAddressPayload {
|
|
|
2103
2122
|
|
|
2104
2123
|
"""Input for customer registration"""
|
|
2105
2124
|
input CustomerCreateInput {
|
|
2125
|
+
"""
|
|
2126
|
+
Opt-in to email marketing checkbox. true → state SUBSCRIBED (single opt-in) unless `marketingOptInLevel: CONFIRMED_OPT_IN` is also set (then PENDING + double opt-in confirmation email). false/null → no consent change.
|
|
2127
|
+
"""
|
|
2128
|
+
acceptsMarketing: Boolean
|
|
2129
|
+
|
|
2106
2130
|
"""Email address"""
|
|
2107
2131
|
email: String!
|
|
2108
2132
|
|
|
@@ -2112,6 +2136,11 @@ input CustomerCreateInput {
|
|
|
2112
2136
|
"""Last name"""
|
|
2113
2137
|
lastName: String
|
|
2114
2138
|
|
|
2139
|
+
"""
|
|
2140
|
+
Opt-in level. Default SINGLE_OPT_IN (signup = email proven implicitly). Set CONFIRMED_OPT_IN to force double opt-in via confirmation email.
|
|
2141
|
+
"""
|
|
2142
|
+
marketingOptInLevel: MarketingOptInLevel
|
|
2143
|
+
|
|
2115
2144
|
"""Password"""
|
|
2116
2145
|
password: String!
|
|
2117
2146
|
|
|
@@ -2234,6 +2263,29 @@ type CustomerSignupPayload {
|
|
|
2234
2263
|
userErrors: [UserError!]!
|
|
2235
2264
|
}
|
|
2236
2265
|
|
|
2266
|
+
"""Input for newsletter subscribe (guest-friendly)."""
|
|
2267
|
+
input CustomerSubscribeToMarketingInput {
|
|
2268
|
+
"""Email address to subscribe to newsletter"""
|
|
2269
|
+
email: String!
|
|
2270
|
+
|
|
2271
|
+
"""
|
|
2272
|
+
Opt-in level. Guest mutation always enforces CONFIRMED_OPT_IN regardless of value.
|
|
2273
|
+
"""
|
|
2274
|
+
marketingOptInLevel: MarketingOptInLevel
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
"""Customer business type — INDIVIDUAL (B2C) or COMPANY (B2B)."""
|
|
2278
|
+
enum CustomerType {
|
|
2279
|
+
COMPANY
|
|
2280
|
+
INDIVIDUAL
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
"""Input for newsletter unsubscribe (guest-friendly)."""
|
|
2284
|
+
input CustomerUnsubscribeFromMarketingInput {
|
|
2285
|
+
"""Email address to unsubscribe"""
|
|
2286
|
+
email: String!
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2237
2289
|
"""Result of address update"""
|
|
2238
2290
|
type CustomerUpdateAddressPayload {
|
|
2239
2291
|
"""Updated address"""
|
|
@@ -2247,6 +2299,17 @@ type CustomerUpdateAddressPayload {
|
|
|
2247
2299
|
|
|
2248
2300
|
"""Input for customer update"""
|
|
2249
2301
|
input CustomerUpdateInput {
|
|
2302
|
+
"""
|
|
2303
|
+
Email marketing toggle. true → state SUBSCRIBED bezpośrednio (auth'd customer = email proven). false → UNSUBSCRIBED. null/undefined → no change.
|
|
2304
|
+
"""
|
|
2305
|
+
acceptsMarketing: Boolean
|
|
2306
|
+
|
|
2307
|
+
"""Company name (required when customerType is COMPANY)"""
|
|
2308
|
+
companyName: String
|
|
2309
|
+
|
|
2310
|
+
"""Business type discriminator (INDIVIDUAL/COMPANY)"""
|
|
2311
|
+
customerType: CustomerType
|
|
2312
|
+
|
|
2250
2313
|
"""First name"""
|
|
2251
2314
|
firstName: String
|
|
2252
2315
|
|
|
@@ -2255,6 +2318,15 @@ input CustomerUpdateInput {
|
|
|
2255
2318
|
|
|
2256
2319
|
"""Phone number"""
|
|
2257
2320
|
phone: String
|
|
2321
|
+
|
|
2322
|
+
"""Polish business registry number — REGON (9 or 14 digits with checksum)"""
|
|
2323
|
+
regon: String
|
|
2324
|
+
|
|
2325
|
+
"""Polish tax ID — NIP (10 digits with checksum)"""
|
|
2326
|
+
taxId: String
|
|
2327
|
+
|
|
2328
|
+
"""EU VAT number (e.g. PL1234567890)"""
|
|
2329
|
+
vatNumber: String
|
|
2258
2330
|
}
|
|
2259
2331
|
|
|
2260
2332
|
"""Result of customer update"""
|
|
@@ -3215,6 +3287,16 @@ type MailingAddress implements Node {
|
|
|
3215
3287
|
|
|
3216
3288
|
"""Second line of street address"""
|
|
3217
3289
|
streetLine2: String
|
|
3290
|
+
|
|
3291
|
+
"""
|
|
3292
|
+
Per-address tax ID (Polish NIP, 10 digits z checksum). B2B use case: różne dane firmowe per adres — np. faktura na firmę matkę z NIP A, dostawa na oddział z NIP B. Distinct from `Customer.taxId` (customer-level globally).
|
|
3293
|
+
"""
|
|
3294
|
+
taxId: String
|
|
3295
|
+
|
|
3296
|
+
"""
|
|
3297
|
+
Per-address EU VAT number (e.g. PL1234567890). B2B cross-border: różny VAT per adres dostawy. Distinct from `Customer.vatNumber` (customer-level globally).
|
|
3298
|
+
"""
|
|
3299
|
+
vatNumber: String
|
|
3218
3300
|
}
|
|
3219
3301
|
|
|
3220
3302
|
"""Paginated mailing addresses (Relay Connection)"""
|
|
@@ -3272,6 +3354,23 @@ input MailingAddressInput {
|
|
|
3272
3354
|
|
|
3273
3355
|
"""Second line of street address"""
|
|
3274
3356
|
streetLine2: String
|
|
3357
|
+
|
|
3358
|
+
"""
|
|
3359
|
+
Per-address Polish tax ID — NIP (10 digits with checksum). B2B use case: różne dane firmowe per adres dostawy/billing.
|
|
3360
|
+
"""
|
|
3361
|
+
taxId: String
|
|
3362
|
+
|
|
3363
|
+
"""Per-address EU VAT number (e.g. PL1234567890). Cross-border B2B."""
|
|
3364
|
+
vatNumber: String
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
"""
|
|
3368
|
+
Email marketing opt-in level — SINGLE_OPT_IN, CONFIRMED_OPT_IN, UNKNOWN.
|
|
3369
|
+
"""
|
|
3370
|
+
enum MarketingOptInLevel {
|
|
3371
|
+
CONFIRMED_OPT_IN
|
|
3372
|
+
SINGLE_OPT_IN
|
|
3373
|
+
UNKNOWN
|
|
3275
3374
|
}
|
|
3276
3375
|
|
|
3277
3376
|
"""Navigation menu"""
|
|
@@ -3335,6 +3434,119 @@ enum MenuItemType {
|
|
|
3335
3434
|
SEARCH
|
|
3336
3435
|
}
|
|
3337
3436
|
|
|
3437
|
+
"""
|
|
3438
|
+
Payload `customerMetaPropertiesSet` — bulk upsert własnych meta properties klienta
|
|
3439
|
+
"""
|
|
3440
|
+
type MetaPropertiesSetPayload {
|
|
3441
|
+
"""Upserted meta properties (puste gdy userErrors)"""
|
|
3442
|
+
metaProperties: [MetaProperty!]!
|
|
3443
|
+
|
|
3444
|
+
"""User errors (code z `MetaPropertyErrorCode` enum)"""
|
|
3445
|
+
userErrors: [UserError!]!
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
"""Pojedyncze pole dodatkowe (meta property) na encji commerce"""
|
|
3449
|
+
type MetaProperty implements Node {
|
|
3450
|
+
"""Created at"""
|
|
3451
|
+
createdAt: DateTime!
|
|
3452
|
+
|
|
3453
|
+
"""Unique identifier"""
|
|
3454
|
+
id: ID!
|
|
3455
|
+
|
|
3456
|
+
"""
|
|
3457
|
+
true = readable tylko przez authenticated context (admin/customer self). false (default) = readable również przez anonymous Storefront API.
|
|
3458
|
+
"""
|
|
3459
|
+
isPrivate: Boolean!
|
|
3460
|
+
|
|
3461
|
+
"""
|
|
3462
|
+
Key w obrębie namespace (3-64 chars). Unique per (ownerType, ownerId, namespace).
|
|
3463
|
+
"""
|
|
3464
|
+
key: String!
|
|
3465
|
+
|
|
3466
|
+
"""
|
|
3467
|
+
Namespace (3-64 chars). Zapobiega kolizji kluczy między aplikacjami. Reserved prefix: `doswiftly:` (platform).
|
|
3468
|
+
"""
|
|
3469
|
+
namespace: String!
|
|
3470
|
+
|
|
3471
|
+
"""Typ wartości — informuje klienta jak parse value."""
|
|
3472
|
+
type: MetaPropertyValueType!
|
|
3473
|
+
|
|
3474
|
+
"""Last updated at"""
|
|
3475
|
+
updatedAt: DateTime!
|
|
3476
|
+
|
|
3477
|
+
"""
|
|
3478
|
+
Wartość — zawsze String. Klient parsuje zgodnie z polem `type` (np. INTEGER → parseInt, JSON → JSON.parse).
|
|
3479
|
+
"""
|
|
3480
|
+
value: String!
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
"""Paginated meta property list (Relay Connection)"""
|
|
3484
|
+
type MetaPropertyConnection {
|
|
3485
|
+
"""Edges (cursor + node pairs)"""
|
|
3486
|
+
edges: [MetaPropertyEdge!]!
|
|
3487
|
+
|
|
3488
|
+
"""Nodes (shortcut bez cursor)"""
|
|
3489
|
+
nodes: [MetaProperty!]!
|
|
3490
|
+
|
|
3491
|
+
"""Page info"""
|
|
3492
|
+
pageInfo: PageInfo!
|
|
3493
|
+
|
|
3494
|
+
"""Total count (max po wszystkich pages)"""
|
|
3495
|
+
totalCount: Int!
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
"""Payload `customerMetaPropertyDelete`"""
|
|
3499
|
+
type MetaPropertyDeletePayload {
|
|
3500
|
+
"""ID usuniętego meta property (null gdy userErrors)"""
|
|
3501
|
+
deletedId: ID
|
|
3502
|
+
|
|
3503
|
+
"""User errors (code z `MetaPropertyErrorCode` enum)"""
|
|
3504
|
+
userErrors: [UserError!]!
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
"""Meta property edge (Relay pagination)"""
|
|
3508
|
+
type MetaPropertyEdge {
|
|
3509
|
+
"""Cursor for pagination"""
|
|
3510
|
+
cursor: String!
|
|
3511
|
+
|
|
3512
|
+
"""Meta property node"""
|
|
3513
|
+
node: MetaProperty!
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
"""Input dla pojedynczego meta property w bulk set"""
|
|
3517
|
+
input MetaPropertyInput {
|
|
3518
|
+
"""Visibility — true = admin/customer-only, false = storefront-readable"""
|
|
3519
|
+
isPrivate: Boolean = false
|
|
3520
|
+
|
|
3521
|
+
"""Key (3-64 chars)"""
|
|
3522
|
+
key: String!
|
|
3523
|
+
|
|
3524
|
+
"""Namespace (3-64 chars, NOT starting with `doswiftly:`)"""
|
|
3525
|
+
namespace: String!
|
|
3526
|
+
|
|
3527
|
+
"""Typ wartości (driver walidacji backend-side)"""
|
|
3528
|
+
type: MetaPropertyValueType!
|
|
3529
|
+
|
|
3530
|
+
"""Wartość jako String (parse zgodnie z type)"""
|
|
3531
|
+
value: String!
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
"""
|
|
3535
|
+
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).
|
|
3536
|
+
"""
|
|
3537
|
+
enum MetaPropertyValueType {
|
|
3538
|
+
BOOLEAN
|
|
3539
|
+
COLOR
|
|
3540
|
+
DATE
|
|
3541
|
+
DATE_TIME
|
|
3542
|
+
DECIMAL
|
|
3543
|
+
INTEGER
|
|
3544
|
+
JSON
|
|
3545
|
+
STRING
|
|
3546
|
+
TEXT
|
|
3547
|
+
URL
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3338
3550
|
"""Monetary value with currency"""
|
|
3339
3551
|
type Money {
|
|
3340
3552
|
"""Decimal money amount"""
|
|
@@ -3432,6 +3644,12 @@ type Mutation {
|
|
|
3432
3644
|
"""Logout customer (clears auth cookie)"""
|
|
3433
3645
|
customerLogout: CustomerLogoutPayload!
|
|
3434
3646
|
|
|
3647
|
+
"""Bulk upsert własnych meta properties klienta (Bearer token wymagany)"""
|
|
3648
|
+
customerMetaPropertiesSet(properties: [MetaPropertyInput!]!): MetaPropertiesSetPayload!
|
|
3649
|
+
|
|
3650
|
+
"""Usuwa pojedyncze meta property klienta po (namespace, key)"""
|
|
3651
|
+
customerMetaPropertyDelete(key: String!, namespace: String!): MetaPropertyDeletePayload!
|
|
3652
|
+
|
|
3435
3653
|
"""Refresh access token"""
|
|
3436
3654
|
customerRefreshToken: CustomerRefreshTokenPayload!
|
|
3437
3655
|
|
|
@@ -3455,6 +3673,12 @@ type Mutation {
|
|
|
3455
3673
|
"""Register new customer"""
|
|
3456
3674
|
customerSignup(input: CustomerCreateInput!): CustomerSignupPayload!
|
|
3457
3675
|
|
|
3676
|
+
"""Subscribe an email to the newsletter (guest-friendly, double opt-in)."""
|
|
3677
|
+
customerSubscribeToMarketing(input: CustomerSubscribeToMarketingInput!): SubscribeToMarketingPayload!
|
|
3678
|
+
|
|
3679
|
+
"""Unsubscribe an email from the newsletter (guest-friendly, idempotent)."""
|
|
3680
|
+
customerUnsubscribeFromMarketing(input: CustomerUnsubscribeFromMarketingInput!): UnsubscribeFromMarketingPayload!
|
|
3681
|
+
|
|
3458
3682
|
"""Update customer profile"""
|
|
3459
3683
|
customerUpdate(customer: CustomerUpdateInput!): CustomerUpdatePayload!
|
|
3460
3684
|
|
|
@@ -3541,6 +3765,12 @@ type Order implements Node {
|
|
|
3541
3765
|
"""Line items count"""
|
|
3542
3766
|
itemCount: Int!
|
|
3543
3767
|
|
|
3768
|
+
"""Lista meta properties (Relay Connection)"""
|
|
3769
|
+
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
3770
|
+
|
|
3771
|
+
"""Pojedyncze meta property po (namespace, key)"""
|
|
3772
|
+
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
3773
|
+
|
|
3544
3774
|
"""Order number (human-readable)"""
|
|
3545
3775
|
orderNumber: String!
|
|
3546
3776
|
|
|
@@ -3820,9 +4050,9 @@ type Product implements Node {
|
|
|
3820
4050
|
averageRating: Float
|
|
3821
4051
|
|
|
3822
4052
|
"""
|
|
3823
|
-
|
|
4053
|
+
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.
|
|
3824
4054
|
"""
|
|
3825
|
-
|
|
4055
|
+
categories: [Category!]!
|
|
3826
4056
|
|
|
3827
4057
|
"""
|
|
3828
4058
|
Compare-at price range (Money pair). Null gdy żaden variant nie ma compareAtPrice.
|
|
@@ -3861,6 +4091,12 @@ type Product implements Node {
|
|
|
3861
4091
|
"""
|
|
3862
4092
|
isPurchasable: Boolean!
|
|
3863
4093
|
|
|
4094
|
+
"""Lista meta properties — Storefront API filtruje isPrivate=false"""
|
|
4095
|
+
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
4096
|
+
|
|
4097
|
+
"""Pojedyncze meta property — Storefront API filtruje isPrivate=false"""
|
|
4098
|
+
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
4099
|
+
|
|
3864
4100
|
"""
|
|
3865
4101
|
Per-product option definitions (Color, Size, …) with their available values. Use these to build a variant picker without aggregating `selectedOptions` manually.
|
|
3866
4102
|
"""
|
|
@@ -3874,6 +4110,11 @@ type Product implements Node {
|
|
|
3874
4110
|
"""
|
|
3875
4111
|
priceRangeWithConversion: ConvertedPriceRange
|
|
3876
4112
|
|
|
4113
|
+
"""
|
|
4114
|
+
Domyślna kategoria dla breadcrumb/nav (= categories[0] po sortOrder). Null gdy produkt nie jest w żadnej kategorii.
|
|
4115
|
+
"""
|
|
4116
|
+
primaryCategory: Category
|
|
4117
|
+
|
|
3877
4118
|
"""Similar products recommendations"""
|
|
3878
4119
|
recommendations(first: Int = 4): ProductRecommendations
|
|
3879
4120
|
|
|
@@ -4060,11 +4301,6 @@ input ProductFilter {
|
|
|
4060
4301
|
"""Filter by variant price range"""
|
|
4061
4302
|
price: PriceRangeFilter
|
|
4062
4303
|
|
|
4063
|
-
"""
|
|
4064
|
-
Filter by product category (free-text classification, stored on Product.category). Distinct from `category: CategoryFilter` which selects by structured Category entity.
|
|
4065
|
-
"""
|
|
4066
|
-
productCategory: String
|
|
4067
|
-
|
|
4068
4304
|
"""
|
|
4069
4305
|
Filter by product type enum (PHYSICAL/DIGITAL/SERVICE/SUBSCRIPTION/GIFT_CARD). Distinct from `productCategory` (free-text classification).
|
|
4070
4306
|
"""
|
|
@@ -4262,6 +4498,12 @@ type ProductVariant {
|
|
|
4262
4498
|
"""Whether variant is available for purchase"""
|
|
4263
4499
|
isAvailable: Boolean!
|
|
4264
4500
|
|
|
4501
|
+
"""Lista meta properties — Storefront API filtruje isPrivate=false"""
|
|
4502
|
+
metaProperties(first: Int = 10, namespace: String): MetaPropertyConnection!
|
|
4503
|
+
|
|
4504
|
+
"""Pojedyncze meta property — Storefront API filtruje isPrivate=false"""
|
|
4505
|
+
metaProperty(key: String!, namespace: String!): MetaProperty
|
|
4506
|
+
|
|
4265
4507
|
"""Variant price (Money). Default field — industry-standard schema."""
|
|
4266
4508
|
price: Money!
|
|
4267
4509
|
|
|
@@ -4395,20 +4637,30 @@ type Query {
|
|
|
4395
4637
|
"""Get cart by ID"""
|
|
4396
4638
|
cart(id: ID!): Cart
|
|
4397
4639
|
|
|
4398
|
-
"""
|
|
4640
|
+
"""
|
|
4641
|
+
Lista kategorii (Relay Connection) z opcjonalnym filtrem rootsOnly/parentId
|
|
4642
|
+
"""
|
|
4399
4643
|
categories(
|
|
4400
|
-
"""
|
|
4644
|
+
"""Forward pagination cursor (after this element)"""
|
|
4401
4645
|
after: String
|
|
4402
4646
|
|
|
4403
|
-
"""
|
|
4404
|
-
|
|
4647
|
+
"""Backward pagination cursor (before this element)"""
|
|
4648
|
+
before: String
|
|
4649
|
+
|
|
4650
|
+
"""Forward pagination — first N elements"""
|
|
4651
|
+
first: Int
|
|
4405
4652
|
|
|
4406
|
-
"""
|
|
4653
|
+
"""Backward pagination — last N elements"""
|
|
4654
|
+
last: Int
|
|
4655
|
+
|
|
4656
|
+
"""
|
|
4657
|
+
Filter by parent category ID — używaj `null` semantically dla "roots" via `rootsOnly` flag
|
|
4658
|
+
"""
|
|
4407
4659
|
parentId: ID
|
|
4408
4660
|
|
|
4409
|
-
"""
|
|
4661
|
+
"""Tylko root categories (parentId IS NULL)"""
|
|
4410
4662
|
rootsOnly: Boolean! = false
|
|
4411
|
-
):
|
|
4663
|
+
): CategoryConnection!
|
|
4412
4664
|
|
|
4413
4665
|
"""Get category by ID or slug"""
|
|
4414
4666
|
category(
|
|
@@ -5662,6 +5914,17 @@ enum StorefrontOrderStatus {
|
|
|
5662
5914
|
PROCESSING
|
|
5663
5915
|
}
|
|
5664
5916
|
|
|
5917
|
+
"""Payload for customerSubscribeToMarketing mutation"""
|
|
5918
|
+
type SubscribeToMarketingPayload {
|
|
5919
|
+
"""
|
|
5920
|
+
Always true (anti-enumeration). Backend may silently skip enqueue if rate-limited or already SUBSCRIBED.
|
|
5921
|
+
"""
|
|
5922
|
+
accepted: Boolean!
|
|
5923
|
+
|
|
5924
|
+
"""User-facing errors"""
|
|
5925
|
+
userErrors: [UserError!]!
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5665
5928
|
"""Tax line item"""
|
|
5666
5929
|
type TaxLine {
|
|
5667
5930
|
"""Tax amount"""
|
|
@@ -5756,6 +6019,15 @@ Unsigned 64-bit integer, JSON-serialized as String (BigInt-safe). Format: "{inte
|
|
|
5756
6019
|
"""
|
|
5757
6020
|
scalar UnsignedInt64
|
|
5758
6021
|
|
|
6022
|
+
"""Payload for customerUnsubscribeFromMarketing mutation"""
|
|
6023
|
+
type UnsubscribeFromMarketingPayload {
|
|
6024
|
+
"""Always true (anti-enumeration)."""
|
|
6025
|
+
accepted: Boolean!
|
|
6026
|
+
|
|
6027
|
+
"""User-facing errors"""
|
|
6028
|
+
userErrors: [UserError!]!
|
|
6029
|
+
}
|
|
6030
|
+
|
|
5759
6031
|
"""URL redirect (SEO, store migration)"""
|
|
5760
6032
|
type UrlRedirect {
|
|
5761
6033
|
"""Source path"""
|