@doswiftly/storefront-sdk 13.0.0 → 14.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
CHANGED
|
@@ -1,5 +1,249 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 14.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 1fda2ab: **BREAKING**: URL identifier unification (`slug` → `handle`) + type renames for naming consistency.
|
|
8
|
+
|
|
9
|
+
### What changed
|
|
10
|
+
|
|
11
|
+
URL-friendly identifiers across the storefront GraphQL API now consistently use `handle` (previously a mix of `slug` and `handle`). Canonical product brand entity renamed from `BrandSummary` to `Brand`. Shop branding type `Brand` renamed to `ShopBrand` to free the `Brand` name for the product entity.
|
|
12
|
+
|
|
13
|
+
### Field renames (GraphQL response)
|
|
14
|
+
|
|
15
|
+
| Type | Old | New |
|
|
16
|
+
| ---------------------------- | ------ | -------- |
|
|
17
|
+
| `Category` | `slug` | `handle` |
|
|
18
|
+
| `BlogPost` | `slug` | `handle` |
|
|
19
|
+
| `BlogCategory` | `slug` | `handle` |
|
|
20
|
+
| `BlogTag` | `slug` | `handle` |
|
|
21
|
+
| `LoyaltyReward` | `slug` | `handle` |
|
|
22
|
+
| `BrandFilter` (input) | `slug` | `handle` |
|
|
23
|
+
| `BrandFilterValue` | `slug` | `handle` |
|
|
24
|
+
| `ProductAttributeDefinition` | `slug` | `handle` |
|
|
25
|
+
| `CategoryFilterOption` | `slug` | `handle` |
|
|
26
|
+
|
|
27
|
+
### Type renames
|
|
28
|
+
- `BrandSummary` → `Brand` (canonical product brand entity, e.g. Funko, Nike). Update `__typename` checks and fragment spreads.
|
|
29
|
+
- `Brand` (shop branding) → `ShopBrand` (tenant-scoped shop metadata: logo, slogan, colors).
|
|
30
|
+
|
|
31
|
+
### Query args renamed
|
|
32
|
+
|
|
33
|
+
```graphql
|
|
34
|
+
# Old
|
|
35
|
+
query { category(slug: "figurki") { id name } }
|
|
36
|
+
query { blogPost(slug: "post-handle") { title } }
|
|
37
|
+
query BlogPosts($categorySlug: String, $tagSlug: String) {
|
|
38
|
+
blogPosts(categorySlug: $categorySlug, tagSlug: $tagSlug) { ... }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# New
|
|
42
|
+
query { category(handle: "figurki") { id name } }
|
|
43
|
+
query { blogPost(handle: "post-handle") { title } }
|
|
44
|
+
query BlogPosts($categoryHandle: String, $tagHandle: String) {
|
|
45
|
+
blogPosts(categoryHandle: $categoryHandle, tagHandle: $tagHandle) { ... }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Brand filter input
|
|
50
|
+
|
|
51
|
+
```graphql
|
|
52
|
+
# Old
|
|
53
|
+
products(filters: [{ brand: { slug: "funko" } }]) { ... }
|
|
54
|
+
|
|
55
|
+
# New
|
|
56
|
+
products(filters: [{ brand: { handle: "funko" } }]) { ... }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### MenuItem resource union
|
|
60
|
+
|
|
61
|
+
```graphql
|
|
62
|
+
# Old
|
|
63
|
+
fragment MenuItem on MenuItem {
|
|
64
|
+
resource {
|
|
65
|
+
__typename
|
|
66
|
+
... on Category {
|
|
67
|
+
id
|
|
68
|
+
slug
|
|
69
|
+
name
|
|
70
|
+
}
|
|
71
|
+
... on BrandSummary {
|
|
72
|
+
id
|
|
73
|
+
slug
|
|
74
|
+
name
|
|
75
|
+
logo
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# New
|
|
81
|
+
fragment MenuItem on MenuItem {
|
|
82
|
+
resource {
|
|
83
|
+
__typename
|
|
84
|
+
... on Category {
|
|
85
|
+
id
|
|
86
|
+
handle
|
|
87
|
+
name
|
|
88
|
+
}
|
|
89
|
+
... on Brand {
|
|
90
|
+
id
|
|
91
|
+
handle
|
|
92
|
+
name
|
|
93
|
+
logo
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Migration steps
|
|
100
|
+
1. Update to `@doswiftly/storefront-operations@^14.0.0` + `@doswiftly/storefront-sdk@^14.0.0`.
|
|
101
|
+
2. Run your codegen — TypeScript will surface every call site that needs update.
|
|
102
|
+
3. Find/replace in storefront code:
|
|
103
|
+
- `.slug` → `.handle` on response fields listed above.
|
|
104
|
+
- `__typename === 'BrandSummary'` → `__typename === 'Brand'`.
|
|
105
|
+
- `{ brand: { slug } }` filter input → `{ brand: { handle } }`.
|
|
106
|
+
- `categorySlug`/`tagSlug` query args → `categoryHandle`/`tagHandle`.
|
|
107
|
+
4. Update GraphQL fragments selecting renamed fields.
|
|
108
|
+
5. Re-run codegen, fix any remaining TypeScript errors.
|
|
109
|
+
|
|
110
|
+
### Why
|
|
111
|
+
|
|
112
|
+
A single naming convention (`handle`) across all URL identifiers removes ambiguity and matches common e-commerce GraphQL convention. The `BrandSummary` suffix was misleading — no full `Brand` ObjectType existed, so the suffix suggested a missing parent type. Shop branding `Brand` blocked the cleaner name for the product entity.
|
|
113
|
+
|
|
114
|
+
Full migration guide with code examples: <https://docs.doswiftly.pl/storefront-developer/migration-14.0>
|
|
115
|
+
|
|
116
|
+
Naming conventions reference: <https://docs.doswiftly.pl/storefront-developer/api/naming-conventions>
|
|
117
|
+
|
|
118
|
+
### Minor Changes
|
|
119
|
+
|
|
120
|
+
- 507d487: Storefront GraphQL: nowy `attributeOptionsSearch(input)` query — paginated + searchable lista opcji atrybutu (Relay Connection).
|
|
121
|
+
|
|
122
|
+
Use case: storefront UI z dużymi filtrami atrybutów (np. 800+ licencji, materiałów, rozmiarów, kolorów) — dropdown z autocomplete i paginacją bez zassania całej listy. `productFilters.attributes[].filterValues[]` zostaje jako quick browse facets (top N), `attributeOptionsSearch` służy do drill-into specific filter card z searchem.
|
|
123
|
+
|
|
124
|
+
Dla **marek/producentów** preferuj dedykowane API `productFilters.brands[]` (Brand jest pełnoprawną encją z logo i productCount) — `attributeOptionsSearch(handle: "marka")` jest legacy fallback dla katalogów bez kanonicznej Brand entity.
|
|
125
|
+
|
|
126
|
+
**Input** (`AttributeOptionsSearchInput`):
|
|
127
|
+
- `handle: String!` — slug atrybutu (np. `"marka"`)
|
|
128
|
+
- `contextInput: AvailableFiltersInput` — produktowy kontekst (kategoria/kolekcja/search/currentFilters) z exclude-self semantyką
|
|
129
|
+
- `first: Int` (1–100, default 50)
|
|
130
|
+
- `after: String` — opaque cursor
|
|
131
|
+
- `search: String` — case-insensitive ILIKE (max 100 znaków)
|
|
132
|
+
- `sort: AttributeOptionsSearchSort` — `COUNT_DESC` (default, most popular first) lub `LABEL_ASC` (alfabetycznie z polskim collation)
|
|
133
|
+
|
|
134
|
+
**Output** (`AttributeFilterValueConnection`): `edges { cursor node }`, `nodes` (shortcut), `pageInfo`, `totalCount`. Wspiera atrybuty `SELECT`/`CHECKBOX`/`RADIO`/`COLOR` (z bazy `attribute_options`) oraz `TEXT`/`TEXTAREA` (z aggregated unique values).
|
|
135
|
+
|
|
136
|
+
**Migration**: brak breaking changes — `productFilters` zachowuje obecny shape. Jeśli budujesz "Pokaż wszystkie marki" UI, przenoś logikę na nowy query żeby uniknąć over-fetching.
|
|
137
|
+
|
|
138
|
+
- 6f8d873: `CartWarningCode` enum gains a fourth value, `SHIPPING_METHOD_AUTO_CLEARED`.
|
|
139
|
+
|
|
140
|
+
Cart line mutations (`cartAddLines`, `cartUpdateLines`, `cartRemoveLines`) now reconcile the selected shipping method against cart contents on every call. When a mutation leaves the cart with no items that require physical delivery (digital-only or empty) but a shipping method is still set, the method is cleared automatically and the mutation response surfaces a `CartWarning { code: "SHIPPING_METHOD_AUTO_CLEARED", target: "selectedShippingMethod", message: "…" }`.
|
|
141
|
+
|
|
142
|
+
This closes the gap where a mixed cart that lost its last physical line via the storefront UI would persist a stale shipping method and fail later at checkout submit with `FORBIDDEN_SHIPPING_METHOD`. There is no escape hatch needed on the storefront — the mutation accepts non-null `shippingMethodId` only, so the backend takes responsibility for the transition.
|
|
143
|
+
|
|
144
|
+
**Storefront impact**: none required. Reads of `cart.selectedShippingMethod` already returned `null` once cleared. Apps that already consume the `warnings[]` array (Apps that follow the standard `userErrors[]` + `warnings[]` payload shape) will surface the auto-clear toast for free. Apps that ignore `warnings[]` continue to work — the mutation still succeeds and the cart is left in a consistent state.
|
|
145
|
+
|
|
146
|
+
**Locale**: `message` is translated server-side (`Accept-Language`) — for example, `pl` returns "Wybrana metoda wysyłki została usunięta, ponieważ koszyk nie wymaga już dostawy.", `en` returns "The selected shipping method was removed because the cart no longer requires delivery."
|
|
147
|
+
|
|
148
|
+
The warning fires only on the line mutation that triggers the transition, never on subsequent reads or unrelated mutations. Decrementing a physical line quantity from `3` to `1`, removing one physical line while another remains, or adding digital lines to a mixed cart all leave the shipping method untouched.
|
|
149
|
+
|
|
150
|
+
- 1fda2ab: Storefront navigation menus: typed `resource` field eksponowany w `MenuItem` fragment + nowy typ linku `BRAND`.
|
|
151
|
+
|
|
152
|
+
**Co nowego**:
|
|
153
|
+
- `MenuItem.resource: MenuItemResource` — typed union (`Category` / `Collection` / `ShopPage` / `Product` / `BrandSummary`) wystawiony w `MenuItem` fragment na wszystkich 3 poziomach zagnieżdżenia. Każdy member zwraca `slug` (Category/Product/BrandSummary) lub `handle` (Collection/ShopPage) gotowy do budowy własnego URL.
|
|
154
|
+
- `MenuItemType.BRAND` — nowy typ linku menu dla marek producentów. `BrandSummary` w `resource` union (`id`, `name`, `slug`, `logo`).
|
|
155
|
+
- `MenuItem.url` dla `BRAND` resolve'uje się serwerowo jako `/brands/<slug>` — spójnie z istniejącą konwencją `/categories|/collections|/pages|/products/<slug>`.
|
|
156
|
+
|
|
157
|
+
**Dwa sposoby renderingu link target**:
|
|
158
|
+
1. **Standard route convention** — użyj `item.url` jak dotąd. Działa jeśli storefront ma routy `/categories/[slug]`, `/collections/[handle]`, `/pages/[handle]`, `/products/[handle]`, `/brands/[slug]`.
|
|
159
|
+
2. **Custom routing** (Next.js single-segment, Remix, itd.) — odczytaj `item.resource.__typename` + per-type `slug`/`handle`:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
function buildHref(item: MenuItem): string {
|
|
163
|
+
if (!item.resource) return item.url ?? "#";
|
|
164
|
+
switch (item.resource.__typename) {
|
|
165
|
+
case "Category":
|
|
166
|
+
return `/${item.resource.slug}`;
|
|
167
|
+
case "Collection":
|
|
168
|
+
return `/${item.resource.handle}`;
|
|
169
|
+
case "ShopPage":
|
|
170
|
+
return `/${item.resource.handle}`;
|
|
171
|
+
case "Product":
|
|
172
|
+
return `/p/${item.resource.handle}`;
|
|
173
|
+
case "BrandSummary":
|
|
174
|
+
return `/marka/${item.resource.slug}`;
|
|
175
|
+
}
|
|
176
|
+
return item.url ?? "#";
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Performance**: wszystkie resource lookupy są batchowane per request — zero N+1 nawet w głębokich menu.
|
|
181
|
+
|
|
182
|
+
**Migration**: brak breaking changes. Kod używający tylko `item.url` działa bez zmian. `resource` jest opt-in dopóki nie zaczniesz go selekcjonować w custom fragmencie.
|
|
183
|
+
|
|
184
|
+
## 13.1.0
|
|
185
|
+
|
|
186
|
+
### Minor Changes
|
|
187
|
+
|
|
188
|
+
- 2e137ad: Added cart-aware shipping methods discovery: `Cart.requiresShipping`, `CartLine.requiresShipping`, and `cart.availableShippingMethods(address)`.
|
|
189
|
+
|
|
190
|
+
**What's new for the storefront**
|
|
191
|
+
- **`cart.requiresShipping: Boolean!`** — single signal whether the cart needs physical shipping at all. `false` for carts that contain only digital goods (downloads, gift cards, services, subscriptions). Use it to skip the shipping picker step in checkout entirely.
|
|
192
|
+
- **`cart.lines.nodes[].requiresShipping: Boolean!`** — per-line classification. Useful in cart drawer UI to flag physical vs digital lines independently.
|
|
193
|
+
- **`cart.availableShippingMethods(address: ShippingAddressInput!): AvailableShippingMethodsPayload!`** — field on `Cart` that returns shipping methods for the cart's contents at the given destination. The backend pulls subtotal and weight from the cart aggregate — you no longer need to compute them on the client. For digital-only carts the response is `{ methods: [], userErrors: [{ code: 'DIGITAL_ONLY_NO_SHIPPING' }] }`.
|
|
194
|
+
- **`CartAvailableShippingMethods` query** — new named operation ready to drop into your storefront codegen pipeline.
|
|
195
|
+
|
|
196
|
+
**Recommended checkout flow**
|
|
197
|
+
|
|
198
|
+
```graphql
|
|
199
|
+
query CartAvailableShippingMethods(
|
|
200
|
+
$cartId: ID!
|
|
201
|
+
$address: ShippingAddressInput!
|
|
202
|
+
) {
|
|
203
|
+
cart(id: $cartId) {
|
|
204
|
+
id
|
|
205
|
+
requiresShipping
|
|
206
|
+
availableShippingMethods(address: $address) {
|
|
207
|
+
methods {
|
|
208
|
+
id
|
|
209
|
+
name
|
|
210
|
+
price {
|
|
211
|
+
amount
|
|
212
|
+
currencyCode
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
userErrors {
|
|
216
|
+
code
|
|
217
|
+
message
|
|
218
|
+
field
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If `cart.requiresShipping === false`, skip rendering the shipping picker and proceed straight to payment. Calling `cartSelectShippingMethod` on a digital-only cart returns `userErrors[{ code: 'FORBIDDEN_SHIPPING_METHOD' }]`, matching the readiness check that `cartComplete` already enforces.
|
|
226
|
+
|
|
227
|
+
**Backward compatibility**
|
|
228
|
+
- The existing standalone `availableShippingMethods(address, cart: CartShippingInput)` query is **unchanged**. Keep using it for pre-cart shipping calculators on product detail pages (when the customer has not created a cart yet).
|
|
229
|
+
- The new field and new query are purely additive. No existing operations change shape.
|
|
230
|
+
|
|
231
|
+
**Free-shipping progress + delivery estimates are now localized**
|
|
232
|
+
|
|
233
|
+
The `freeShippingProgress.message` field and `estimatedDelivery.description` field used to be hardcoded in English. They now respect the `Accept-Language` header (defaults to Polish — `pl`). If your storefront previously parsed these strings, switch to the structured fields (`qualifies`, `progressPercent`, `remaining.amount`, `minDays`, `maxDays`) instead.
|
|
234
|
+
|
|
235
|
+
**Reason codes on shipping `userErrors`**
|
|
236
|
+
|
|
237
|
+
| Code | When | UI reaction |
|
|
238
|
+
| -------------------------- | -------------------------------- | -------------------------------------------- |
|
|
239
|
+
| `DIGITAL_ONLY_NO_SHIPPING` | Cart contains only digital items | Skip shipping picker, go straight to payment |
|
|
240
|
+
| `NO_SHIPPING_METHODS` | Address has no matching zone | Show "no delivery to your country" |
|
|
241
|
+
| `SHIPPING_ERROR` | Internal service error (rare) | Retry or fall back to pre-cart preview query |
|
|
242
|
+
|
|
243
|
+
**Why both packages bump together**
|
|
244
|
+
|
|
245
|
+
`@doswiftly/storefront-sdk` ships local copies of the cart fragments that this update extends with `requiresShipping`, so the SDK release pairs with the schema/operations release. `@doswiftly/commerce-policy` exports `NON_PHYSICAL_TYPES` for downstream tooling that wants to mirror the same classification on the client side.
|
|
246
|
+
|
|
3
247
|
## 13.0.0
|
|
4
248
|
|
|
5
249
|
### Major Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"cart.d.ts","sourceRoot":"","sources":["../../../src/core/operations/cart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAsSH,eAAO,MAAM,UAAU,QAOrB,CAAC;AAMH,eAAO,MAAM,WAAW,QAWtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAWzB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAW5B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAW3B,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAMH,eAAO,MAAM,yBAAyB,QAWpC,CAAC;AAEH,eAAO,MAAM,wBAAwB,QAWnC,CAAC;AAEH,eAAO,MAAM,2BAA2B,QAWtC,CAAC;AAEH,eAAO,MAAM,0BAA0B,QAWrC,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAW/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,QAWhC,CAAC;AAEH,eAAO,MAAM,+BAA+B,QAW1C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAWxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QASzB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,QAoBtC,CAAC"}
|
|
@@ -113,6 +113,7 @@ const CART_LINE_FRAGMENT = `
|
|
|
113
113
|
productTitle
|
|
114
114
|
productHandle
|
|
115
115
|
productType
|
|
116
|
+
requiresShipping
|
|
116
117
|
}
|
|
117
118
|
${CART_LINE_COST_FRAGMENT}
|
|
118
119
|
${PRODUCT_VARIANT_FRAGMENT}
|
|
@@ -207,6 +208,7 @@ const CART_FRAGMENT = `
|
|
|
207
208
|
selectedShippingMethod { ...CartShippingMethod }
|
|
208
209
|
selectedPaymentMethod { ...CartSelectedPaymentMethod }
|
|
209
210
|
appliedGiftCards { ...CartAppliedGiftCard }
|
|
211
|
+
requiresShipping
|
|
210
212
|
createdAt
|
|
211
213
|
updatedAt
|
|
212
214
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doswiftly/storefront-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "Storefront runtime SDK for DoSwiftly Commerce — layered transport, middleware pipeline, React providers, Zustand stores, cache strategies. 0 runtime dependencies in core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|